@pixerate/editor 0.176.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.
@@ -30,8 +30,9 @@ function fontHasWeight(font, weight) {
30
30
  }
31
31
  function filterFonts(fonts, filter = {}) {
32
32
  const q = _nullishCoalesce(_optionalChain([filter, 'access', _ => _.query, 'optionalAccess', _2 => _2.trim, 'call', _3 => _3(), 'access', _4 => _4.toLowerCase, 'call', _5 => _5()]), () => ( ""));
33
+ const wantedTags = (_nullishCoalesce(filter.tags, () => ( []))).map((t) => t.trim().toLowerCase().replace(/\s+/g, "-")).filter(Boolean);
33
34
  const kept = fonts.filter(
34
- (f) => (!filter.category || f.category === filter.category) && (!filter.source || f.source === filter.source) && (filter.weight === void 0 || fontHasWeight(f, filter.weight)) && (!filter.style || f.styles.includes(filter.style)) && (filter.isVariable === void 0 || !!f.isVariable === filter.isVariable) && (!q || f.family.toLowerCase().includes(q))
35
+ (f) => (!filter.category || f.category === filter.category) && (!filter.source || f.source === filter.source) && (filter.weight === void 0 || fontHasWeight(f, filter.weight)) && (!filter.style || f.styles.includes(filter.style)) && (filter.isVariable === void 0 || !!f.isVariable === filter.isVariable) && (!wantedTags.length || wantedTags.every((t) => _optionalChain([f, 'access', _6 => _6.tags, 'optionalAccess', _7 => _7.includes, 'call', _8 => _8(t)]))) && (!q || f.family.toLowerCase().includes(q))
35
36
  );
36
37
  if (!q) return kept;
37
38
  const starts = kept.filter((f) => f.family.toLowerCase().startsWith(q));
@@ -217,7 +218,7 @@ function createSystemFontProvider(options = {}) {
217
218
  for (const e of entries) {
218
219
  const key = e.family.toLowerCase();
219
220
  const known = findFont(SYSTEM_FONT_CATALOG, e.family);
220
- const entry = _nullishCoalesce(byFamily.get(key), () => ( { family: _nullishCoalesce(_optionalChain([known, 'optionalAccess', _6 => _6.family]), () => ( e.family)), category: _nullishCoalesce(_optionalChain([known, 'optionalAccess', _7 => _7.category]), () => ( guessFontCategory(e.family))), weights: [], styles: [], source: "system" }));
221
+ const entry = _nullishCoalesce(byFamily.get(key), () => ( { family: _nullishCoalesce(_optionalChain([known, 'optionalAccess', _9 => _9.family]), () => ( e.family)), category: _nullishCoalesce(_optionalChain([known, 'optionalAccess', _10 => _10.category]), () => ( guessFontCategory(e.family))), weights: [], styles: [], source: "system" }));
221
222
  const w = weightFromStyleName(e.style);
222
223
  if (!entry.weights.includes(w)) entry.weights.push(w);
223
224
  const st = /italic/i.test(_nullishCoalesce(e.style, () => ( ""))) ? "italic" : /oblique/i.test(_nullishCoalesce(e.style, () => ( ""))) ? "oblique" : "normal";
@@ -279,7 +280,7 @@ function createWebFontProvider(options) {
279
280
  const done = /* @__PURE__ */ new Set();
280
281
  const g = globalThis;
281
282
  const createFace = _nullishCoalesce(options.createFontFace, () => ( (g.FontFace ? (family, source, descriptors) => new g.FontFace(family, source, descriptors) : void 0)));
282
- const set = () => _nullishCoalesce(options.fontFaceSet, () => ( _optionalChain([g, 'access', _8 => _8.document, 'optionalAccess', _9 => _9.fonts])));
283
+ const set = () => _nullishCoalesce(options.fontFaceSet, () => ( _optionalChain([g, 'access', _11 => _11.document, 'optionalAccess', _12 => _12.fonts])));
283
284
  const def = (family) => defs.find((d) => d.family.toLowerCase() === family.trim().toLowerCase());
284
285
  const key = (family, weight, style) => `${family.toLowerCase()}|${normalizeFontWeight(weight)}|${_nullishCoalesce(style, () => ( "normal"))}`;
285
286
  function pickSource(d, weight, style) {
@@ -314,7 +315,7 @@ function createWebFontProvider(options) {
314
315
  ...source.unicodeRange ? { unicodeRange: source.unicodeRange } : {}
315
316
  });
316
317
  const promise = face.load().then(() => {
317
- _optionalChain([set, 'call', _10 => _10(), 'optionalAccess', _11 => _11.add, 'call', _12 => _12(face)]);
318
+ _optionalChain([set, 'call', _13 => _13(), 'optionalAccess', _14 => _14.add, 'call', _15 => _15(face)]);
318
319
  done.add(k);
319
320
  return true;
320
321
  }).catch(() => {
@@ -30,8 +30,9 @@ function fontHasWeight(font, weight) {
30
30
  }
31
31
  function filterFonts(fonts, filter = {}) {
32
32
  const q = filter.query?.trim().toLowerCase() ?? "";
33
+ const wantedTags = (filter.tags ?? []).map((t) => t.trim().toLowerCase().replace(/\s+/g, "-")).filter(Boolean);
33
34
  const kept = fonts.filter(
34
- (f) => (!filter.category || f.category === filter.category) && (!filter.source || f.source === filter.source) && (filter.weight === void 0 || fontHasWeight(f, filter.weight)) && (!filter.style || f.styles.includes(filter.style)) && (filter.isVariable === void 0 || !!f.isVariable === filter.isVariable) && (!q || f.family.toLowerCase().includes(q))
35
+ (f) => (!filter.category || f.category === filter.category) && (!filter.source || f.source === filter.source) && (filter.weight === void 0 || fontHasWeight(f, filter.weight)) && (!filter.style || f.styles.includes(filter.style)) && (filter.isVariable === void 0 || !!f.isVariable === filter.isVariable) && (!wantedTags.length || wantedTags.every((t) => f.tags?.includes(t))) && (!q || f.family.toLowerCase().includes(q))
35
36
  );
36
37
  if (!q) return kept;
37
38
  const starts = kept.filter((f) => f.family.toLowerCase().startsWith(q));
@@ -1,6 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
- var _chunkFKUKJ3XAcjs = require('../chunk-FKUKJ3XA.cjs');
3
+ var _chunk4XSBOGMKcjs = require('../chunk-4XSBOGMK.cjs');
4
4
 
5
5
 
6
6
  var _chunk6DUEV36Fcjs = require('../chunk-6DUEV36F.cjs');
@@ -621,7 +621,7 @@ function effectControlField(name, schema, fallback, options = {}) {
621
621
  }
622
622
  if (type === "boolean") return { ...base, type: "boolean", default: def === true };
623
623
  if (isEffectFontControl(schema)) {
624
- const fonts = _nullishCoalesce(options.fonts, () => ( _chunkFKUKJ3XAcjs.SYSTEM_FONT_CATALOG));
624
+ const fonts = _nullishCoalesce(options.fonts, () => ( _chunk4XSBOGMKcjs.SYSTEM_FONT_CATALOG));
625
625
  const opts = fonts.map((f) => ({ value: f.family, label: f.family, description: f.category }));
626
626
  const current = typeof def === "string" && def ? def : _nullishCoalesce(_optionalChain([opts, 'access', _23 => _23[0], 'optionalAccess', _24 => _24.value]), () => ( "sans-serif"));
627
627
  if (!opts.some((o) => o.value.toLowerCase() === current.toLowerCase())) opts.unshift({ value: current, label: current, description: "current" });
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SYSTEM_FONT_CATALOG
3
- } from "../chunk-CJE5MOIS.js";
3
+ } from "../chunk-N4ZXW3WB.js";
4
4
  import {
5
5
  createEffectAuthoringSession
6
6
  } from "../chunk-WMVEYSOA.js";
@@ -15,7 +15,7 @@
15
15
 
16
16
 
17
17
 
18
- var _chunkFKUKJ3XAcjs = require('../chunk-FKUKJ3XA.cjs');
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, () => ( _chunkFKUKJ3XAcjs.guessFontCategory.call(void 0, families[0])));
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 = _chunkFKUKJ3XAcjs.normalizeFontWeight.call(void 0, font.weight) >= 700;
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 = _chunkFKUKJ3XAcjs.normalizeFontWeight.call(void 0, font.weight) >= 700;
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 = _chunkFKUKJ3XAcjs.normalizeFontWeight.call(void 0, font.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) => _chunkFKUKJ3XAcjs.normalizeFontWeight.call(void 0, 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', _4 => _4[0], 'optionalAccess', _5 => _5.family]))), () => ( "system-ui"));
481
- const known = _chunkFKUKJ3XAcjs.findFont.call(void 0, fonts, family);
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', _6 => _6.family]), () => ( family)),
485
- weight: _chunkFKUKJ3XAcjs.normalizeFontWeight.call(void 0, v.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', _7 => _7.category]))) ? { category: _nullishCoalesce(v.category, () => ( _optionalChain([known, 'optionalAccess', _8 => _8.category]))) } : {}
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 _chunkFKUKJ3XAcjs.findFont.call(void 0, this.state.fonts, this.state.value.family);
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', _9 => _9.selectedFont, 'optionalAccess', _10 => _10.styles, 'access', _11 => _11.length]) ? [...this.selectedFont.styles] : ["normal", "italic"];
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', _12 => _12.selectedFont, 'optionalAccess', _13 => _13.category])))),
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 = _chunkFKUKJ3XAcjs.findFont.call(void 0, fonts, this.state.value.family);
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 = _chunkFKUKJ3XAcjs.findFont.call(void 0, this.state.fonts, family);
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', _14 => _14.family]), () => ( family.trim()));
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 ? _chunkFKUKJ3XAcjs.nearestFontWeight.call(void 0, this.state.value.weight, offeredWeights(font)) : this.state.value.weight;
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: _chunkFKUKJ3XAcjs.nearestFontWeight.call(void 0, _chunkFKUKJ3XAcjs.normalizeFontWeight.call(void 0, weight), this.availableWeights) });
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 = _chunkFKUKJ3XAcjs.findFont.call(void 0, this.state.fonts, family);
758
+ const known = _chunk4XSBOGMKcjs.findFont.call(void 0, this.state.fonts, family);
622
759
  const next = {
623
- family: _nullishCoalesce(_optionalChain([known, 'optionalAccess', _15 => _15.family]), () => ( family)),
624
- weight: value.weight !== void 0 ? _chunkFKUKJ3XAcjs.normalizeFontWeight.call(void 0, value.weight) : v.weight,
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', _16 => _16.category]))), () => ( (family === v.family ? v.category : void 0))) ? { category: _nullishCoalesce(_nullishCoalesce(value.category, () => ( _optionalChain([known, 'optionalAccess', _17 => _17.category]))), () => ( v.category)) } : {}
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', _18 => _18.provider, 'optionalAccess', _19 => _19.loadFont])) return false;
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', _20 => _20.onChange, 'optionalCall', _21 => _21(spec)]);
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 = _chunkFKUKJ3XAcjs.filterFonts.call(void 0, state.fonts, { ...state.category !== "all" ? { category: state.category } : {}, query: state.query });
709
- const recent = this.recentFamilies.map((f) => _chunkFKUKJ3XAcjs.findFont.call(void 0, state.fonts, f)).filter((f) => !!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, () => ( _chunkFKUKJ3XAcjs.createSystemFontProvider.call(void 0, )));
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 = _chunkFKUKJ3XAcjs.filterFonts.call(void 0, all, { category: args.category, source: args.source, weight: args.weight, style: args.style, query: args.query });
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
- return { fonts: kept.slice(0, limit), total: kept.length, truncated: kept.length > limit };
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 = _chunkFKUKJ3XAcjs.findFont.call(void 0, await fonts.listFonts(), first);
791
- const category = _nullishCoalesce(_nullishCoalesce(args.category, () => ( _optionalChain([listed, 'optionalAccess', _22 => _22.category]))), () => ( _chunkFKUKJ3XAcjs.guessFontCategory.call(void 0, first)));
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 = _chunkFKUKJ3XAcjs.findFont.call(void 0, await fonts.listFonts(), String(args.family));
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 = _chunkFKUKJ3XAcjs.normalizeFontWeight.call(void 0, args.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', _23 => _23.readOnly, 'optionalCall', _24 => _24()]);
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 && !_chunkFKUKJ3XAcjs.findFont.call(void 0, fonts, args.family)) {
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 ? _chunkFKUKJ3XAcjs.findFont.call(void 0, fonts, args.family) : model.selectedFont;
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 = _chunkFKUKJ3XAcjs.filterFonts.call(void 0, state.fonts, { category: args.category, query: args.query });
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
- 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 = _chunkFKUKJ3XAcjs.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 = _chunkFKUKJ3XAcjs.SYSTEM_FONT_CATALOG; exports.TYPE_HIERARCHY_STEPS = TYPE_HIERARCHY_STEPS; exports.auditTextReadability = auditTextReadability; exports.checkTypographyAccessibility = checkTypographyAccessibility; exports.combineFontProviders = _chunkFKUKJ3XAcjs.combineFontProviders; exports.computeFluidSize = computeFluidSize; exports.computeTypeScale = computeTypeScale; exports.countGraphemes = countGraphemes; exports.createCatalogFontProvider = _chunkFKUKJ3XAcjs.createCatalogFontProvider; exports.createFontPicker = createFontPicker; exports.createFontPickerToolProvider = createFontPickerToolProvider; exports.createSystemFontProvider = _chunkFKUKJ3XAcjs.createSystemFontProvider; exports.createTypographyToolProvider = createTypographyToolProvider; exports.createWebFontProvider = _chunkFKUKJ3XAcjs.createWebFontProvider; exports.ensureFontsLoaded = _chunkFKUKJ3XAcjs.ensureFontsLoaded; exports.estimateCharactersPerLine = estimateCharactersPerLine; exports.filterFonts = _chunkFKUKJ3XAcjs.filterFonts; exports.findFont = _chunkFKUKJ3XAcjs.findFont; exports.fontFaceCss = _chunkFKUKJ3XAcjs.fontFaceCss; exports.fontHasWeight = _chunkFKUKJ3XAcjs.fontHasWeight; exports.formatFontFamily = formatFontFamily; exports.generateTypeHierarchy = generateTypeHierarchy; exports.getScaleRatio = getScaleRatio; exports.graphemes = graphemes; exports.guessFontCategory = _chunkFKUKJ3XAcjs.guessFontCategory; exports.isFontWeight = _chunkFKUKJ3XAcjs.isFontWeight; exports.isLargeText = isLargeText; exports.nearestFontWeight = _chunkFKUKJ3XAcjs.nearestFontWeight; exports.normalizeFontWeight = _chunkFKUKJ3XAcjs.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 = _chunkFKUKJ3XAcjs.webFontSummary;
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';
@@ -15,7 +15,7 @@ import {
15
15
  nearestFontWeight,
16
16
  normalizeFontWeight,
17
17
  webFontSummary
18
- } from "../chunk-CJE5MOIS.js";
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
- return { fonts: kept.slice(0, limit), total: kept.length, truncated: kept.length > limit };
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,
@@ -14,6 +14,8 @@ export interface FontSummary {
14
14
  source: FontSource;
15
15
  /** Platforms that ship it, for system fonts. */
16
16
  platforms?: FontPlatform[];
17
+ /** Lowercase, hyphenated classes from the provider, e.g. `display`, `slab-serif`, `variable-wdth`. */
18
+ tags?: string[];
17
19
  }
18
20
  export interface FontProvider {
19
21
  readonly id: string;
@@ -33,6 +35,8 @@ export interface FontFilter {
33
35
  /** Case-insensitive substring of the family name. */
34
36
  query?: string;
35
37
  isVariable?: boolean;
38
+ /** Keep fonts carrying every one of these tags (any case, spaces as hyphens). */
39
+ tags?: readonly string[];
36
40
  }
37
41
  /** Whether a font offers a weight (variable fonts offer every weight in their range). */
38
42
  export declare function fontHasWeight(font: FontSummary, weight: FontWeight): boolean;
@@ -14,6 +14,8 @@ export interface FontSummary {
14
14
  source: FontSource;
15
15
  /** Platforms that ship it, for system fonts. */
16
16
  platforms?: FontPlatform[];
17
+ /** Lowercase, hyphenated classes from the provider, e.g. `display`, `slab-serif`, `variable-wdth`. */
18
+ tags?: string[];
17
19
  }
18
20
  export interface FontProvider {
19
21
  readonly id: string;
@@ -33,6 +35,8 @@ export interface FontFilter {
33
35
  /** Case-insensitive substring of the family name. */
34
36
  query?: string;
35
37
  isVariable?: boolean;
38
+ /** Keep fonts carrying every one of these tags (any case, spaces as hyphens). */
39
+ tags?: readonly string[];
36
40
  }
37
41
  /** Whether a font offers a weight (variable fonts offer every weight in their range). */
38
42
  export declare function fontHasWeight(font: FontSummary, weight: FontWeight): boolean;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Font services that serve CSS (Google Fonts, Bunny Fonts, a self-hosted css2-compatible proxy):
3
+ * a provider that loads a family by adding the service's stylesheet and waiting for the face,
4
+ * and an adapter for the Google Fonts catalog and stylesheet URLs.
5
+ */
6
+ import { type FontProvider, type FontSummary } from './registry.cjs';
7
+ import { type FontStyle, type FontWeight } from './types.cjs';
8
+ /** The parts of `document.fonts` the stylesheet provider uses. */
9
+ export interface StylesheetFontSetLike {
10
+ load(font: string, text?: string): Promise<readonly unknown[]>;
11
+ }
12
+ export interface StylesheetFontProviderOptions {
13
+ id?: string;
14
+ /** The service's families; called once, on first use. */
15
+ listFonts: () => FontSummary[] | Promise<FontSummary[]>;
16
+ /** The stylesheet URL that declares one face. */
17
+ stylesheetUrl: (family: string, weight: FontWeight, style: FontStyle) => string;
18
+ /**
19
+ * Adds a stylesheet and resolves when it has loaded (default: a `<link rel="stylesheet">` in
20
+ * `document.head`). Inject a fake in tests: nothing then hits the network.
21
+ */
22
+ addStylesheet?: (url: string) => Promise<void>;
23
+ /** Where faces are waited for (default `document.fonts`). */
24
+ fontFaceSet?: StylesheetFontSetLike;
25
+ /**
26
+ * Characters the face must cover once loaded (default Latin letters and digits). Services split
27
+ * a family into `unicode-range` subsets, and `document.fonts.load` without text fetches none of
28
+ * the files a page needs for words.
29
+ */
30
+ sampleText?: string;
31
+ }
32
+ /**
33
+ * Fonts from a service that serves CSS. `loadFont` asks for the nearest weight and style the
34
+ * family offers, adds that face's stylesheet once, then resolves `true` when the browser has the face (`document.fonts.load` matched it), `false` when the family
35
+ * is not listed, the stylesheet fails or no face matches. Loads are cached per face; failures can
36
+ * be retried.
37
+ */
38
+ export declare function createStylesheetFontProvider(options: StylesheetFontProviderOptions): FontProvider & {
39
+ loaded(family: string, weight?: FontWeight, style?: FontStyle): boolean;
40
+ };
41
+ /** One family in the Google Fonts metadata (`https://fonts.google.com/metadata/fonts`), as far as this module reads it. */
42
+ export interface GoogleFontsFamilyMetadata {
43
+ family: string;
44
+ /** `Sans Serif`, `Serif`, `Display`, `Handwriting` or `Monospace`. */
45
+ category?: string;
46
+ /** Further classes, e.g. `Display`, `Handwriting`, `Monospace`, `Symbols`. */
47
+ classifications?: readonly string[];
48
+ /** Stroke class, e.g. `Sans Serif`, `Serif`, `Slab Serif`. */
49
+ stroke?: string | null;
50
+ /** Faces by weight, `i` for italic: `{ "400": …, "700i": … }`. */
51
+ fonts?: Record<string, unknown>;
52
+ axes?: ReadonlyArray<{
53
+ tag: string;
54
+ min: number;
55
+ max: number;
56
+ }>;
57
+ /** Rank, 1 = most popular. */
58
+ popularity?: number;
59
+ subsets?: readonly string[];
60
+ }
61
+ /**
62
+ * The families in a Google Fonts metadata response (the object, its `familyMetadataList`, or the
63
+ * raw text with its `)]}'` prefix), most popular first. A family classified as display,
64
+ * handwriting or monospace takes that category even when Google files it under sans serif or
65
+ * serif (Anton is a sans serif classified as display); its classes, stroke and axes become
66
+ * `tags` (`display`, `slab-serif`, `variable-wdth`…).
67
+ */
68
+ export declare function googleFontsCatalog(metadata: unknown): FontSummary[];
69
+ /** The Google Fonts css2 URL for one face, e.g. `…/css2?family=Space+Grotesk:ital,wght@0,700&display=swap`. */
70
+ export declare function googleFontsStylesheetUrl(family: string, weight?: FontWeight, style?: FontStyle, options?: {
71
+ baseUrl?: string;
72
+ display?: string;
73
+ }): string;
74
+ export interface GoogleFontsProviderOptions extends Omit<StylesheetFontProviderOptions, 'listFonts' | 'stylesheetUrl'> {
75
+ /**
76
+ * The Google Fonts metadata, or the catalog already built from it. fonts.google.com does not
77
+ * allow cross-origin reads, so a browser host fetches it through its own server.
78
+ */
79
+ catalog: () => Promise<unknown>;
80
+ /** css2-compatible stylesheet endpoint (default `https://fonts.googleapis.com/css2`). */
81
+ baseUrl?: string;
82
+ }
83
+ /** Google Fonts (or a css2-compatible mirror): its catalog, loaded face by face through its stylesheets. */
84
+ export declare function createGoogleFontsProvider(options: GoogleFontsProviderOptions): FontProvider & {
85
+ loaded(family: string, weight?: FontWeight, style?: FontStyle): boolean;
86
+ };
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Font services that serve CSS (Google Fonts, Bunny Fonts, a self-hosted css2-compatible proxy):
3
+ * a provider that loads a family by adding the service's stylesheet and waiting for the face,
4
+ * and an adapter for the Google Fonts catalog and stylesheet URLs.
5
+ */
6
+ import { type FontProvider, type FontSummary } from './registry.js';
7
+ import { type FontStyle, type FontWeight } from './types.js';
8
+ /** The parts of `document.fonts` the stylesheet provider uses. */
9
+ export interface StylesheetFontSetLike {
10
+ load(font: string, text?: string): Promise<readonly unknown[]>;
11
+ }
12
+ export interface StylesheetFontProviderOptions {
13
+ id?: string;
14
+ /** The service's families; called once, on first use. */
15
+ listFonts: () => FontSummary[] | Promise<FontSummary[]>;
16
+ /** The stylesheet URL that declares one face. */
17
+ stylesheetUrl: (family: string, weight: FontWeight, style: FontStyle) => string;
18
+ /**
19
+ * Adds a stylesheet and resolves when it has loaded (default: a `<link rel="stylesheet">` in
20
+ * `document.head`). Inject a fake in tests: nothing then hits the network.
21
+ */
22
+ addStylesheet?: (url: string) => Promise<void>;
23
+ /** Where faces are waited for (default `document.fonts`). */
24
+ fontFaceSet?: StylesheetFontSetLike;
25
+ /**
26
+ * Characters the face must cover once loaded (default Latin letters and digits). Services split
27
+ * a family into `unicode-range` subsets, and `document.fonts.load` without text fetches none of
28
+ * the files a page needs for words.
29
+ */
30
+ sampleText?: string;
31
+ }
32
+ /**
33
+ * Fonts from a service that serves CSS. `loadFont` asks for the nearest weight and style the
34
+ * family offers, adds that face's stylesheet once, then resolves `true` when the browser has the face (`document.fonts.load` matched it), `false` when the family
35
+ * is not listed, the stylesheet fails or no face matches. Loads are cached per face; failures can
36
+ * be retried.
37
+ */
38
+ export declare function createStylesheetFontProvider(options: StylesheetFontProviderOptions): FontProvider & {
39
+ loaded(family: string, weight?: FontWeight, style?: FontStyle): boolean;
40
+ };
41
+ /** One family in the Google Fonts metadata (`https://fonts.google.com/metadata/fonts`), as far as this module reads it. */
42
+ export interface GoogleFontsFamilyMetadata {
43
+ family: string;
44
+ /** `Sans Serif`, `Serif`, `Display`, `Handwriting` or `Monospace`. */
45
+ category?: string;
46
+ /** Further classes, e.g. `Display`, `Handwriting`, `Monospace`, `Symbols`. */
47
+ classifications?: readonly string[];
48
+ /** Stroke class, e.g. `Sans Serif`, `Serif`, `Slab Serif`. */
49
+ stroke?: string | null;
50
+ /** Faces by weight, `i` for italic: `{ "400": …, "700i": … }`. */
51
+ fonts?: Record<string, unknown>;
52
+ axes?: ReadonlyArray<{
53
+ tag: string;
54
+ min: number;
55
+ max: number;
56
+ }>;
57
+ /** Rank, 1 = most popular. */
58
+ popularity?: number;
59
+ subsets?: readonly string[];
60
+ }
61
+ /**
62
+ * The families in a Google Fonts metadata response (the object, its `familyMetadataList`, or the
63
+ * raw text with its `)]}'` prefix), most popular first. A family classified as display,
64
+ * handwriting or monospace takes that category even when Google files it under sans serif or
65
+ * serif (Anton is a sans serif classified as display); its classes, stroke and axes become
66
+ * `tags` (`display`, `slab-serif`, `variable-wdth`…).
67
+ */
68
+ export declare function googleFontsCatalog(metadata: unknown): FontSummary[];
69
+ /** The Google Fonts css2 URL for one face, e.g. `…/css2?family=Space+Grotesk:ital,wght@0,700&display=swap`. */
70
+ export declare function googleFontsStylesheetUrl(family: string, weight?: FontWeight, style?: FontStyle, options?: {
71
+ baseUrl?: string;
72
+ display?: string;
73
+ }): string;
74
+ export interface GoogleFontsProviderOptions extends Omit<StylesheetFontProviderOptions, 'listFonts' | 'stylesheetUrl'> {
75
+ /**
76
+ * The Google Fonts metadata, or the catalog already built from it. fonts.google.com does not
77
+ * allow cross-origin reads, so a browser host fetches it through its own server.
78
+ */
79
+ catalog: () => Promise<unknown>;
80
+ /** css2-compatible stylesheet endpoint (default `https://fonts.googleapis.com/css2`). */
81
+ baseUrl?: string;
82
+ }
83
+ /** Google Fonts (or a css2-compatible mirror): its catalog, loaded face by face through its stylesheets. */
84
+ export declare function createGoogleFontsProvider(options: GoogleFontsProviderOptions): FontProvider & {
85
+ loaded(family: string, weight?: FontWeight, style?: FontStyle): boolean;
86
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor",
3
- "version": "0.176.0",
3
+ "version": "0.177.0",
4
4
  "description": "UI-agnostic rich text and media editing core library",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -23,8 +23,12 @@ what this machine has.
23
23
 
24
24
  ## Working method
25
25
 
26
- 1. **Find fonts.** `list_fonts` with `category`, `weight`, `style` or `query`. Only use families it
27
- returns (or that the user names), and always ship them inside a stack from `resolve_font_stack`.
26
+ 1. **Find fonts.** `list_fonts` with `category`, `weight`, `style`, `tags` or `query`. Only use
27
+ families it returns (or that the user names), and always ship them inside a stack from
28
+ `resolve_font_stack`. When the host offers a web font service (`source: 'web'`, e.g. Google
29
+ Fonts), its catalog is far larger than the installed fonts and listed most popular first:
30
+ narrow it by `category` and by the `tags` the result counts (`display`, `slab-serif`,
31
+ `variable-wdth`…) to find faces with the character you want, rather than taking the top few.
28
32
  2. **Load before rendering.** `load_font` before drawing or capturing text in a web font, so the
29
33
  first frame does not use the fallback. `loaded: false` means the fallback will show.
30
34
  3. **Set up the scale.** `generate_type_hierarchy` for named tokens and ready CSS custom
@@ -62,7 +66,7 @@ what this machine has.
62
66
 
63
67
  | Tool | When to use | Returns |
64
68
  | --- | --- | --- |
65
- | `list_fonts` | Discover families the host offers; filter by `category`, `source`, `weight`, `style`, `query`; `limit` default 50 | `{ fonts: [{ family, category, weights, styles, isVariable?, source }], total, truncated }` |
69
+ | `list_fonts` | Discover families the host offers; filter by `category`, `source`, `weight`, `style`, `tags`, `query`; `limit` default 50 | `{ fonts: [{ family, category, weights, styles, isVariable?, source, tags? }], total, truncated, tags: [{ tag, count }] }` |
66
70
  | `resolve_font_stack` | Turn a family (or stack) into a resilient `font-family` value for its category; also says if the provider lists it | `{ stack, families, category, available }` |
67
71
  | `suggest_type_scale` | Sizes for arbitrary integer `steps` (-12…12) from `baseSize` (px, default 16) and `ratio` (name or number, default major-third) | `{ ratio, baseSize, steps: [{ name, step, size, lineHeight, letterSpacing }] }` |
68
72
  | `generate_type_hierarchy` | Named tokens caption…display on one scale, with CSS custom properties (`prefix`, `selector`) | `{ ratio, ratioName?, baseSize, steps: { body, h1, … }, css }` |