@thi.ng/webgl-msdf
Version:
Multi-channel SDF font rendering & basic text layout for WebGL
111 lines (110 loc) • 3.04 kB
JavaScript
import { add } from "@thi.ng/transducers/add";
import { map } from "@thi.ng/transducers/map";
import { mapcat } from "@thi.ng/transducers/mapcat";
import { range } from "@thi.ng/transducers/range";
import { transduce } from "@thi.ng/transducers/transduce";
import { AttribPool } from "@thi.ng/vector-pools/attrib-pool";
import { addm2 } from "@thi.ng/vectors/addm";
import { ONE4 } from "@thi.ng/vectors/api";
import { invert2 } from "@thi.ng/vectors/invert";
import { madd2 } from "@thi.ng/vectors/madd";
import { mul2 } from "@thi.ng/vectors/mul";
const text = (glyphs, txt, opts) => {
opts = {
align: alignLeft,
spacing: 1,
leading: 1,
dirX: 1,
dirY: 1,
color: ONE4,
...opts
};
const dir = [opts.dirX, opts.dirY];
const lineHeight = glyphs.lineHeight * opts.leading * opts.dirY;
const len = txt.replace("\n", "").length;
const attribs = new AttribPool({
attribs: {
position: { type: "f32", size: 3, byteOffset: 0 },
uv: { type: "f32", size: 2, byteOffset: 12 },
...opts.useColor ? {
color: {
type: "f32",
default: opts.color,
size: 4,
byteOffset: 20
}
} : null
},
num: len * 4,
mem: {
size: len * 4 * (opts.useColor ? 36 : 20) + 8 + /* FIXME */
40
}
});
const lines = txt.split("\n");
const invSize = invert2([], glyphs.size);
for (let i = 0, yy = 0, id = 0; i < lines.length; i++) {
const line = lines[i];
let xx = opts.align(glyphs, opts, line);
for (let j = 0; j < line.length; j++, id++) {
const { pos, size, offset, step } = glyphs.chars[line[j]];
const [sx, sy] = mul2([], size, dir);
const [x, y] = madd2([], offset, dir, [xx, yy]);
attribs.setAttribValues(
"position",
[
[x, y, 0],
[x + sx, y, 0],
[x + sx, y + sy, 0],
[x, y + sy, 0]
],
id * 4
);
attribs.setAttribValues(
"uv",
[
mul2([], pos, invSize),
mul2([], [pos[0] + size[0], pos[1]], invSize),
addm2([], pos, size, invSize),
mul2([], [pos[0], pos[1] + size[1]], invSize)
],
id * 4
);
xx += step * opts.dirX * opts.spacing;
}
yy += lineHeight;
}
return {
attribPool: attribs,
indices: {
data: new Uint16Array(
mapcat(
(i) => [i, i + 1, i + 2, i, i + 2, i + 3],
range(0, len * 4, 4)
)
)
},
uniforms: {},
num: len * 6,
mode: 4
// TRIANGLES
};
};
const textWidth = (font, opts, txt) => {
const s = opts.spacing !== void 0 ? opts.spacing : 1;
return transduce(
map((x) => font.chars[x].step * s),
add(),
txt
);
};
const alignLeft = () => 0;
const alignRight = (font, opts, line) => -(opts.dirX || 1) * textWidth(font, opts, line);
const alignCenter = (font, opts, line) => -(opts.dirX || 1) * textWidth(font, opts, line) / 2;
export {
alignCenter,
alignLeft,
alignRight,
text,
textWidth
};