@pixerate/editor 0.175.0 → 0.177.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.
- package/dist/{chunk-FKUKJ3XA.cjs → chunk-4XSBOGMK.cjs} +5 -4
- package/dist/{chunk-CJE5MOIS.js → chunk-N4ZXW3WB.js} +2 -1
- package/dist/{chunk-OVYQAYGH.cjs → chunk-UHMSMUHK.cjs} +75 -66
- package/dist/{chunk-BDIM6NVW.js → chunk-Y46OMERN.js} +10 -1
- package/dist/effects/fake.d.cts +1 -1
- package/dist/effects/fake.d.ts +1 -1
- package/dist/effects/index.cjs +38 -24
- package/dist/effects/index.js +20 -6
- package/dist/effects/types.d.cts +6 -0
- package/dist/effects/types.d.ts +6 -0
- package/dist/effects/ui.d.cts +0 -6
- package/dist/effects/ui.d.ts +0 -6
- package/dist/image-editor/controller.d.cts +28 -5
- package/dist/image-editor/controller.d.ts +28 -5
- package/dist/image-editor/effect-layers.d.cts +40 -0
- package/dist/image-editor/effect-layers.d.ts +40 -0
- package/dist/image-editor/index.cjs +931 -812
- package/dist/image-editor/index.d.cts +1 -0
- package/dist/image-editor/index.d.ts +1 -0
- package/dist/image-editor/index.js +152 -33
- package/dist/image-editor/layers/model.d.cts +15 -1
- package/dist/image-editor/layers/model.d.ts +15 -1
- package/dist/image-editor/layers/panel.d.cts +6 -2
- package/dist/image-editor/layers/panel.d.ts +6 -2
- package/dist/image-editor/provider-effect-tools.d.cts +2 -1
- package/dist/image-editor/provider-effect-tools.d.ts +2 -1
- package/dist/paint-editor/index.cjs +6 -6
- package/dist/paint-editor/index.js +1 -1
- package/dist/typography/index.cjs +185 -40
- package/dist/typography/index.d.cts +1 -0
- package/dist/typography/index.d.ts +1 -0
- package/dist/typography/index.js +149 -4
- package/dist/typography/registry.d.cts +4 -0
- package/dist/typography/registry.d.ts +4 -0
- package/dist/typography/stylesheet.d.cts +86 -0
- package/dist/typography/stylesheet.d.ts +86 -0
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +2 -1
- package/skills/pixerate-typography/SKILL.md +7 -3
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
var
|
|
18
|
+
var _chunk4XSBOGMKcjs = require('../chunk-4XSBOGMK.cjs');
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
var _chunkTFF2GTGNcjs = require('../chunk-TFF2GTGN.cjs');
|
|
@@ -312,6 +312,143 @@ function auditTextReadability(text, font = {}) {
|
|
|
312
312
|
};
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
// src/typography/stylesheet.ts
|
|
316
|
+
function linkStylesheet(url) {
|
|
317
|
+
const doc = globalThis.document;
|
|
318
|
+
if (!_optionalChain([doc, 'optionalAccess', _4 => _4.head])) return Promise.reject(new Error("No document to add a stylesheet to."));
|
|
319
|
+
return new Promise((resolve, reject) => {
|
|
320
|
+
const link = doc.createElement("link");
|
|
321
|
+
link.rel = "stylesheet";
|
|
322
|
+
link.href = url;
|
|
323
|
+
link.onload = () => resolve();
|
|
324
|
+
link.onerror = () => {
|
|
325
|
+
link.remove();
|
|
326
|
+
reject(new Error(`Stylesheet failed to load: ${url}`));
|
|
327
|
+
};
|
|
328
|
+
doc.head.appendChild(link);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
var SAMPLE_TEXT = "AaBbZz09";
|
|
332
|
+
var quoteFamily = (family) => `"${family.replace(/["\\]/g, "\\$&")}"`;
|
|
333
|
+
function createStylesheetFontProvider(options) {
|
|
334
|
+
let catalog = null;
|
|
335
|
+
const fonts = () => {
|
|
336
|
+
catalog ??= Promise.resolve().then(options.listFonts).catch((error) => {
|
|
337
|
+
catalog = null;
|
|
338
|
+
throw error;
|
|
339
|
+
});
|
|
340
|
+
return catalog;
|
|
341
|
+
};
|
|
342
|
+
const addStylesheet = _nullishCoalesce(options.addStylesheet, () => ( linkStylesheet));
|
|
343
|
+
const fontSet = () => _nullishCoalesce(options.fontFaceSet, () => ( _optionalChain([globalThis, 'access', _5 => _5.document, 'optionalAccess', _6 => _6.fonts])));
|
|
344
|
+
const sheets = /* @__PURE__ */ new Map();
|
|
345
|
+
const faces = /* @__PURE__ */ new Map();
|
|
346
|
+
const done = /* @__PURE__ */ new Set();
|
|
347
|
+
const key = (family, weight, style) => `${family.toLowerCase()}|${_chunk4XSBOGMKcjs.normalizeFontWeight.call(void 0, weight)}|${_nullishCoalesce(style, () => ( "normal"))}`;
|
|
348
|
+
const sheet = (url) => {
|
|
349
|
+
let pending = sheets.get(url);
|
|
350
|
+
if (!pending) {
|
|
351
|
+
pending = addStylesheet(url).catch((error) => {
|
|
352
|
+
sheets.delete(url);
|
|
353
|
+
throw error;
|
|
354
|
+
});
|
|
355
|
+
sheets.set(url, pending);
|
|
356
|
+
}
|
|
357
|
+
return pending;
|
|
358
|
+
};
|
|
359
|
+
return {
|
|
360
|
+
id: _nullishCoalesce(options.id, () => ( "stylesheet")),
|
|
361
|
+
listFonts: async () => (await fonts()).map((f) => ({ ...f })),
|
|
362
|
+
hasFont: async (family) => !!_chunk4XSBOGMKcjs.findFont.call(void 0, await fonts(), family),
|
|
363
|
+
loaded: (family, weight, style) => done.has(key(family, weight, style)),
|
|
364
|
+
async loadFont(family, weight, style) {
|
|
365
|
+
const k = key(family, weight, style);
|
|
366
|
+
const cached = faces.get(k);
|
|
367
|
+
if (cached) return cached;
|
|
368
|
+
const promise = (async () => {
|
|
369
|
+
const listed = _chunk4XSBOGMKcjs.findFont.call(void 0, await fonts(), family);
|
|
370
|
+
const set = fontSet();
|
|
371
|
+
if (!listed || !set) return false;
|
|
372
|
+
const wanted = _chunk4XSBOGMKcjs.normalizeFontWeight.call(void 0, weight);
|
|
373
|
+
const w = listed.isVariable || !listed.weights.length ? wanted : _chunk4XSBOGMKcjs.nearestFontWeight.call(void 0, wanted, listed.weights);
|
|
374
|
+
const st = !listed.styles.length || listed.styles.includes(_nullishCoalesce(style, () => ( "normal"))) ? _nullishCoalesce(style, () => ( "normal")) : "normal";
|
|
375
|
+
await sheet(options.stylesheetUrl(listed.family, w, st));
|
|
376
|
+
const matched = await set.load(`${st === "normal" ? "" : `${st} `}${w} 1em ${quoteFamily(listed.family)}`, _nullishCoalesce(options.sampleText, () => ( SAMPLE_TEXT)));
|
|
377
|
+
if (!matched.length) throw new Error("No face matched.");
|
|
378
|
+
done.add(k);
|
|
379
|
+
return true;
|
|
380
|
+
})().catch(() => {
|
|
381
|
+
faces.delete(k);
|
|
382
|
+
return false;
|
|
383
|
+
});
|
|
384
|
+
faces.set(k, promise);
|
|
385
|
+
return promise;
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
var GOOGLE_CATEGORIES = {
|
|
390
|
+
"sans serif": "sans-serif",
|
|
391
|
+
serif: "serif",
|
|
392
|
+
display: "display",
|
|
393
|
+
handwriting: "handwriting",
|
|
394
|
+
monospace: "monospace"
|
|
395
|
+
};
|
|
396
|
+
var tagOf = (value) => value.trim().toLowerCase().replace(/\s+/g, "-");
|
|
397
|
+
function googleFontsCatalog(metadata) {
|
|
398
|
+
let data = metadata;
|
|
399
|
+
if (typeof data === "string") data = JSON.parse(data.replace(/^\)\]\}'\s*/, ""));
|
|
400
|
+
const list = Array.isArray(data) ? data : _optionalChain([data, 'optionalAccess', _7 => _7.familyMetadataList]);
|
|
401
|
+
if (!Array.isArray(list)) throw new Error("Not Google Fonts metadata: expected familyMetadataList.");
|
|
402
|
+
const families = list.filter((f) => typeof _optionalChain([f, 'optionalAccess', _8 => _8.family]) === "string" && f.family);
|
|
403
|
+
return families.map((f, index) => ({ f, index })).sort((a, b) => (_nullishCoalesce(a.f.popularity, () => ( Infinity))) - (_nullishCoalesce(b.f.popularity, () => ( Infinity))) || a.index - b.index).map(({ f }) => {
|
|
404
|
+
const classes = (_nullishCoalesce(f.classifications, () => ( []))).map(tagOf);
|
|
405
|
+
const filed = _nullishCoalesce(GOOGLE_CATEGORIES[(_nullishCoalesce(f.category, () => ( ""))).toLowerCase()], () => ( "sans-serif"));
|
|
406
|
+
const category = _nullishCoalesce(["monospace", "handwriting", "display"].find((c) => classes.includes(c)), () => ( filed));
|
|
407
|
+
const weights = /* @__PURE__ */ new Set();
|
|
408
|
+
const styles = /* @__PURE__ */ new Set();
|
|
409
|
+
for (const face of Object.keys(_nullishCoalesce(f.fonts, () => ( {})))) {
|
|
410
|
+
const m = /^(\d{3})(i?)$/.exec(face);
|
|
411
|
+
if (!m) continue;
|
|
412
|
+
weights.add(_chunk4XSBOGMKcjs.normalizeFontWeight.call(void 0, Number(m[1])));
|
|
413
|
+
styles.add(m[2] ? "italic" : "normal");
|
|
414
|
+
}
|
|
415
|
+
const wght = _optionalChain([f, 'access', _9 => _9.axes, 'optionalAccess', _10 => _10.find, 'call', _11 => _11((a) => a.tag === "wght")]);
|
|
416
|
+
if (wght) {
|
|
417
|
+
for (const w of FONT_WEIGHTS) if (w >= wght.min && w <= wght.max) weights.add(w);
|
|
418
|
+
}
|
|
419
|
+
if (!weights.size) weights.add(400);
|
|
420
|
+
if (!styles.size) styles.add("normal");
|
|
421
|
+
const tags = [.../* @__PURE__ */ new Set([...f.category ? [tagOf(f.category)] : [], ...classes, ...f.stroke ? [tagOf(f.stroke)] : [], ...(_nullishCoalesce(f.axes, () => ( []))).map((a) => `variable-${a.tag.toLowerCase()}`)])];
|
|
422
|
+
return {
|
|
423
|
+
family: f.family,
|
|
424
|
+
category,
|
|
425
|
+
weights: [...weights].sort((a, b) => a - b),
|
|
426
|
+
styles: [...styles],
|
|
427
|
+
...wght ? { isVariable: true } : {},
|
|
428
|
+
source: "web",
|
|
429
|
+
...tags.length ? { tags } : {}
|
|
430
|
+
};
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
function googleFontsStylesheetUrl(family, weight = 400, style = "normal", options = {}) {
|
|
434
|
+
const base = _nullishCoalesce(options.baseUrl, () => ( "https://fonts.googleapis.com/css2"));
|
|
435
|
+
const name = encodeURIComponent(family.trim()).replace(/%20/g, "+");
|
|
436
|
+
return `${base}?family=${name}:ital,wght@${style === "normal" ? 0 : 1},${_chunk4XSBOGMKcjs.normalizeFontWeight.call(void 0, weight)}&display=${_nullishCoalesce(options.display, () => ( "swap"))}`;
|
|
437
|
+
}
|
|
438
|
+
function createGoogleFontsProvider(options) {
|
|
439
|
+
const { catalog, baseUrl, ...rest } = options;
|
|
440
|
+
return createStylesheetFontProvider({
|
|
441
|
+
id: "google-fonts",
|
|
442
|
+
...rest,
|
|
443
|
+
listFonts: async () => {
|
|
444
|
+
const data = await catalog();
|
|
445
|
+
const built = Array.isArray(data) && data.every((f) => typeof _optionalChain([f, 'optionalAccess', _12 => _12.family]) === "string" && typeof _optionalChain([f, 'optionalAccess', _13 => _13.category]) === "string" && Array.isArray(_optionalChain([f, 'optionalAccess', _14 => _14.weights])));
|
|
446
|
+
return built ? data : googleFontsCatalog(data);
|
|
447
|
+
},
|
|
448
|
+
stylesheetUrl: (family, weight, style) => googleFontsStylesheetUrl(family, weight, style, { baseUrl })
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
315
452
|
// src/typography/stack.ts
|
|
316
453
|
var GENERIC_FONT_FAMILIES = [
|
|
317
454
|
"serif",
|
|
@@ -406,7 +543,7 @@ function resolveFontFallback(family, category) {
|
|
|
406
543
|
const families = parseFontFamily(family);
|
|
407
544
|
if (!families.length) throw new Error("family must name at least one font.");
|
|
408
545
|
if (GENERIC.has(families[families.length - 1])) return formatFontFamily(families);
|
|
409
|
-
const cat = _nullishCoalesce(category, () => (
|
|
546
|
+
const cat = _nullishCoalesce(category, () => ( _chunk4XSBOGMKcjs.guessFontCategory.call(void 0, families[0])));
|
|
410
547
|
return formatFontFamily([...families, ...parseFontFamily(FONT_STACKS[CATEGORY_FALLBACK_STACK[cat]])]);
|
|
411
548
|
}
|
|
412
549
|
|
|
@@ -414,7 +551,7 @@ function resolveFontFallback(family, category) {
|
|
|
414
551
|
var LARGE_TEXT_PX = 24;
|
|
415
552
|
var LARGE_BOLD_TEXT_PX = 14 * 4 / 3;
|
|
416
553
|
function isLargeText(font) {
|
|
417
|
-
const bold =
|
|
554
|
+
const bold = _chunk4XSBOGMKcjs.normalizeFontWeight.call(void 0, font.weight) >= 700;
|
|
418
555
|
return font.size >= LARGE_TEXT_PX || bold && font.size >= LARGE_BOLD_TEXT_PX - 0.01;
|
|
419
556
|
}
|
|
420
557
|
function checkTypographyAccessibility(options) {
|
|
@@ -428,12 +565,12 @@ function checkTypographyAccessibility(options) {
|
|
|
428
565
|
const ratio = Math.round(_chunk5UEFBM2Mcjs.contrastRatio.call(void 0, fg, bg) * 100) / 100;
|
|
429
566
|
const level = _chunk5UEFBM2Mcjs.contrastLevel.call(void 0, ratio, { large });
|
|
430
567
|
const passesAA = level !== "fail";
|
|
431
|
-
const bold =
|
|
568
|
+
const bold = _chunk4XSBOGMKcjs.normalizeFontWeight.call(void 0, font.weight) >= 700;
|
|
432
569
|
const issues = [];
|
|
433
570
|
const required = large ? { AA: 3, AAA: 4.5 } : { AA: 4.5, AAA: 7 };
|
|
434
571
|
if (!passesAA) issues.push(`Contrast ${ratio}:1 is below the ${required.AA}:1 AA minimum for ${large ? "large" : "normal"} text.`);
|
|
435
572
|
if (font.size < 12) issues.push(`${font.size}px text is hard to read at any contrast; use 12px or more (16px for body text).`);
|
|
436
|
-
const weight =
|
|
573
|
+
const weight = _chunk4XSBOGMKcjs.normalizeFontWeight.call(void 0, font.weight);
|
|
437
574
|
if (weight <= 300 && font.size < 24) issues.push(`Weight ${weight} at ${font.size}px has thin strokes that lower effective contrast; use 400 or heavier below 24px.`);
|
|
438
575
|
const suggestion = passesAA ? void 0 : _chunkTFF2GTGNcjs.suggestAccessibleForeground.call(void 0, fg, bg, { large });
|
|
439
576
|
return {
|
|
@@ -453,7 +590,7 @@ function checkTypographyAccessibility(options) {
|
|
|
453
590
|
var ALL_WEIGHTS = [100, 200, 300, 400, 500, 600, 700, 800, 900];
|
|
454
591
|
function offeredWeights(font) {
|
|
455
592
|
if (!font || !font.weights.length) return [...ALL_WEIGHTS];
|
|
456
|
-
const ws = font.weights.map((w) =>
|
|
593
|
+
const ws = font.weights.map((w) => _chunk4XSBOGMKcjs.normalizeFontWeight.call(void 0, w));
|
|
457
594
|
if (font.isVariable) return ALL_WEIGHTS.filter((w) => w >= Math.min(...ws) && w <= Math.max(...ws));
|
|
458
595
|
return [...new Set(ws)].sort((a, b) => a - b);
|
|
459
596
|
}
|
|
@@ -477,17 +614,17 @@ var FontPickerModel = (_class = class {
|
|
|
477
614
|
this.recentFamilies = [..._nullishCoalesce(options.recent, () => ( []))];
|
|
478
615
|
const fonts = [..._nullishCoalesce(options.fonts, () => ( []))];
|
|
479
616
|
const v = _nullishCoalesce(options.value, () => ( {}));
|
|
480
|
-
const family = _nullishCoalesce(_nullishCoalesce(v.family, () => ( _optionalChain([fonts, 'access',
|
|
481
|
-
const known =
|
|
617
|
+
const family = _nullishCoalesce(_nullishCoalesce(v.family, () => ( _optionalChain([fonts, 'access', _15 => _15[0], 'optionalAccess', _16 => _16.family]))), () => ( "system-ui"));
|
|
618
|
+
const known = _chunk4XSBOGMKcjs.findFont.call(void 0, fonts, family);
|
|
482
619
|
this.state = this.derive({
|
|
483
620
|
value: {
|
|
484
|
-
family: _nullishCoalesce(_optionalChain([known, 'optionalAccess',
|
|
485
|
-
weight:
|
|
621
|
+
family: _nullishCoalesce(_optionalChain([known, 'optionalAccess', _17 => _17.family]), () => ( family)),
|
|
622
|
+
weight: _chunk4XSBOGMKcjs.normalizeFontWeight.call(void 0, v.weight),
|
|
486
623
|
style: _nullishCoalesce(v.style, () => ( "normal")),
|
|
487
624
|
size: _nullishCoalesce(v.size, () => ( 16)),
|
|
488
625
|
lineHeight: _nullishCoalesce(v.lineHeight, () => ( 1.5)),
|
|
489
626
|
letterSpacing: _nullishCoalesce(v.letterSpacing, () => ( 0)),
|
|
490
|
-
..._nullishCoalesce(v.category, () => ( _optionalChain([known, 'optionalAccess',
|
|
627
|
+
..._nullishCoalesce(v.category, () => ( _optionalChain([known, 'optionalAccess', _18 => _18.category]))) ? { category: _nullishCoalesce(v.category, () => ( _optionalChain([known, 'optionalAccess', _19 => _19.category]))) } : {}
|
|
491
628
|
},
|
|
492
629
|
category: "all",
|
|
493
630
|
query: "",
|
|
@@ -522,7 +659,7 @@ var FontPickerModel = (_class = class {
|
|
|
522
659
|
}
|
|
523
660
|
/** The selected family's summary, if it is in `fonts`. */
|
|
524
661
|
get selectedFont() {
|
|
525
|
-
return
|
|
662
|
+
return _chunk4XSBOGMKcjs.findFont.call(void 0, this.state.fonts, this.state.value.family);
|
|
526
663
|
}
|
|
527
664
|
/** Weights the selected family offers (all nine when unknown or variable). */
|
|
528
665
|
get availableWeights() {
|
|
@@ -530,13 +667,13 @@ var FontPickerModel = (_class = class {
|
|
|
530
667
|
}
|
|
531
668
|
/** Styles the selected family offers (`normal` when unknown). */
|
|
532
669
|
get availableStyles() {
|
|
533
|
-
return _optionalChain([this, 'access',
|
|
670
|
+
return _optionalChain([this, 'access', _20 => _20.selectedFont, 'optionalAccess', _21 => _21.styles, 'access', _22 => _22.length]) ? [...this.selectedFont.styles] : ["normal", "italic"];
|
|
534
671
|
}
|
|
535
672
|
/** CSS for the selection: a fallback-resolved `font-family` plus the other properties. */
|
|
536
673
|
get css() {
|
|
537
674
|
const v = this.state.value;
|
|
538
675
|
return {
|
|
539
|
-
fontFamily: resolveFontFallback(v.family, _nullishCoalesce(v.category, () => ( _optionalChain([this, 'access',
|
|
676
|
+
fontFamily: resolveFontFallback(v.family, _nullishCoalesce(v.category, () => ( _optionalChain([this, 'access', _23 => _23.selectedFont, 'optionalAccess', _24 => _24.category])))),
|
|
540
677
|
fontWeight: String(v.weight),
|
|
541
678
|
fontStyle: v.style,
|
|
542
679
|
fontSize: `${v.size}px`,
|
|
@@ -555,7 +692,7 @@ var FontPickerModel = (_class = class {
|
|
|
555
692
|
try {
|
|
556
693
|
const fonts = await this.provider.listFonts();
|
|
557
694
|
if (this.destroyed) return fonts;
|
|
558
|
-
const known =
|
|
695
|
+
const known = _chunk4XSBOGMKcjs.findFont.call(void 0, fonts, this.state.value.family);
|
|
559
696
|
this.set({ fonts, loading: false, value: known ? { ...this.state.value, family: known.family, category: known.category } : this.state.value });
|
|
560
697
|
return fonts;
|
|
561
698
|
} catch (e) {
|
|
@@ -580,11 +717,11 @@ var FontPickerModel = (_class = class {
|
|
|
580
717
|
* the family has it, else picks the nearest one; keeps the style if offered, else `normal`.
|
|
581
718
|
*/
|
|
582
719
|
selectFamily(family) {
|
|
583
|
-
const font =
|
|
720
|
+
const font = _chunk4XSBOGMKcjs.findFont.call(void 0, this.state.fonts, family);
|
|
584
721
|
if (!font && this.state.fonts.length) throw new Error(`Font "${family}" is not available; pick one of the listed families (search with a query).`);
|
|
585
|
-
const name = _nullishCoalesce(_optionalChain([font, 'optionalAccess',
|
|
722
|
+
const name = _nullishCoalesce(_optionalChain([font, 'optionalAccess', _25 => _25.family]), () => ( family.trim()));
|
|
586
723
|
if (!name) throw new Error("family must not be empty.");
|
|
587
|
-
const weight = font ?
|
|
724
|
+
const weight = font ? _chunk4XSBOGMKcjs.nearestFontWeight.call(void 0, this.state.value.weight, offeredWeights(font)) : this.state.value.weight;
|
|
588
725
|
const style = !font || font.styles.includes(this.state.value.style) ? this.state.value.style : "normal";
|
|
589
726
|
this.recentFamilies = [name, ...this.recentFamilies.filter((f) => f.toLowerCase() !== name.toLowerCase())].slice(0, this.maxRecent);
|
|
590
727
|
const { category: _old, ...rest } = this.state.value;
|
|
@@ -593,7 +730,7 @@ var FontPickerModel = (_class = class {
|
|
|
593
730
|
}
|
|
594
731
|
/** Sets the weight; snaps to the nearest weight the family offers. */
|
|
595
732
|
setWeight(weight) {
|
|
596
|
-
this.commit({ ...this.state.value, weight:
|
|
733
|
+
this.commit({ ...this.state.value, weight: _chunk4XSBOGMKcjs.nearestFontWeight.call(void 0, _chunk4XSBOGMKcjs.normalizeFontWeight.call(void 0, weight), this.availableWeights) });
|
|
597
734
|
}
|
|
598
735
|
setStyle(style) {
|
|
599
736
|
if (!["normal", "italic", "oblique"].includes(style)) throw new Error(`Unknown style "${style}"; use normal, italic or oblique.`);
|
|
@@ -618,15 +755,15 @@ var FontPickerModel = (_class = class {
|
|
|
618
755
|
setValue(value) {
|
|
619
756
|
const v = this.state.value;
|
|
620
757
|
const family = _nullishCoalesce(value.family, () => ( v.family));
|
|
621
|
-
const known =
|
|
758
|
+
const known = _chunk4XSBOGMKcjs.findFont.call(void 0, this.state.fonts, family);
|
|
622
759
|
const next = {
|
|
623
|
-
family: _nullishCoalesce(_optionalChain([known, 'optionalAccess',
|
|
624
|
-
weight: value.weight !== void 0 ?
|
|
760
|
+
family: _nullishCoalesce(_optionalChain([known, 'optionalAccess', _26 => _26.family]), () => ( family)),
|
|
761
|
+
weight: value.weight !== void 0 ? _chunk4XSBOGMKcjs.normalizeFontWeight.call(void 0, value.weight) : v.weight,
|
|
625
762
|
style: _nullishCoalesce(value.style, () => ( v.style)),
|
|
626
763
|
size: _nullishCoalesce(value.size, () => ( v.size)),
|
|
627
764
|
lineHeight: _nullishCoalesce(value.lineHeight, () => ( v.lineHeight)),
|
|
628
765
|
letterSpacing: _nullishCoalesce(value.letterSpacing, () => ( v.letterSpacing)),
|
|
629
|
-
..._nullishCoalesce(_nullishCoalesce(value.category, () => ( _optionalChain([known, 'optionalAccess',
|
|
766
|
+
..._nullishCoalesce(_nullishCoalesce(value.category, () => ( _optionalChain([known, 'optionalAccess', _27 => _27.category]))), () => ( (family === v.family ? v.category : void 0))) ? { category: _nullishCoalesce(_nullishCoalesce(value.category, () => ( _optionalChain([known, 'optionalAccess', _28 => _28.category]))), () => ( v.category)) } : {}
|
|
630
767
|
};
|
|
631
768
|
if (JSON.stringify(next) === JSON.stringify(v)) return;
|
|
632
769
|
this.set({ value: next });
|
|
@@ -682,7 +819,7 @@ var FontPickerModel = (_class = class {
|
|
|
682
819
|
/** Loads a family's face through the provider (for previews) and tracks its status. */
|
|
683
820
|
async loadFamily(family, weight, style) {
|
|
684
821
|
const key = family.toLowerCase();
|
|
685
|
-
if (!_optionalChain([this, 'access',
|
|
822
|
+
if (!_optionalChain([this, 'access', _29 => _29.provider, 'optionalAccess', _30 => _30.loadFont])) return false;
|
|
686
823
|
if (this.state.loadStatus[key] === "loaded") return true;
|
|
687
824
|
this.set({ loadStatus: { ...this.state.loadStatus, [key]: "loading" } });
|
|
688
825
|
const ok = await this.provider.loadFont(family, _nullishCoalesce(weight, () => ( this.state.value.weight)), _nullishCoalesce(style, () => ( this.state.value.style))).catch(() => false);
|
|
@@ -701,12 +838,12 @@ var FontPickerModel = (_class = class {
|
|
|
701
838
|
}
|
|
702
839
|
this.set({ value });
|
|
703
840
|
const spec = this.value;
|
|
704
|
-
_optionalChain([this, 'access',
|
|
841
|
+
_optionalChain([this, 'access', _31 => _31.onChange, 'optionalCall', _32 => _32(spec)]);
|
|
705
842
|
for (const l of [...this.valueListeners]) l(spec);
|
|
706
843
|
}
|
|
707
844
|
derive(state) {
|
|
708
|
-
const results =
|
|
709
|
-
const recent = this.recentFamilies.map((f) =>
|
|
845
|
+
const results = _chunk4XSBOGMKcjs.filterFonts.call(void 0, state.fonts, { ...state.category !== "all" ? { category: state.category } : {}, query: state.query });
|
|
846
|
+
const recent = this.recentFamilies.map((f) => _chunk4XSBOGMKcjs.findFont.call(void 0, state.fonts, f)).filter((f) => !!f);
|
|
710
847
|
return { ...state, results, recent, highlighted: Math.min(state.highlighted, results.length - 1) };
|
|
711
848
|
}
|
|
712
849
|
set(patch) {
|
|
@@ -744,11 +881,11 @@ var rejectUnknownRatio = (ratio) => {
|
|
|
744
881
|
if (typeof ratio === "string" || typeof ratio === "number") resolveScaleRatio(ratio);
|
|
745
882
|
};
|
|
746
883
|
function createTypographyToolProvider(options = {}) {
|
|
747
|
-
const fonts = _nullishCoalesce(options.provider, () => (
|
|
884
|
+
const fonts = _nullishCoalesce(options.provider, () => ( _chunk4XSBOGMKcjs.createSystemFontProvider.call(void 0, )));
|
|
748
885
|
const listFonts = {
|
|
749
886
|
name: "list_fonts",
|
|
750
887
|
title: "List fonts",
|
|
751
|
-
description: "Lists the fonts the host offers, optionally filtered by category, source, weight, style or a name query (families starting with the query first). Returns { fonts: [{ family, category, weights, styles, isVariable?, source }], total, truncated }. Use a family from here with resolve_font_stack, load_font or set_font_selection.",
|
|
888
|
+
description: "Lists the fonts the host offers, optionally filtered by category, source, weight, style, tags or a name query (families starting with the query first), in the provider's order (web services list their most popular first). Returns { fonts: [{ family, category, weights, styles, isVariable?, source, tags? }], total, truncated, tags: [{ tag, count }] } where tags counts the classes of every font matched (display, slab-serif, variable-wdth\u2026), so you can narrow by them. Use a family from here with resolve_font_stack, load_font or set_font_selection.",
|
|
752
889
|
inputSchema: {
|
|
753
890
|
type: "object",
|
|
754
891
|
properties: {
|
|
@@ -757,6 +894,7 @@ function createTypographyToolProvider(options = {}) {
|
|
|
757
894
|
weight: WEIGHT_SCHEMA,
|
|
758
895
|
style: STYLE_SCHEMA,
|
|
759
896
|
query: { type: "string", description: "Case-insensitive part of the family name." },
|
|
897
|
+
tags: { type: "array", items: { type: "string", minLength: 1 }, maxItems: 8, description: "Only fonts carrying every one of these tags (from a previous result's tags)." },
|
|
760
898
|
limit: { type: "integer", minimum: 1, maximum: 500, description: "Most fonts to return (default 50, max 500)." }
|
|
761
899
|
},
|
|
762
900
|
additionalProperties: false
|
|
@@ -764,9 +902,12 @@ function createTypographyToolProvider(options = {}) {
|
|
|
764
902
|
annotations: { title: "List fonts", ...READ },
|
|
765
903
|
handler: async (args) => {
|
|
766
904
|
const all = await fonts.listFonts();
|
|
767
|
-
const kept =
|
|
905
|
+
const kept = _chunk4XSBOGMKcjs.filterFonts.call(void 0, all, { category: args.category, source: args.source, weight: args.weight, style: args.style, query: args.query, tags: args.tags });
|
|
768
906
|
const limit = _nullishCoalesce(args.limit, () => ( 50));
|
|
769
|
-
|
|
907
|
+
const counts = /* @__PURE__ */ new Map();
|
|
908
|
+
for (const f of kept) for (const t of _nullishCoalesce(f.tags, () => ( []))) counts.set(t, (_nullishCoalesce(counts.get(t), () => ( 0))) + 1);
|
|
909
|
+
const tags = [...counts].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([tag, count]) => ({ tag, count }));
|
|
910
|
+
return { fonts: kept.slice(0, limit), total: kept.length, truncated: kept.length > limit, tags };
|
|
770
911
|
}
|
|
771
912
|
};
|
|
772
913
|
const resolveFontStack = {
|
|
@@ -787,8 +928,8 @@ function createTypographyToolProvider(options = {}) {
|
|
|
787
928
|
const families = parseFontFamily(String(_nullishCoalesce(args.family, () => ( ""))));
|
|
788
929
|
if (!families.length) throw new Error('family must name at least one font, e.g. "Inter".');
|
|
789
930
|
const first = families[0];
|
|
790
|
-
const listed =
|
|
791
|
-
const category = _nullishCoalesce(_nullishCoalesce(args.category, () => ( _optionalChain([listed, 'optionalAccess',
|
|
931
|
+
const listed = _chunk4XSBOGMKcjs.findFont.call(void 0, await fonts.listFonts(), first);
|
|
932
|
+
const category = _nullishCoalesce(_nullishCoalesce(args.category, () => ( _optionalChain([listed, 'optionalAccess', _33 => _33.category]))), () => ( _chunk4XSBOGMKcjs.guessFontCategory.call(void 0, first)));
|
|
792
933
|
const stack = resolveFontFallback(args.family, category);
|
|
793
934
|
return { stack, families: parseFontFamily(stack), category, available: !!listed || !!await fonts.hasFont(first) };
|
|
794
935
|
}
|
|
@@ -943,9 +1084,9 @@ function createTypographyToolProvider(options = {}) {
|
|
|
943
1084
|
},
|
|
944
1085
|
annotations: { title: "Load font", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
945
1086
|
handler: async (args) => {
|
|
946
|
-
const listed =
|
|
1087
|
+
const listed = _chunk4XSBOGMKcjs.findFont.call(void 0, await fonts.listFonts(), String(args.family));
|
|
947
1088
|
if (!listed) throw new Error(`Font "${args.family}" is not offered by this host; call list_fonts to see the families you can load.`);
|
|
948
|
-
const weight =
|
|
1089
|
+
const weight = _chunk4XSBOGMKcjs.normalizeFontWeight.call(void 0, args.weight);
|
|
949
1090
|
const style = _nullishCoalesce(args.style, () => ( "normal"));
|
|
950
1091
|
const loaded = await fonts.loadFont(listed.family, weight, style);
|
|
951
1092
|
return { family: listed.family, weight, style, loaded };
|
|
@@ -988,15 +1129,15 @@ function createFontPickerToolProvider(model, options = {}) {
|
|
|
988
1129
|
},
|
|
989
1130
|
annotations: { title: "Set font selection", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
990
1131
|
handler: (args) => {
|
|
991
|
-
const reason = _optionalChain([options, 'access',
|
|
1132
|
+
const reason = _optionalChain([options, 'access', _34 => _34.readOnly, 'optionalCall', _35 => _35()]);
|
|
992
1133
|
if (reason) throw new Error(`The font picker is read-only: ${reason}`);
|
|
993
1134
|
if (!Object.keys(args).length) throw new Error("Pass at least one of family, weight, style, size, lineHeight or letterSpacing.");
|
|
994
1135
|
const { fonts } = model.getState();
|
|
995
|
-
if (args.family !== void 0 && fonts.length && !
|
|
1136
|
+
if (args.family !== void 0 && fonts.length && !_chunk4XSBOGMKcjs.findFont.call(void 0, fonts, args.family)) {
|
|
996
1137
|
throw new Error(`Font "${args.family}" is not in the picker; call list_fonts and pass one of its families.`);
|
|
997
1138
|
}
|
|
998
1139
|
if (args.style !== void 0) {
|
|
999
|
-
const font = args.family !== void 0 ?
|
|
1140
|
+
const font = args.family !== void 0 ? _chunk4XSBOGMKcjs.findFont.call(void 0, fonts, args.family) : model.selectedFont;
|
|
1000
1141
|
if (font && font.styles.length && !font.styles.includes(args.style)) throw new Error(`"${font.family}" has no ${args.style} style; it offers ${font.styles.join(", ")}.`);
|
|
1001
1142
|
}
|
|
1002
1143
|
if (args.family !== void 0) model.selectFamily(args.family);
|
|
@@ -1024,7 +1165,7 @@ function createFontPickerToolProvider(model, options = {}) {
|
|
|
1024
1165
|
annotations: { title: "List picker fonts", ...READ },
|
|
1025
1166
|
handler: (args) => {
|
|
1026
1167
|
const state = model.getState();
|
|
1027
|
-
const kept =
|
|
1168
|
+
const kept = _chunk4XSBOGMKcjs.filterFonts.call(void 0, state.fonts, { category: args.category, query: args.query });
|
|
1028
1169
|
const limit = _nullishCoalesce(args.limit, () => ( 50));
|
|
1029
1170
|
return { fonts: kept.slice(0, limit), recent: state.recent.map((f) => f.family), total: kept.length, truncated: kept.length > limit };
|
|
1030
1171
|
}
|
|
@@ -1098,4 +1239,8 @@ function createFontPickerToolProvider(model, options = {}) {
|
|
|
1098
1239
|
|
|
1099
1240
|
|
|
1100
1241
|
|
|
1101
|
-
|
|
1242
|
+
|
|
1243
|
+
|
|
1244
|
+
|
|
1245
|
+
|
|
1246
|
+
exports.ASSUMED_NORMAL_LINE_HEIGHT = ASSUMED_NORMAL_LINE_HEIGHT; exports.CATEGORY_FALLBACK_STACK = CATEGORY_FALLBACK_STACK; exports.DEFAULT_PREVIEW_TEXT = DEFAULT_PREVIEW_TEXT; exports.FONT_CATEGORIES = FONT_CATEGORIES; exports.FONT_CATEGORY_LABELS = FONT_CATEGORY_LABELS; exports.FONT_PLATFORMS = _chunk4XSBOGMKcjs.FONT_PLATFORMS; exports.FONT_STACKS = FONT_STACKS; exports.FONT_STACK_NAMES = FONT_STACK_NAMES; exports.FONT_STYLES = FONT_STYLES; exports.FONT_WEIGHTS = FONT_WEIGHTS; exports.FontPickerModel = FontPickerModel; exports.GENERIC_FONT_FAMILIES = GENERIC_FONT_FAMILIES; exports.LARGE_BOLD_TEXT_PX = LARGE_BOLD_TEXT_PX; exports.LARGE_TEXT_PX = LARGE_TEXT_PX; exports.MODULAR_SCALE_RATIOS = MODULAR_SCALE_RATIOS; exports.MODULAR_SCALE_RATIO_NAMES = MODULAR_SCALE_RATIO_NAMES; exports.OPTIMAL_MEASURE = OPTIMAL_MEASURE; exports.READING_WORDS_PER_MINUTE = READING_WORDS_PER_MINUTE; exports.RECOMMENDED_LINE_HEIGHT = RECOMMENDED_LINE_HEIGHT; exports.SYSTEM_FONT_CATALOG = _chunk4XSBOGMKcjs.SYSTEM_FONT_CATALOG; exports.TYPE_HIERARCHY_STEPS = TYPE_HIERARCHY_STEPS; exports.auditTextReadability = auditTextReadability; exports.checkTypographyAccessibility = checkTypographyAccessibility; exports.combineFontProviders = _chunk4XSBOGMKcjs.combineFontProviders; exports.computeFluidSize = computeFluidSize; exports.computeTypeScale = computeTypeScale; exports.countGraphemes = countGraphemes; exports.createCatalogFontProvider = _chunk4XSBOGMKcjs.createCatalogFontProvider; exports.createFontPicker = createFontPicker; exports.createFontPickerToolProvider = createFontPickerToolProvider; exports.createGoogleFontsProvider = createGoogleFontsProvider; exports.createStylesheetFontProvider = createStylesheetFontProvider; exports.createSystemFontProvider = _chunk4XSBOGMKcjs.createSystemFontProvider; exports.createTypographyToolProvider = createTypographyToolProvider; exports.createWebFontProvider = _chunk4XSBOGMKcjs.createWebFontProvider; exports.ensureFontsLoaded = _chunk4XSBOGMKcjs.ensureFontsLoaded; exports.estimateCharactersPerLine = estimateCharactersPerLine; exports.filterFonts = _chunk4XSBOGMKcjs.filterFonts; exports.findFont = _chunk4XSBOGMKcjs.findFont; exports.fontFaceCss = _chunk4XSBOGMKcjs.fontFaceCss; exports.fontHasWeight = _chunk4XSBOGMKcjs.fontHasWeight; exports.formatFontFamily = formatFontFamily; exports.generateTypeHierarchy = generateTypeHierarchy; exports.getScaleRatio = getScaleRatio; exports.googleFontsCatalog = googleFontsCatalog; exports.googleFontsStylesheetUrl = googleFontsStylesheetUrl; exports.graphemes = graphemes; exports.guessFontCategory = _chunk4XSBOGMKcjs.guessFontCategory; exports.isFontWeight = _chunk4XSBOGMKcjs.isFontWeight; exports.isLargeText = isLargeText; exports.nearestFontWeight = _chunk4XSBOGMKcjs.nearestFontWeight; exports.normalizeFontWeight = _chunk4XSBOGMKcjs.normalizeFontWeight; exports.offeredWeights = offeredWeights; exports.parseFontFamily = parseFontFamily; exports.quoteFontFamily = quoteFontFamily; exports.recommendedLetterSpacing = recommendedLetterSpacing; exports.recommendedLineHeight = recommendedLineHeight; exports.resolveFontFallback = resolveFontFallback; exports.resolveScaleRatio = resolveScaleRatio; exports.segmentText = segmentText; exports.typeHierarchyToCss = typeHierarchyToCss; exports.typeScaleStep = typeScaleStep; exports.webFontSummary = _chunk4XSBOGMKcjs.webFontSummary;
|
|
@@ -10,6 +10,7 @@ export * from './segment.cjs';
|
|
|
10
10
|
export * from './metrics.cjs';
|
|
11
11
|
export * from './registry.cjs';
|
|
12
12
|
export * from './catalog.cjs';
|
|
13
|
+
export * from './stylesheet.cjs';
|
|
13
14
|
export * from './stack.cjs';
|
|
14
15
|
export * from './accessibility.cjs';
|
|
15
16
|
export * from './picker.cjs';
|
|
@@ -10,6 +10,7 @@ export * from './segment.js';
|
|
|
10
10
|
export * from './metrics.js';
|
|
11
11
|
export * from './registry.js';
|
|
12
12
|
export * from './catalog.js';
|
|
13
|
+
export * from './stylesheet.js';
|
|
13
14
|
export * from './stack.js';
|
|
14
15
|
export * from './accessibility.js';
|
|
15
16
|
export * from './picker.js';
|
package/dist/typography/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
nearestFontWeight,
|
|
16
16
|
normalizeFontWeight,
|
|
17
17
|
webFontSummary
|
|
18
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-N4ZXW3WB.js";
|
|
19
19
|
import {
|
|
20
20
|
suggestAccessibleForeground
|
|
21
21
|
} from "../chunk-YBAPT7NL.js";
|
|
@@ -312,6 +312,143 @@ function auditTextReadability(text, font = {}) {
|
|
|
312
312
|
};
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
// src/typography/stylesheet.ts
|
|
316
|
+
function linkStylesheet(url) {
|
|
317
|
+
const doc = globalThis.document;
|
|
318
|
+
if (!doc?.head) return Promise.reject(new Error("No document to add a stylesheet to."));
|
|
319
|
+
return new Promise((resolve, reject) => {
|
|
320
|
+
const link = doc.createElement("link");
|
|
321
|
+
link.rel = "stylesheet";
|
|
322
|
+
link.href = url;
|
|
323
|
+
link.onload = () => resolve();
|
|
324
|
+
link.onerror = () => {
|
|
325
|
+
link.remove();
|
|
326
|
+
reject(new Error(`Stylesheet failed to load: ${url}`));
|
|
327
|
+
};
|
|
328
|
+
doc.head.appendChild(link);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
var SAMPLE_TEXT = "AaBbZz09";
|
|
332
|
+
var quoteFamily = (family) => `"${family.replace(/["\\]/g, "\\$&")}"`;
|
|
333
|
+
function createStylesheetFontProvider(options) {
|
|
334
|
+
let catalog = null;
|
|
335
|
+
const fonts = () => {
|
|
336
|
+
catalog ??= Promise.resolve().then(options.listFonts).catch((error) => {
|
|
337
|
+
catalog = null;
|
|
338
|
+
throw error;
|
|
339
|
+
});
|
|
340
|
+
return catalog;
|
|
341
|
+
};
|
|
342
|
+
const addStylesheet = options.addStylesheet ?? linkStylesheet;
|
|
343
|
+
const fontSet = () => options.fontFaceSet ?? globalThis.document?.fonts;
|
|
344
|
+
const sheets = /* @__PURE__ */ new Map();
|
|
345
|
+
const faces = /* @__PURE__ */ new Map();
|
|
346
|
+
const done = /* @__PURE__ */ new Set();
|
|
347
|
+
const key = (family, weight, style) => `${family.toLowerCase()}|${normalizeFontWeight(weight)}|${style ?? "normal"}`;
|
|
348
|
+
const sheet = (url) => {
|
|
349
|
+
let pending = sheets.get(url);
|
|
350
|
+
if (!pending) {
|
|
351
|
+
pending = addStylesheet(url).catch((error) => {
|
|
352
|
+
sheets.delete(url);
|
|
353
|
+
throw error;
|
|
354
|
+
});
|
|
355
|
+
sheets.set(url, pending);
|
|
356
|
+
}
|
|
357
|
+
return pending;
|
|
358
|
+
};
|
|
359
|
+
return {
|
|
360
|
+
id: options.id ?? "stylesheet",
|
|
361
|
+
listFonts: async () => (await fonts()).map((f) => ({ ...f })),
|
|
362
|
+
hasFont: async (family) => !!findFont(await fonts(), family),
|
|
363
|
+
loaded: (family, weight, style) => done.has(key(family, weight, style)),
|
|
364
|
+
async loadFont(family, weight, style) {
|
|
365
|
+
const k = key(family, weight, style);
|
|
366
|
+
const cached = faces.get(k);
|
|
367
|
+
if (cached) return cached;
|
|
368
|
+
const promise = (async () => {
|
|
369
|
+
const listed = findFont(await fonts(), family);
|
|
370
|
+
const set = fontSet();
|
|
371
|
+
if (!listed || !set) return false;
|
|
372
|
+
const wanted = normalizeFontWeight(weight);
|
|
373
|
+
const w = listed.isVariable || !listed.weights.length ? wanted : nearestFontWeight(wanted, listed.weights);
|
|
374
|
+
const st = !listed.styles.length || listed.styles.includes(style ?? "normal") ? style ?? "normal" : "normal";
|
|
375
|
+
await sheet(options.stylesheetUrl(listed.family, w, st));
|
|
376
|
+
const matched = await set.load(`${st === "normal" ? "" : `${st} `}${w} 1em ${quoteFamily(listed.family)}`, options.sampleText ?? SAMPLE_TEXT);
|
|
377
|
+
if (!matched.length) throw new Error("No face matched.");
|
|
378
|
+
done.add(k);
|
|
379
|
+
return true;
|
|
380
|
+
})().catch(() => {
|
|
381
|
+
faces.delete(k);
|
|
382
|
+
return false;
|
|
383
|
+
});
|
|
384
|
+
faces.set(k, promise);
|
|
385
|
+
return promise;
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
var GOOGLE_CATEGORIES = {
|
|
390
|
+
"sans serif": "sans-serif",
|
|
391
|
+
serif: "serif",
|
|
392
|
+
display: "display",
|
|
393
|
+
handwriting: "handwriting",
|
|
394
|
+
monospace: "monospace"
|
|
395
|
+
};
|
|
396
|
+
var tagOf = (value) => value.trim().toLowerCase().replace(/\s+/g, "-");
|
|
397
|
+
function googleFontsCatalog(metadata) {
|
|
398
|
+
let data = metadata;
|
|
399
|
+
if (typeof data === "string") data = JSON.parse(data.replace(/^\)\]\}'\s*/, ""));
|
|
400
|
+
const list = Array.isArray(data) ? data : data?.familyMetadataList;
|
|
401
|
+
if (!Array.isArray(list)) throw new Error("Not Google Fonts metadata: expected familyMetadataList.");
|
|
402
|
+
const families = list.filter((f) => typeof f?.family === "string" && f.family);
|
|
403
|
+
return families.map((f, index) => ({ f, index })).sort((a, b) => (a.f.popularity ?? Infinity) - (b.f.popularity ?? Infinity) || a.index - b.index).map(({ f }) => {
|
|
404
|
+
const classes = (f.classifications ?? []).map(tagOf);
|
|
405
|
+
const filed = GOOGLE_CATEGORIES[(f.category ?? "").toLowerCase()] ?? "sans-serif";
|
|
406
|
+
const category = ["monospace", "handwriting", "display"].find((c) => classes.includes(c)) ?? filed;
|
|
407
|
+
const weights = /* @__PURE__ */ new Set();
|
|
408
|
+
const styles = /* @__PURE__ */ new Set();
|
|
409
|
+
for (const face of Object.keys(f.fonts ?? {})) {
|
|
410
|
+
const m = /^(\d{3})(i?)$/.exec(face);
|
|
411
|
+
if (!m) continue;
|
|
412
|
+
weights.add(normalizeFontWeight(Number(m[1])));
|
|
413
|
+
styles.add(m[2] ? "italic" : "normal");
|
|
414
|
+
}
|
|
415
|
+
const wght = f.axes?.find((a) => a.tag === "wght");
|
|
416
|
+
if (wght) {
|
|
417
|
+
for (const w of FONT_WEIGHTS) if (w >= wght.min && w <= wght.max) weights.add(w);
|
|
418
|
+
}
|
|
419
|
+
if (!weights.size) weights.add(400);
|
|
420
|
+
if (!styles.size) styles.add("normal");
|
|
421
|
+
const tags = [.../* @__PURE__ */ new Set([...f.category ? [tagOf(f.category)] : [], ...classes, ...f.stroke ? [tagOf(f.stroke)] : [], ...(f.axes ?? []).map((a) => `variable-${a.tag.toLowerCase()}`)])];
|
|
422
|
+
return {
|
|
423
|
+
family: f.family,
|
|
424
|
+
category,
|
|
425
|
+
weights: [...weights].sort((a, b) => a - b),
|
|
426
|
+
styles: [...styles],
|
|
427
|
+
...wght ? { isVariable: true } : {},
|
|
428
|
+
source: "web",
|
|
429
|
+
...tags.length ? { tags } : {}
|
|
430
|
+
};
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
function googleFontsStylesheetUrl(family, weight = 400, style = "normal", options = {}) {
|
|
434
|
+
const base = options.baseUrl ?? "https://fonts.googleapis.com/css2";
|
|
435
|
+
const name = encodeURIComponent(family.trim()).replace(/%20/g, "+");
|
|
436
|
+
return `${base}?family=${name}:ital,wght@${style === "normal" ? 0 : 1},${normalizeFontWeight(weight)}&display=${options.display ?? "swap"}`;
|
|
437
|
+
}
|
|
438
|
+
function createGoogleFontsProvider(options) {
|
|
439
|
+
const { catalog, baseUrl, ...rest } = options;
|
|
440
|
+
return createStylesheetFontProvider({
|
|
441
|
+
id: "google-fonts",
|
|
442
|
+
...rest,
|
|
443
|
+
listFonts: async () => {
|
|
444
|
+
const data = await catalog();
|
|
445
|
+
const built = Array.isArray(data) && data.every((f) => typeof f?.family === "string" && typeof f?.category === "string" && Array.isArray(f?.weights));
|
|
446
|
+
return built ? data : googleFontsCatalog(data);
|
|
447
|
+
},
|
|
448
|
+
stylesheetUrl: (family, weight, style) => googleFontsStylesheetUrl(family, weight, style, { baseUrl })
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
315
452
|
// src/typography/stack.ts
|
|
316
453
|
var GENERIC_FONT_FAMILIES = [
|
|
317
454
|
"serif",
|
|
@@ -748,7 +885,7 @@ function createTypographyToolProvider(options = {}) {
|
|
|
748
885
|
const listFonts = {
|
|
749
886
|
name: "list_fonts",
|
|
750
887
|
title: "List fonts",
|
|
751
|
-
description: "Lists the fonts the host offers, optionally filtered by category, source, weight, style or a name query (families starting with the query first). Returns { fonts: [{ family, category, weights, styles, isVariable?, source }], total, truncated }. Use a family from here with resolve_font_stack, load_font or set_font_selection.",
|
|
888
|
+
description: "Lists the fonts the host offers, optionally filtered by category, source, weight, style, tags or a name query (families starting with the query first), in the provider's order (web services list their most popular first). Returns { fonts: [{ family, category, weights, styles, isVariable?, source, tags? }], total, truncated, tags: [{ tag, count }] } where tags counts the classes of every font matched (display, slab-serif, variable-wdth\u2026), so you can narrow by them. Use a family from here with resolve_font_stack, load_font or set_font_selection.",
|
|
752
889
|
inputSchema: {
|
|
753
890
|
type: "object",
|
|
754
891
|
properties: {
|
|
@@ -757,6 +894,7 @@ function createTypographyToolProvider(options = {}) {
|
|
|
757
894
|
weight: WEIGHT_SCHEMA,
|
|
758
895
|
style: STYLE_SCHEMA,
|
|
759
896
|
query: { type: "string", description: "Case-insensitive part of the family name." },
|
|
897
|
+
tags: { type: "array", items: { type: "string", minLength: 1 }, maxItems: 8, description: "Only fonts carrying every one of these tags (from a previous result's tags)." },
|
|
760
898
|
limit: { type: "integer", minimum: 1, maximum: 500, description: "Most fonts to return (default 50, max 500)." }
|
|
761
899
|
},
|
|
762
900
|
additionalProperties: false
|
|
@@ -764,9 +902,12 @@ function createTypographyToolProvider(options = {}) {
|
|
|
764
902
|
annotations: { title: "List fonts", ...READ },
|
|
765
903
|
handler: async (args) => {
|
|
766
904
|
const all = await fonts.listFonts();
|
|
767
|
-
const kept = filterFonts(all, { category: args.category, source: args.source, weight: args.weight, style: args.style, query: args.query });
|
|
905
|
+
const kept = filterFonts(all, { category: args.category, source: args.source, weight: args.weight, style: args.style, query: args.query, tags: args.tags });
|
|
768
906
|
const limit = args.limit ?? 50;
|
|
769
|
-
|
|
907
|
+
const counts = /* @__PURE__ */ new Map();
|
|
908
|
+
for (const f of kept) for (const t of f.tags ?? []) counts.set(t, (counts.get(t) ?? 0) + 1);
|
|
909
|
+
const tags = [...counts].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([tag, count]) => ({ tag, count }));
|
|
910
|
+
return { fonts: kept.slice(0, limit), total: kept.length, truncated: kept.length > limit, tags };
|
|
770
911
|
}
|
|
771
912
|
};
|
|
772
913
|
const resolveFontStack = {
|
|
@@ -1069,6 +1210,8 @@ export {
|
|
|
1069
1210
|
createCatalogFontProvider,
|
|
1070
1211
|
createFontPicker,
|
|
1071
1212
|
createFontPickerToolProvider,
|
|
1213
|
+
createGoogleFontsProvider,
|
|
1214
|
+
createStylesheetFontProvider,
|
|
1072
1215
|
createSystemFontProvider,
|
|
1073
1216
|
createTypographyToolProvider,
|
|
1074
1217
|
createWebFontProvider,
|
|
@@ -1081,6 +1224,8 @@ export {
|
|
|
1081
1224
|
formatFontFamily,
|
|
1082
1225
|
generateTypeHierarchy,
|
|
1083
1226
|
getScaleRatio,
|
|
1227
|
+
googleFontsCatalog,
|
|
1228
|
+
googleFontsStylesheetUrl,
|
|
1084
1229
|
graphemes,
|
|
1085
1230
|
guessFontCategory,
|
|
1086
1231
|
isFontWeight,
|