返回 presentation-ai
infographic-font-loader.ts
根目录 / src / hooks / presentation / infographic / infographic-font-loader.ts
1 "use client";
2
3 import fontInfos from "@/components/ui/font-picker/font-preview/fontInfo.json";
4 import { type Font } from "@/components/ui/font-picker/types";
5 import {
6 getFourVariants,
7 loadFontFromObject,
8 } from "@/components/ui/font-picker/utils/utils";
9
10 type FontInfo = Omit<Font, "cased">;
11
12 const GENERIC_FONT_FAMILIES = new Set([
13 "arial",
14 "cursive",
15 "emoji",
16 "fantasy",
17 "fangsong",
18 "inherit",
19 "initial",
20 "monospace",
21 "sans-serif",
22 "serif",
23 "system-ui",
24 "ui-monospace",
25 "ui-rounded",
26 "ui-sans-serif",
27 "ui-serif",
28 "unset",
29 ]);
30
31 const GOOGLE_FONTS = (fontInfos as FontInfo[]).map((font) => ({
32 ...font,
33 cased: font.name.toLowerCase(),
34 }));
35
36 const GOOGLE_FONTS_BY_NAME = new Map(
37 GOOGLE_FONTS.map((font) => [font.cased, font]),
38 );
39
40 export function loadInfographicFonts(payload: unknown): boolean {
41 if (typeof document === "undefined") return false;
42
43 const fontFamilies = extractInfographicFontFamilies(payload);
44 let loadedFont = false;
45
46 for (const fontFamily of fontFamilies) {
47 const font = GOOGLE_FONTS_BY_NAME.get(fontFamily.toLowerCase());
48 if (!font) continue;
49
50 loadFontFromObject(font, false, getFourVariants);
51 loadedFont = true;
52 }
53
54 return loadedFont;
55 }
56
57 function extractInfographicFontFamilies(payload: unknown): string[] {
58 const fonts = new Set<string>();
59 collectFonts(payload, fonts);
60
61 return [...fonts];
62 }
63
64 function collectFonts(value: unknown, fonts: Set<string>): void {
65 if (!value) return;
66
67 if (typeof value === "string") {
68 collectSyntaxFonts(value, fonts);
69 return;
70 }
71
72 if (Array.isArray(value)) {
73 for (const item of value) {
74 collectFonts(item, fonts);
75 }
76 return;
77 }
78
79 if (typeof value !== "object") return;
80
81 for (const [key, nestedValue] of Object.entries(value)) {
82 if (isFontFamilyKey(key) && typeof nestedValue === "string") {
83 addFontFamilies(nestedValue, fonts);
84 continue;
85 }
86
87 collectFonts(nestedValue, fonts);
88 }
89 }
90
91 function collectSyntaxFonts(syntax: string, fonts: Set<string>): void {
92 const fontFamilyMatches = syntax.matchAll(
93 /(?:font-family|fontFamily)\s+([^\n\r]+)/gi,
94 );
95
96 for (const match of fontFamilyMatches) {
97 const fontFamily = match[1];
98 if (fontFamily) addFontFamilies(fontFamily, fonts);
99 }
100 }
101
102 function isFontFamilyKey(key: string): boolean {
103 return key === "fontFamily" || key === "font-family";
104 }
105
106 function addFontFamilies(value: string, fonts: Set<string>): void {
107 for (const fontFamily of normalizeFontFamilies(value)) {
108 fonts.add(fontFamily);
109 }
110 }
111
112 function normalizeFontFamilies(value: string): string[] {
113 return value
114 .split(",")
115 .map((fontFamily) => fontFamily.trim().replace(/^["']|["']$/g, ""))
116 .filter((fontFamily) => {
117 if (!fontFamily) return false;
118 return !GENERIC_FONT_FAMILIES.has(fontFamily.toLowerCase());
119 });
120 }
121
121 lines TYPESCRIPT