| import { writable } from "svelte/store"; |
|
|
| import { type Editor } from "@graphite/editor"; |
| import { TriggerFontLoad } from "@graphite/messages"; |
|
|
| |
| export function createFontsState(editor: Editor) { |
| |
| const { subscribe } = writable({}); |
|
|
| function createURL(font: string, weight: string): URL { |
| const url = new URL("https://fonts.googleapis.com/css2"); |
| url.searchParams.set("display", "swap"); |
| url.searchParams.set("family", `${font}:wght@${weight}`); |
| url.searchParams.set("text", font); |
|
|
| return url; |
| } |
|
|
| async function fontNames(): Promise<{ name: string; url: URL | undefined }[]> { |
| const pickPreviewWeight = (variants: string[]) => { |
| const weights = variants.map((variant) => Number(variant.match(/.* \((\d+)\)/)?.[1] || "NaN")); |
| const weightGoal = 400; |
| const sorted = weights.map((weight) => [weight, Math.abs(weightGoal - weight - 1)]); |
| sorted.sort(([_, a], [__, b]) => a - b); |
| return sorted[0][0].toString(); |
| }; |
| return (await loadFontList()).map((font) => ({ name: font.family, url: createURL(font.family, pickPreviewWeight(font.variants)) })); |
| } |
|
|
| async function getFontStyles(fontFamily: string): Promise<{ name: string; url: URL | undefined }[]> { |
| const font = (await loadFontList()).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 loadFontList()).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})`; |
| } |
|
|
| let fontList: Promise<{ family: string; variants: string[]; files: Map<string, string> }[]> | undefined; |
|
|
| async function loadFontList(): Promise<{ family: string; variants: string[]; files: Map<string, string> }[]> { |
| if (fontList) return fontList; |
|
|
| fontList = new Promise<{ family: string; variants: string[]; files: Map<string, string> }[]>((resolve) => { |
| fetch(fontListAPI) |
| .then((response) => response.json()) |
| .then((fontListResponse) => { |
| const fontListData = fontListResponse.items as { family: string; variants: string[]; files: Record<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 fontList; |
| } |
|
|
| |
| 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.handle.onFontLoad(triggerFontLoad.font.fontFamily, triggerFontLoad.font.fontStyle, url, new Uint8Array(response)); |
| } else { |
| editor.handle.errorDialog("Failed to load font", `The font ${triggerFontLoad.font.fontFamily} with style ${triggerFontLoad.font.fontStyle} does not exist`); |
| } |
| }); |
|
|
| return { |
| subscribe, |
| fontNames, |
| getFontStyles, |
| getFontFileUrl, |
| }; |
| } |
| export type FontsState = ReturnType<typeof createFontsState>; |
|
|
| const fontListAPI = "https://api.graphite.rs/font-list"; |
|
|
| |
| const weightNameMapping = new Map([ |
| [100, "Thin"], |
| [200, "Extra Light"], |
| [300, "Light"], |
| [400, "Regular"], |
| [500, "Medium"], |
| [600, "Semi Bold"], |
| [700, "Bold"], |
| [800, "Extra Bold"], |
| [900, "Black"], |
| [950, "Extra Black"], |
| ]); |
|
|