2025-10-16 12:59:24 +02:00

178 lines
4.0 KiB
JavaScript

import { UUID } from "./uuid.js";
import { fromStyle } from "./style.js";
export const FontStyle = {
NORMAL: 0,
REGULAR: 0,
ITALIC: 1,
OBLIQUE: 1,
fromStyle,
};
export class FontData {
#id;
#url;
#font;
#name;
#styleName;
#extension;
#weight;
#style;
#arrayBuffer;
constructor(init) {
this.#id = new UUID();
this.#font = init.font;
this.#url = `fonts/${this.#font}`;
const [name, styleNameAndExtension] = this.#font.split("-");
const [styleName, extension] = styleNameAndExtension.split(".");
const isItalic = styleName.endsWith("Italic");
this.#styleName = styleName;
this.#extension = extension;
this.#name = name;
this.#weight = styleName.replaceAll("Italic", "");
this.#style = isItalic ? "italic" : "normal";
this.#arrayBuffer = init.arrayBuffer;
}
get id() {
return this.#id;
}
get url() {
return this.#url;
}
get extension() {
return this.#extension;
}
get name() {
return this.#name;
}
get weight() {
return this.#weight;
}
get style() {
return this.#style;
}
get arrayBuffer() {
return this.#arrayBuffer;
}
get styleAsNumber() {
return FontStyle.fromStyle(this.#style) ?? 0;
}
get weightAsNumber() {
if (this.#styleName.startsWith("Thin")) {
return 100;
} else if (this.#styleName.startsWith("ExtraLight")) {
return 200;
} else if (this.#styleName.startsWith("Light")) {
return 300;
} else if (this.#styleName.startsWith("Regular")) {
return 400;
} else if (this.#styleName.startsWith("Medium")) {
return 500;
} else if (this.#styleName.startsWith("SemiBold")) {
return 600;
} else if (this.#styleName.startsWith("Bold")) {
return 700;
} else if (this.#styleName.startsWith("ExtraBold")) {
return 800;
} else if (this.#styleName.startsWith("Black")) {
return 900;
} else {
return 400;
}
}
}
export class FontManager {
#module;
#fonts;
/**
*
* @param {WASMModule} module
*/
constructor(module) {
this.#module = module;
}
get fonts() {
return this.#fonts;
}
async #loadFontList() {
const response = await fetch("fonts/fonts.txt");
const text = await response.text();
const fonts = text.split("\n").filter((l) => !!l);
return fonts;
}
async #loadFonts() {
const fontData = new Map();
const fonts = await this.#loadFontList();
for (const font of fonts) {
const response = await fetch(`fonts/${font}`);
const arrayBuffer = await response.arrayBuffer();
const newFontData = new FontData({
font: font,
arrayBuffer,
});
if (!fontData.has(newFontData.name)) {
fontData.set(newFontData.name, []);
}
const currentFontData = fontData.get(newFontData.name);
currentFontData.push(newFontData);
}
return fontData;
}
#store(fonts) {
for (const [fontName, fontStyles] of fonts) {
for (const font of fontStyles) {
const shapeId = UUID.ZERO;
const fontId = font.id;
const weight = font.weightAsNumber;
const style = font.styleAsNumber;
const size = font.arrayBuffer.byteLength;
const ptr = this.#module.call("alloc_bytes", size);
this.#module.set(ptr, size, new Uint8Array(font.arrayBuffer));
const emoji = false;
const fallback = false;
this.#module.call(
"store_font",
shapeId[0],
shapeId[1],
shapeId[2],
shapeId[3],
fontId[0],
fontId[1],
fontId[2],
fontId[3],
weight,
style,
emoji,
fallback,
);
this.#module.call(
"is_font_uploaded",
fontId[0],
fontId[1],
fontId[2],
fontId[3],
weight,
style,
emoji,
);
}
}
}
async load() {
this.#fonts = await this.#loadFonts();
this.#store(this.#fonts);
}
}