Graphite/frontend/src/state-providers/fonts.ts

95 lines
3.6 KiB
TypeScript

import { reactive } from "vue";
import { Editor } from "@/wasm-communication/editor";
import { TriggerFontLoad } from "@/wasm-communication/messages";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function createFontsState(editor: Editor) {
const state = reactive({});
function createURL(font: string): URL {
const url = new URL("https://fonts.googleapis.com/css2");
url.searchParams.set("display", "swap");
url.searchParams.set("family", font);
url.searchParams.set("text", font);
return url;
}
async function fontNames(): Promise<{ name: string; url: URL | undefined }[]> {
return (await fontList).map((font) => ({ name: font.family, url: createURL(font.family) }));
}
async function getFontStyles(fontFamily: string): Promise<{ name: string; url: URL | undefined }[]> {
const font = (await fontList).find((value) => value.family === fontFamily);
return font?.variants.map((variant) => ({ name: variant, url: undefined })) || [];
}
async function getFontFileUrl(fontFamily: string, fontStyle: string): Promise<string | undefined> {
const font = (await fontList).find((value) => value.family === fontFamily);
const fontFileUrl = font?.files.get(fontStyle);
return fontFileUrl?.replace("http://", "https://");
}
function formatFontStyleName(fontStyle: string): string {
const isItalic = fontStyle.endsWith("italic");
const weight = fontStyle === "regular" || fontStyle === "italic" ? 400 : parseInt(fontStyle, 10);
let weightName = "";
let bestWeight = Infinity;
weightNameMapping.forEach((nameChecking, weightChecking) => {
if (Math.abs(weightChecking - weight) < bestWeight) {
bestWeight = Math.abs(weightChecking - weight);
weightName = nameChecking;
}
});
return `${weightName}${isItalic ? " Italic" : ""} (${weight})`;
}
// Subscribe to process backend events
editor.subscriptions.subscribeJsMessage(TriggerFontLoad, async (triggerFontLoad) => {
const url = await getFontFileUrl(triggerFontLoad.font.fontFamily, triggerFontLoad.font.fontStyle);
if (url) {
const response = await (await fetch(url)).arrayBuffer();
editor.instance.onFontLoad(triggerFontLoad.font.fontFamily, triggerFontLoad.font.fontStyle, url, new Uint8Array(response), triggerFontLoad.isDefault);
} else {
editor.instance.errorDialog("Failed to load font", `The font ${triggerFontLoad.font.fontFamily} with style ${triggerFontLoad.font.fontStyle} does not exist`);
}
});
const fontList: Promise<{ family: string; variants: string[]; files: Map<string, string> }[]> = new Promise((resolve) => {
fetch(fontListAPI)
.then((response) => response.json())
.then((fontListResponse) => {
const fontListData = fontListResponse.items as { family: string; variants: string[]; files: { [name: string]: string } }[];
const result = fontListData.map((font) => {
const { family } = font;
const variants = font.variants.map(formatFontStyleName);
const files = new Map(font.variants.map((x) => [formatFontStyleName(x), font.files[x]]));
return { family, variants, files };
});
resolve(result);
});
});
return { state, fontNames, getFontStyles, getFontFileUrl };
}
export type FontsState = ReturnType<typeof createFontsState>;
const fontListAPI = "https://api.graphite.rs/font-list";
// From https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight#common_weight_name_mapping
const weightNameMapping = new Map([
[100, "Thin"],
[200, "Extra Light"],
[300, "Light"],
[400, "Normal"],
[500, "Medium"],
[600, "Semi Bold"],
[700, "Bold"],
[800, "Extra Bold"],
[900, "Black"],
[950, "Extra Black"],
]);