Fix font rendering

This commit is contained in:
2026-03-07 18:40:33 -03:00
parent 5ad4188567
commit 7fcce0da98
8 changed files with 307 additions and 148 deletions
+1 -1
View File
@@ -45,7 +45,7 @@ export class AssetManager {
audio.load();
this.assets[key] = audio;
this.loading_promises.push(promise);
} else if (src.endsWith(".txt") || src.endsWith(".md")) {
} else if (src.endsWith(".txt") || src.endsWith(".md") || src.endsWith(".fnt")) {
const promise = async () => {
const resp = await fetch(src);
this.assets[key] = await resp.text();
-12
View File
@@ -5,24 +5,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>bworld</title>
<style>
/*
Font by Daniel Linssen (https://managore.itch.io)
https://managore.itch.io/m6x11
*/
@font-face {
font-family: "m6x11";
src: url("/assets/fonts/m6x11.ttf");
font-weight: normal;
font-style: normal;
font-smooth: never;
}
html, body {
width: 100vw;
height: 100vh;
margin: 0;
overflow: hidden;
}
canvas {
display: block;
image-rendering: pixelated;
+6 -1
View File
@@ -2,7 +2,7 @@ import { EverythingRegistry } from "../common/everything_registry.ts";
import { AssetManager } from "./assets.ts";
import { ClientWorld } from "./client_world.ts";
import { InputManager } from "./input_manager.ts";
import { begin_drawing, end_drawing, init_window } from "./renderer.ts";
import { begin_drawing, end_drawing, init_font, init_window } from "./renderer.ts";
EverythingRegistry.add_registry("tiles");
await import("./tiles/mod.ts");
@@ -77,8 +77,13 @@ AssetManager.instance.load("bworld:player", "/assets/sprites/player.png");
AssetManager.instance.load("bworld:roguelike", "/assets/sprites/roguelike.png");
AssetManager.instance.load("bworld:ui", "/assets/sprites/ui.png");
AssetManager.instance.load("bworld:m6x11", "/assets/fonts/m6x11.png");
AssetManager.instance.load("bworld:m6x11_fnt", "/assets/fonts/m6x11.fnt");
await AssetManager.instance.load_all();
init_font();
const client_world = new ClientWorld();
const loop = new ClientLoop(client_world);
+152 -115
View File
@@ -1,8 +1,9 @@
import { AssetManager } from "./assets.ts";
import { Camera } from "./components/camera.ts";
const MAX_SPRITES = 10000;
const VERTS_PER_SPRITE = 6;
const FLOATS_PER_VERT = 4;
const FLOATS_PER_VERT = 8;
let gl: WebGLRenderingContext;
export let canvas: HTMLCanvasElement;
@@ -27,18 +28,15 @@ let _texture_loc: WebGLUniformLocation | null;
let current_texture: WebGLTexture | null = null;
const glyph_size = 32;
let font_texture: WebGLTexture;
const glyphs: Record<string, { u0: number; v0: number; u1: number; v1: number }> = {};
const vertex_src = `
attribute vec2 aPos;
attribute vec2 aUV;
attribute vec4 aColor;
varying vec2 vUV;
varying vec4 vColor;
uniform vec2 uResolution;
uniform vec2 uCameraPos;
uniform float uCameraZoom;
uniform float uCameraRot;
@@ -70,16 +68,19 @@ void main(){
gl_Position = vec4(clip * vec2(1.0,-1.0),0.0,1.0);
vUV = aUV;
vColor = aColor;
}
`;
const fragment_src = `
precision mediump float;
varying vec2 vUV;
varying vec4 vColor;
uniform sampler2D uTex;
void main(){
gl_FragColor = texture2D(uTex,vUV);
void main() {
vec4 texColor = texture2D(uTex, vUV);
gl_FragColor = texColor * vColor;
}
`;
@@ -116,6 +117,10 @@ export function init_window(canvas_element: HTMLCanvasElement) {
gl.enableVertexAttribArray(uv_loc);
gl.vertexAttribPointer(uv_loc, 2, gl.FLOAT, false, stride, 8);
const color_loc = gl.getAttribLocation(program, "aColor");
gl.enableVertexAttribArray(color_loc);
gl.vertexAttribPointer(color_loc, 4, gl.FLOAT, false, FLOATS_PER_VERT * 4, 16);
resolution_loc = gl.getUniformLocation(program, "uResolution");
_texture_loc = gl.getUniformLocation(program, "uTex");
@@ -129,8 +134,6 @@ export function init_window(canvas_element: HTMLCanvasElement) {
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
init_font();
}
function create_shader(type: number, src: string): WebGLShader {
@@ -253,38 +256,6 @@ export function draw_texture_region(
push_quad(dx, dy, dw, dh, u0, v0, u1, v1);
}
export function draw_text(str: string, x: number, y: number) {
if (!font_texture) {
return;
}
if (current_texture !== font_texture) {
flush_batch();
current_texture = font_texture;
}
for (const ch of str) {
const g = glyphs[ch];
if (!g) {
x += glyph_size;
continue;
}
push_quad(
x,
y,
glyph_size,
glyph_size,
g.u0,
g.v0,
g.u1,
g.v1,
);
x += glyph_size * 0.6;
}
}
function flush_batch() {
if (vert_index === 0 || !current_texture) {
return;
@@ -295,12 +266,11 @@ function flush_batch() {
gl.useProgram(program);
//const posLoc = gl.getAttribLocation(program, "aPos");
//const uvLoc = gl.getAttribLocation(program, "aUV");
gl.enableVertexAttribArray(pos_loc);
gl.vertexAttribPointer(pos_loc, 2, gl.FLOAT, false, 16, 0);
gl.enableVertexAttribArray(uv_loc);
gl.vertexAttribPointer(uv_loc, 2, gl.FLOAT, false, 16, 8);
// const stride = FLOATS_PER_VERT * 4;
// gl.enableVertexAttribArray(pos_loc);
// gl.vertexAttribPointer(pos_loc, 2, gl.FLOAT, false, stride, 0);
// gl.enableVertexAttribArray(uv_loc);
// gl.vertexAttribPointer(uv_loc, 2, gl.FLOAT, false, stride, 8);
gl.uniform2f(camera_pos_loc, Math.floor(camera.x), Math.floor(camera.y));
gl.uniform1f(camera_zoom_loc, camera.zoom);
@@ -312,84 +282,39 @@ function flush_batch() {
vert_index = 0;
}
function push_quad(dx: number, dy: number, dw: number, dh: number, u0: number, v0: number, u1: number, v1: number) {
function push_quad(
dx: number,
dy: number,
dw: number,
dh: number,
u0: number,
v0: number,
u1: number,
v1: number,
r = 1,
g = 1,
b = 1,
a = 1,
) {
const x2 = dx + dw;
const y2 = dy + dh;
// deno-fmt-ignore
const v = [
dx,
dy,
u0,
v0,
x2,
dy,
u1,
v0,
dx,
y2,
u0,
v1,
dx, dy, u0, v0, r, g, b, a,
x2, dy, u1, v0, r, g, b, a,
dx, y2, u0, v1, r, g, b, a,
x2,
dy,
u1,
v0,
x2,
y2,
u1,
v1,
dx,
y2,
u0,
v1,
];
x2, dy, u1, v0, r, g, b, a,
x2, y2, u1, v1, r, g, b, a,
dx, y2, u0, v1, r, g, b, a,
];
for (let i = 0; i < v.length; i += 1) {
vertex_data[vert_index++] = v[i];
}
}
function init_font() {
const c = document.createElement("canvas");
c.width = 512;
c.height = 512;
const ctx = c.getContext("2d")!;
ctx.fillStyle = "white";
ctx.font = glyph_size + "px m6x11";
let x = 0;
let y = glyph_size;
for (let i = 32; i < 127; i++) {
const ch = String.fromCharCode(i);
ctx.fillText(ch, x, y);
glyphs[ch] = {
u0: x / 512,
v0: (y - glyph_size) / 512,
u1: (x + glyph_size) / 512,
v1: y / 512,
};
x += glyph_size;
if (x > 512 - glyph_size) {
x = 0;
y += glyph_size;
}
}
font_texture = gl.createTexture()!;
gl.bindTexture(gl.TEXTURE_2D, font_texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
}
export function resize_canvas() {
const width = self.innerWidth;
const height = self.innerHeight;
@@ -415,3 +340,115 @@ export function end_mode_2d() {
flush_batch();
camera = default_camera;
}
// font stuff
let font_texture: Texture;
let font_fnt: FntFont;
export function init_font() {
font_texture = AssetManager.instance.get<Texture>("bworld:m6x11");
font_fnt = parse_fnt(AssetManager.instance.get("bworld:m6x11_fnt"));
}
interface FntChar {
id: number;
x: number;
y: number;
width: number;
height: number;
xoffset: number;
yoffset: number;
xadvance: number;
page: number;
}
interface FntFont {
line_height: number;
chars: Record<number, FntChar>;
pages: string[];
}
function parse_fnt(text: string): FntFont {
const lines = text.split(/\r?\n/);
const font: FntFont = {
line_height: 0,
chars: {},
pages: [],
};
for (const line of lines) {
const parts = line.split(" ");
const type = parts[0];
const data: Record<string, string> = {};
for (const part of parts.slice(1)) {
const [k, v] = part.split("=");
if (!k) {
continue;
}
data[k] = v?.replace(/"/g, "");
}
if (type === "common") {
font.line_height = Number(data.lineHeight);
}
if (type === "page") {
font.pages.push(data.file);
}
if (type === "char") {
const char: FntChar = {
id: Number(data.id),
x: Number(data.x),
y: Number(data.y),
width: Number(data.width),
height: Number(data.height),
xoffset: Number(data.xoffset),
yoffset: Number(data.yoffset),
xadvance: Number(data.xadvance),
page: Number(data.page),
};
font.chars[char.id] = char;
}
}
return font;
}
export function draw_text(str: string, x: number, y: number, scale: number = 1, color = [1, 1, 1, 1]) {
if (!font_texture) {
return;
}
if (current_texture !== font_texture.tex) {
flush_batch();
current_texture = font_texture.tex;
}
for (const ch of str) {
const glyph = font_fnt.chars[ch.charCodeAt(0)];
if (!glyph) {
continue;
}
const dx = x + glyph.xoffset * scale;
const dy = y + glyph.yoffset * scale;
const dw = glyph.width * scale;
const dh = glyph.height * scale;
const u0 = glyph.x / font_texture.width;
const v0 = glyph.y / font_texture.height;
const u1 = (glyph.x + glyph.width) / font_texture.width;
const v1 = (glyph.y + glyph.height) / font_texture.height;
push_quad(dx, dy, dw, dh, u0, v0, u1, v1, color[0], color[1], color[2], color[3]);
x += glyph.xadvance * scale;
}
}
+7 -12
View File
@@ -119,21 +119,16 @@ export function draw_item(item: ItemStack, x: number, y: number) {
if (item.max_amount !== 1) {
draw_text(
String(item.amount),
x + SLOT_SIZE + 2 - 4,
y + SLOT_SIZE + 2 - 4,
//2,
//"#3f3f3f",
//"bottom",
//"right",
x + SLOT_SIZE + 2 - 11 * 2,
y + SLOT_SIZE + 2 - 11 * 2,
1,
[63 / 255, 63 / 255, 63 / 255],
);
draw_text(
String(item.amount),
x + SLOT_SIZE - 4,
y + SLOT_SIZE - 4,
//2,
//"white",
//"bottom",
//"right",
x + SLOT_SIZE - 11 * 2,
y + SLOT_SIZE - 11 * 2,
1,
);
}
}