@zseven-w/pen-renderer 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
- import type { TypefaceFontProvider, CanvasKit } from 'canvaskit-wasm'
1
+ import type { TypefaceFontProvider, CanvasKit } from 'canvaskit-wasm';
2
2
 
3
3
  export interface FontManagerOptions {
4
4
  /** Base URL for bundled font files. Default: '/fonts/' */
5
- fontBasePath?: string
5
+ fontBasePath?: string;
6
6
  /** Custom Google Fonts CSS endpoint. Default: 'https://fonts.googleapis.com/css2' */
7
- googleFontsCssUrl?: string
7
+ googleFontsCssUrl?: string;
8
8
  }
9
9
 
10
10
  /**
@@ -22,52 +22,20 @@ const BUNDLED_FONTS: Record<string, string[]> = {
22
22
  'inter-ext-600.woff2',
23
23
  'inter-ext-700.woff2',
24
24
  ],
25
- poppins: [
26
- 'poppins-400.woff2',
27
- 'poppins-500.woff2',
28
- 'poppins-600.woff2',
29
- 'poppins-700.woff2',
30
- ],
31
- roboto: [
32
- 'roboto-400.woff2',
33
- 'roboto-500.woff2',
34
- 'roboto-700.woff2',
35
- ],
25
+ poppins: ['poppins-400.woff2', 'poppins-500.woff2', 'poppins-600.woff2', 'poppins-700.woff2'],
26
+ roboto: ['roboto-400.woff2', 'roboto-500.woff2', 'roboto-700.woff2'],
36
27
  montserrat: [
37
28
  'montserrat-400.woff2',
38
29
  'montserrat-500.woff2',
39
30
  'montserrat-600.woff2',
40
31
  'montserrat-700.woff2',
41
32
  ],
42
- 'open sans': [
43
- 'open-sans-400.woff2',
44
- 'open-sans-600.woff2',
45
- 'open-sans-700.woff2',
46
- ],
47
- lato: [
48
- 'lato-400.woff2',
49
- 'lato-700.woff2',
50
- ],
51
- raleway: [
52
- 'raleway-400.woff2',
53
- 'raleway-500.woff2',
54
- 'raleway-600.woff2',
55
- 'raleway-700.woff2',
56
- ],
57
- 'dm sans': [
58
- 'dm-sans-400.woff2',
59
- 'dm-sans-500.woff2',
60
- 'dm-sans-700.woff2',
61
- ],
62
- 'playfair display': [
63
- 'playfair-display-400.woff2',
64
- 'playfair-display-700.woff2',
65
- ],
66
- nunito: [
67
- 'nunito-400.woff2',
68
- 'nunito-600.woff2',
69
- 'nunito-700.woff2',
70
- ],
33
+ 'open sans': ['open-sans-400.woff2', 'open-sans-600.woff2', 'open-sans-700.woff2'],
34
+ lato: ['lato-400.woff2', 'lato-700.woff2'],
35
+ raleway: ['raleway-400.woff2', 'raleway-500.woff2', 'raleway-600.woff2', 'raleway-700.woff2'],
36
+ 'dm sans': ['dm-sans-400.woff2', 'dm-sans-500.woff2', 'dm-sans-700.woff2'],
37
+ 'playfair display': ['playfair-display-400.woff2', 'playfair-display-700.woff2'],
38
+ nunito: ['nunito-400.woff2', 'nunito-600.woff2', 'nunito-700.woff2'],
71
39
  'source sans 3': [
72
40
  'source-sans-3-400.woff2',
73
41
  'source-sans-3-600.woff2',
@@ -84,7 +52,7 @@ const BUNDLED_FONTS: Record<string, string[]> = {
84
52
  'noto-sans-sc-latin-400.woff2',
85
53
  'noto-sans-sc-latin-700.woff2',
86
54
  ],
87
- }
55
+ };
88
56
 
89
57
  /** List of all bundled font family names (for UI font picker) */
90
58
  export const BUNDLED_FONT_FAMILIES = [
@@ -100,7 +68,7 @@ export const BUNDLED_FONT_FAMILIES = [
100
68
  'Playfair Display',
101
69
  'Nunito',
102
70
  'Source Sans 3',
103
- ]
71
+ ];
104
72
 
105
73
  /**
106
74
  * Manages font loading for CanvasKit's Paragraph API (vector text rendering).
@@ -109,43 +77,57 @@ export const BUNDLED_FONT_FAMILIES = [
109
77
  * Google Fonts CDN. Once loaded, text is rendered as true vector glyphs.
110
78
  */
111
79
  export class SkiaFontManager {
112
- private provider: TypefaceFontProvider
113
- private fontBasePath: string
114
- private googleFontsCssUrl: string
80
+ private provider: TypefaceFontProvider;
81
+ private fontBasePath: string;
82
+ private googleFontsCssUrl: string;
115
83
  /** Registered family names (lowercase) -> true once loaded */
116
- private loadedFamilies = new Set<string>()
84
+ private loadedFamilies = new Set<string>();
117
85
  /** Font families that failed to load */
118
- private failedFamilies = new Set<string>()
86
+ private failedFamilies = new Set<string>();
119
87
  /** System fonts that render via bitmap */
120
- private systemFontFamilies = new Set<string>()
88
+ private systemFontFamilies = new Set<string>();
121
89
  /** In-flight font fetch promises to avoid duplicate requests */
122
- private pendingFetches = new Map<string, Promise<boolean>>()
90
+ private pendingFetches = new Map<string, Promise<boolean>>();
123
91
 
124
92
  constructor(ck: CanvasKit, options?: FontManagerOptions) {
125
- this.provider = ck.TypefaceFontProvider.Make()
126
- this.fontBasePath = options?.fontBasePath ?? '/fonts/'
93
+ this.provider = ck.TypefaceFontProvider.Make();
94
+ this.fontBasePath = options?.fontBasePath ?? '/fonts/';
127
95
  // Ensure trailing slash
128
- if (!this.fontBasePath.endsWith('/')) this.fontBasePath += '/'
129
- this.googleFontsCssUrl = options?.googleFontsCssUrl ?? 'https://fonts.googleapis.com/css2'
96
+ if (!this.fontBasePath.endsWith('/')) this.fontBasePath += '/';
97
+ this.googleFontsCssUrl = options?.googleFontsCssUrl ?? 'https://fonts.googleapis.com/css2';
130
98
  }
131
99
 
132
100
  getProvider(): TypefaceFontProvider {
133
- return this.provider
101
+ return this.provider;
102
+ }
103
+
104
+ /** Number of in-flight font load promises. */
105
+ pendingCount(): number {
106
+ return this.pendingFetches.size;
107
+ }
108
+
109
+ /**
110
+ * Wait for every currently pending font fetch to settle.
111
+ * Used by SkiaEngine.waitForSettled to coordinate readback timing.
112
+ */
113
+ async flushPending(): Promise<void> {
114
+ const snapshot = Array.from(this.pendingFetches.values());
115
+ await Promise.all(snapshot.map((p) => p.catch(() => false)));
134
116
  }
135
117
 
136
118
  /** Check if a font family is ready for use */
137
119
  isFontReady(family: string): boolean {
138
- return this.loadedFamilies.has(family.toLowerCase())
120
+ return this.loadedFamilies.has(family.toLowerCase());
139
121
  }
140
122
 
141
123
  /** Check if a font family is bundled (available offline) */
142
124
  isBundled(family: string): boolean {
143
- return family.toLowerCase() in BUNDLED_FONTS
125
+ return family.toLowerCase() in BUNDLED_FONTS;
144
126
  }
145
127
 
146
128
  /** Check if a font is a system font that should use bitmap rendering */
147
129
  isSystemFont(family: string): boolean {
148
- return this.systemFontFamilies.has(family.toLowerCase()) || isSystemFont(family)
130
+ return this.systemFontFamilies.has(family.toLowerCase()) || isSystemFont(family);
149
131
  }
150
132
 
151
133
  /**
@@ -153,43 +135,43 @@ export class SkiaFontManager {
153
135
  * Only includes fonts actually registered in the TypefaceFontProvider.
154
136
  */
155
137
  getFallbackChain(primaryFamily: string): string[] {
156
- const chain: string[] = []
157
- const lower = primaryFamily.toLowerCase()
138
+ const chain: string[] = [];
139
+ const lower = primaryFamily.toLowerCase();
158
140
  if (this.loadedFamilies.has(lower)) {
159
- chain.push(primaryFamily)
141
+ chain.push(primaryFamily);
160
142
  }
161
143
  if (this.loadedFamilies.has(lower + ' ext')) {
162
- chain.push(primaryFamily + ' Ext')
144
+ chain.push(primaryFamily + ' Ext');
163
145
  }
164
146
  if (lower !== 'noto sans sc' && this.loadedFamilies.has('noto sans sc')) {
165
- chain.push('Noto Sans SC')
147
+ chain.push('Noto Sans SC');
166
148
  }
167
149
  if (lower !== 'inter') {
168
- if (this.loadedFamilies.has('inter')) chain.push('Inter')
169
- if (this.loadedFamilies.has('inter ext')) chain.push('Inter Ext')
150
+ if (this.loadedFamilies.has('inter')) chain.push('Inter');
151
+ if (this.loadedFamilies.has('inter ext')) chain.push('Inter Ext');
170
152
  }
171
- if (chain.length === 0) chain.push('Inter')
172
- return chain
153
+ if (chain.length === 0) chain.push('Inter');
154
+ return chain;
173
155
  }
174
156
 
175
157
  /**
176
158
  * Check if there's at least one loaded fallback font for the given primary family.
177
159
  */
178
160
  hasAnyFallback(primaryFamily: string): boolean {
179
- const key = primaryFamily.toLowerCase()
180
- if (key === 'inter' || key === 'noto sans sc') return false
181
- return this.loadedFamilies.has('inter') || this.loadedFamilies.has('noto sans sc')
161
+ const key = primaryFamily.toLowerCase();
162
+ if (key === 'inter' || key === 'noto sans sc') return false;
163
+ return this.loadedFamilies.has('inter') || this.loadedFamilies.has('noto sans sc');
182
164
  }
183
165
 
184
166
  /** Register a font from raw ArrayBuffer data */
185
167
  registerFont(data: ArrayBuffer, familyName: string): boolean {
186
168
  try {
187
- this.provider.registerFont(data, familyName)
188
- this.loadedFamilies.add(familyName.toLowerCase())
189
- return true
169
+ this.provider.registerFont(data, familyName);
170
+ this.loadedFamilies.add(familyName.toLowerCase());
171
+ return true;
190
172
  } catch (e) {
191
- console.warn(`[FontManager] Failed to register "${familyName}":`, e)
192
- return false
173
+ console.warn(`[FontManager] Failed to register "${familyName}":`, e);
174
+ return false;
193
175
  }
194
176
  }
195
177
 
@@ -197,80 +179,82 @@ export class SkiaFontManager {
197
179
  * Ensure a font family is loaded. Tries bundled fonts first, then Google Fonts.
198
180
  */
199
181
  async ensureFont(family: string, weights: number[] = [400, 500, 600, 700]): Promise<boolean> {
200
- const key = family.toLowerCase()
201
- if (this.loadedFamilies.has(key)) return true
202
- if (this.failedFamilies.has(key)) return false
203
- if (this.systemFontFamilies.has(key)) return false
204
-
205
- const existing = this.pendingFetches.get(key)
206
- if (existing) return existing
207
-
208
- const promise = this._loadFont(family, weights)
209
- this.pendingFetches.set(key, promise)
210
- const result = await promise
211
- this.pendingFetches.delete(key)
182
+ const key = family.toLowerCase();
183
+ if (this.loadedFamilies.has(key)) return true;
184
+ if (this.failedFamilies.has(key)) return false;
185
+ if (this.systemFontFamilies.has(key)) return false;
186
+
187
+ const existing = this.pendingFetches.get(key);
188
+ if (existing) return existing;
189
+
190
+ const promise = this._loadFont(family, weights);
191
+ this.pendingFetches.set(key, promise);
192
+ const result = await promise;
193
+ this.pendingFetches.delete(key);
212
194
  if (!result) {
213
195
  if (isSystemFont(family)) {
214
- this.systemFontFamilies.add(key)
196
+ this.systemFontFamilies.add(key);
215
197
  } else {
216
- this.failedFamilies.add(key)
198
+ this.failedFamilies.add(key);
217
199
  }
218
200
  }
219
- return result
201
+ return result;
220
202
  }
221
203
 
222
204
  /**
223
205
  * Load multiple font families concurrently.
224
206
  */
225
207
  async ensureFonts(families: string[]): Promise<Set<string>> {
226
- const unique = [...new Set(families.map(f => f.trim()).filter(Boolean))]
227
- const results = await Promise.allSettled(
228
- unique.map(f => this.ensureFont(f))
229
- )
230
- const loaded = new Set<string>()
208
+ const unique = [...new Set(families.map((f) => f.trim()).filter(Boolean))];
209
+ const results = await Promise.allSettled(unique.map((f) => this.ensureFont(f)));
210
+ const loaded = new Set<string>();
231
211
  results.forEach((r, i) => {
232
- if (r.status === 'fulfilled' && r.value) loaded.add(unique[i])
233
- })
234
- return loaded
212
+ if (r.status === 'fulfilled' && r.value) loaded.add(unique[i]);
213
+ });
214
+ return loaded;
235
215
  }
236
216
 
237
217
  private async _loadFont(family: string, weights: number[]): Promise<boolean> {
238
218
  // 1. Try bundled fonts first (no network dependency)
239
- const bundled = BUNDLED_FONTS[family.toLowerCase()]
219
+ const bundled = BUNDLED_FONTS[family.toLowerCase()];
240
220
  if (bundled) {
241
- const urls = bundled.map(f => `${this.fontBasePath}${f}`)
242
- const ok = await this._fetchLocalFonts(family, urls, bundled)
243
- if (ok) return true
221
+ const urls = bundled.map((f) => `${this.fontBasePath}${f}`);
222
+ const ok = await this._fetchLocalFonts(family, urls, bundled);
223
+ if (ok) return true;
244
224
  }
245
225
 
246
226
  // 2. Skip Google Fonts for system/proprietary fonts
247
227
  if (isSystemFont(family)) {
248
- return false
228
+ return false;
249
229
  }
250
230
 
251
231
  // 3. Fall back to Google Fonts CDN
252
- return this._fetchGoogleFont(family, weights)
232
+ return this._fetchGoogleFont(family, weights);
253
233
  }
254
234
 
255
- private async _fetchLocalFonts(family: string, urls: string[], relPaths: string[]): Promise<boolean> {
235
+ private async _fetchLocalFonts(
236
+ family: string,
237
+ urls: string[],
238
+ relPaths: string[],
239
+ ): Promise<boolean> {
256
240
  try {
257
241
  const buffers = await Promise.all(
258
242
  urls.map(async (url) => {
259
- const resp = await fetch(url)
260
- if (!resp.ok) return null
261
- return resp.arrayBuffer()
262
- })
263
- )
264
- let registered = 0
243
+ const resp = await fetch(url);
244
+ if (!resp.ok) return null;
245
+ return resp.arrayBuffer();
246
+ }),
247
+ );
248
+ let registered = 0;
265
249
  for (let i = 0; i < buffers.length; i++) {
266
- const buf = buffers[i]
267
- if (!buf) continue
268
- const regName = relPaths[i].includes('-ext-') ? family + ' Ext' : family
269
- if (this.registerFont(buf, regName)) registered++
250
+ const buf = buffers[i];
251
+ if (!buf) continue;
252
+ const regName = relPaths[i].includes('-ext-') ? family + ' Ext' : family;
253
+ if (this.registerFont(buf, regName)) registered++;
270
254
  }
271
- return registered > 0
255
+ return registered > 0;
272
256
  } catch {
273
- return false
257
+ return false;
274
258
  }
275
259
  }
276
260
 
@@ -278,9 +262,9 @@ export class SkiaFontManager {
278
262
  * Fetch a font from Google Fonts CDN with China mirror fallback.
279
263
  */
280
264
  private async _fetchGoogleFont(family: string, weights: number[]): Promise<boolean> {
281
- const weightStr = weights.join(';')
282
- const encodedFamily = encodeURIComponent(family)
283
- const query = `family=${encodedFamily}:wght@${weightStr}&display=swap`
265
+ const weightStr = weights.join(';');
266
+ const encodedFamily = encodeURIComponent(family);
267
+ const query = `family=${encodedFamily}:wght@${weightStr}&display=swap`;
284
268
 
285
269
  const cdnConfigs = [
286
270
  {
@@ -291,49 +275,51 @@ export class SkiaFontManager {
291
275
  cssBase: 'https://fonts.font.im/css2',
292
276
  fontUrlPattern: /url\((https?:\/\/[^)]+\.woff2)\)/g,
293
277
  },
294
- ]
278
+ ];
295
279
 
296
280
  for (const cdn of cdnConfigs) {
297
281
  try {
298
- const cssUrl = `${cdn.cssBase}?${query}`
299
- const cssResp = await fetchWithTimeout(cssUrl, 4000)
300
- if (!cssResp.ok) continue
301
- const css = await cssResp.text()
282
+ const cssUrl = `${cdn.cssBase}?${query}`;
283
+ const cssResp = await fetchWithTimeout(cssUrl, 4000);
284
+ if (!cssResp.ok) continue;
285
+ const css = await cssResp.text();
302
286
 
303
- const urls: string[] = []
304
- let match: RegExpExecArray | null
287
+ const urls: string[] = [];
288
+ let match: RegExpExecArray | null;
305
289
  while ((match = cdn.fontUrlPattern.exec(css)) !== null) {
306
- urls.push(match[1])
290
+ urls.push(match[1]);
307
291
  }
308
- if (urls.length === 0) continue
292
+ if (urls.length === 0) continue;
309
293
 
310
294
  const fontBuffers = await Promise.all(
311
295
  urls.map(async (url) => {
312
296
  try {
313
- const resp = await fetchWithTimeout(url, 8000)
314
- return resp.ok ? resp.arrayBuffer() : null
315
- } catch { return null }
316
- })
317
- )
318
-
319
- let registered = 0
297
+ const resp = await fetchWithTimeout(url, 8000);
298
+ return resp.ok ? resp.arrayBuffer() : null;
299
+ } catch {
300
+ return null;
301
+ }
302
+ }),
303
+ );
304
+
305
+ let registered = 0;
320
306
  for (const buf of fontBuffers) {
321
- if (buf && this.registerFont(buf, family)) registered++
307
+ if (buf && this.registerFont(buf, family)) registered++;
322
308
  }
323
- if (registered > 0) return true
309
+ if (registered > 0) return true;
324
310
  } catch {
325
311
  // CDN failed, try next
326
312
  }
327
313
  }
328
- return false
314
+ return false;
329
315
  }
330
316
 
331
317
  dispose() {
332
- this.provider.delete()
333
- this.loadedFamilies.clear()
334
- this.failedFamilies.clear()
335
- this.systemFontFamilies.clear()
336
- this.pendingFetches.clear()
318
+ this.provider.delete();
319
+ this.loadedFamilies.clear();
320
+ this.failedFamilies.clear();
321
+ this.systemFontFamilies.clear();
322
+ this.pendingFetches.clear();
337
323
  }
338
324
  }
339
325
 
@@ -341,61 +327,106 @@ export class SkiaFontManager {
341
327
  // System font detection (browser-only)
342
328
  // ---------------------------------------------------------------------------
343
329
 
344
- const localFontCache = new Map<string, boolean>()
330
+ const localFontCache = new Map<string, boolean>();
345
331
 
346
332
  function isFontLocallyAvailable(family: string): boolean {
347
- const key = family.toLowerCase()
348
- const cached = localFontCache.get(key)
349
- if (cached !== undefined) return cached
350
-
351
- if (typeof document === 'undefined') return false
352
- const canvas = document.createElement('canvas')
353
- const ctx = canvas.getContext('2d')
354
- if (!ctx) return false
355
-
356
- const testStr = 'mmmmmmmmmmlli1|'
357
- ctx.font = '72px monospace'
358
- const monoWidth = ctx.measureText(testStr).width
359
- ctx.font = '72px serif'
360
- const serifWidth = ctx.measureText(testStr).width
361
- ctx.font = `72px "${family}", monospace`
362
- const testMonoWidth = ctx.measureText(testStr).width
363
- ctx.font = `72px "${family}", serif`
364
- const testSerifWidth = ctx.measureText(testStr).width
365
-
366
- const available = testMonoWidth !== monoWidth && testSerifWidth !== serifWidth
367
- localFontCache.set(key, available)
368
- return available
333
+ const key = family.toLowerCase();
334
+ const cached = localFontCache.get(key);
335
+ if (cached !== undefined) return cached;
336
+
337
+ if (typeof document === 'undefined') return false;
338
+ const canvas = document.createElement('canvas');
339
+ const ctx = canvas.getContext('2d');
340
+ if (!ctx) return false;
341
+
342
+ const testStr = 'mmmmmmmmmmlli1|';
343
+ ctx.font = '72px monospace';
344
+ const monoWidth = ctx.measureText(testStr).width;
345
+ ctx.font = '72px serif';
346
+ const serifWidth = ctx.measureText(testStr).width;
347
+ ctx.font = `72px "${family}", monospace`;
348
+ const testMonoWidth = ctx.measureText(testStr).width;
349
+ ctx.font = `72px "${family}", serif`;
350
+ const testSerifWidth = ctx.measureText(testStr).width;
351
+
352
+ const available = testMonoWidth !== monoWidth && testSerifWidth !== serifWidth;
353
+ localFontCache.set(key, available);
354
+ return available;
369
355
  }
370
356
 
371
357
  const NON_GOOGLE_FONT_PATTERNS = [
372
- /^microsoft/i, /^ms /i, /^segoe/i, /^simhei/i, /^simsun/i,
373
- /^kaiti/i, /^fangsong/i, /^youyuan/i, /^lishu/i, /^dengxian/i,
374
- /^sf /i, /^sf-/i, /^apple/i, /^pingfang/i, /^hiragino/i,
375
- /^helvetica/i, /^menlo/i, /^monaco/i, /^lucida grande/i,
376
- /^avenir/i, /^\.apple/i,
377
- /^d-din/i, /^din[ -]/i, /^din$/i, /^proxima/i, /^gotham/i,
378
- /^futura/i, /^akzidenz/i, /^univers/i, /^frutiger/i,
379
- /^youshebiaotihei/i, /^youshebiaoti/i,
380
- /^fz/i, /^alibaba/i, /^huawen/i, /^stk/i, /^st[hf]/i,
381
- /^source han /i, /^noto sans cjk/i, /^noto serif cjk/i,
382
- /^yu gothic/i, /^yu mincho/i, /^meiryo/i, /^ms gothic/i, /^ms mincho/i,
383
- /^system-ui/i, /^-apple-system/i, /^blinkmacsystemfont/i,
384
- /^arial/i, /^times new roman/i, /^courier new/i, /^georgia/i,
385
- /^verdana/i, /^tahoma/i, /^trebuchet/i, /^impact/i,
386
- /^comic sans/i, /^consolas/i, /^calibri/i, /^cambria/i,
387
- ]
358
+ /^microsoft/i,
359
+ /^ms /i,
360
+ /^segoe/i,
361
+ /^simhei/i,
362
+ /^simsun/i,
363
+ /^kaiti/i,
364
+ /^fangsong/i,
365
+ /^youyuan/i,
366
+ /^lishu/i,
367
+ /^dengxian/i,
368
+ /^sf /i,
369
+ /^sf-/i,
370
+ /^apple/i,
371
+ /^pingfang/i,
372
+ /^hiragino/i,
373
+ /^helvetica/i,
374
+ /^menlo/i,
375
+ /^monaco/i,
376
+ /^lucida grande/i,
377
+ /^avenir/i,
378
+ /^\.apple/i,
379
+ /^d-din/i,
380
+ /^din[ -]/i,
381
+ /^din$/i,
382
+ /^proxima/i,
383
+ /^gotham/i,
384
+ /^futura/i,
385
+ /^akzidenz/i,
386
+ /^univers/i,
387
+ /^frutiger/i,
388
+ /^youshebiaotihei/i,
389
+ /^youshebiaoti/i,
390
+ /^fz/i,
391
+ /^alibaba/i,
392
+ /^huawen/i,
393
+ /^stk/i,
394
+ /^st[hf]/i,
395
+ /^source han /i,
396
+ /^noto sans cjk/i,
397
+ /^noto serif cjk/i,
398
+ /^yu gothic/i,
399
+ /^yu mincho/i,
400
+ /^meiryo/i,
401
+ /^ms gothic/i,
402
+ /^ms mincho/i,
403
+ /^system-ui/i,
404
+ /^-apple-system/i,
405
+ /^blinkmacsystemfont/i,
406
+ /^arial/i,
407
+ /^times new roman/i,
408
+ /^courier new/i,
409
+ /^georgia/i,
410
+ /^verdana/i,
411
+ /^tahoma/i,
412
+ /^trebuchet/i,
413
+ /^impact/i,
414
+ /^comic sans/i,
415
+ /^consolas/i,
416
+ /^calibri/i,
417
+ /^cambria/i,
418
+ ];
388
419
 
389
420
  function isKnownNonGoogleFont(family: string): boolean {
390
- return NON_GOOGLE_FONT_PATTERNS.some(p => p.test(family.trim()))
421
+ return NON_GOOGLE_FONT_PATTERNS.some((p) => p.test(family.trim()));
391
422
  }
392
423
 
393
424
  function isSystemFont(family: string): boolean {
394
- return isFontLocallyAvailable(family) || isKnownNonGoogleFont(family)
425
+ return isFontLocallyAvailable(family) || isKnownNonGoogleFont(family);
395
426
  }
396
427
 
397
428
  function fetchWithTimeout(url: string, ms: number): Promise<Response> {
398
- const controller = new AbortController()
399
- const timer = setTimeout(() => controller.abort(), ms)
400
- return fetch(url, { signal: controller.signal }).finally(() => clearTimeout(timer))
429
+ const controller = new AbortController();
430
+ const timer = setTimeout(() => controller.abort(), ms);
431
+ return fetch(url, { signal: controller.signal }).finally(() => clearTimeout(timer));
401
432
  }