@liiift-studio/sanity-font-manager 2.8.0 → 2.10.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/index.js CHANGED
@@ -8585,6 +8585,24 @@ var init_generateCssFile = __esm({
8585
8585
  }
8586
8586
  });
8587
8587
 
8588
+ // src/utils/computeCedarsProfile.js
8589
+ function computeCedarsProfile(buffer) {
8590
+ const profile = (0, import_cedars_engine.analyzeFont)(buffer);
8591
+ return {
8592
+ scores: profile.scores,
8593
+ labels: profile.labels,
8594
+ availability: profile.availability,
8595
+ plus: profile.plus,
8596
+ vector: profile.vector
8597
+ };
8598
+ }
8599
+ var import_cedars_engine;
8600
+ var init_computeCedarsProfile = __esm({
8601
+ "src/utils/computeCedarsProfile.js"() {
8602
+ import_cedars_engine = require("@liiift-studio/cedars-engine");
8603
+ }
8604
+ });
8605
+
8588
8606
  // src/utils/generateFontData.js
8589
8607
  function buildFontMetadata(font) {
8590
8608
  const metaData = getFontMetadata(font);
@@ -8607,10 +8625,11 @@ async function generateFontData({ fileInput, url, fontKit, fontId, client, commi
8607
8625
  srcUrl = url;
8608
8626
  }
8609
8627
  let font = fontKit;
8628
+ let fontBuffer = null;
8610
8629
  if (!fontKit || fontKit == null) {
8611
- let buffer = await fetch(srcUrl);
8612
- buffer = await buffer.arrayBuffer();
8613
- font = await parseFont(buffer, `${fontId}.ttf`);
8630
+ const res = await fetch(srcUrl);
8631
+ fontBuffer = await res.arrayBuffer();
8632
+ font = await parseFont(fontBuffer, `${fontId}.ttf`);
8614
8633
  }
8615
8634
  const variableAxes = getVariationAxes(font);
8616
8635
  const namedInstances = getNamedInstances(font);
@@ -8637,6 +8656,16 @@ async function generateFontData({ fileInput, url, fontKit, fontId, client, commi
8637
8656
  if (variableAxes && variableInstances && Object.keys(variableInstances).length > 0) {
8638
8657
  variableFont = true;
8639
8658
  }
8659
+ let cedarsPlus = null;
8660
+ try {
8661
+ if (!fontBuffer) {
8662
+ const res = await fetch(srcUrl);
8663
+ fontBuffer = await res.arrayBuffer();
8664
+ }
8665
+ cedarsPlus = computeCedarsProfile(fontBuffer);
8666
+ } catch (err) {
8667
+ console.warn("CEDARS profile computation failed:", (err == null ? void 0 : err.message) || err);
8668
+ }
8640
8669
  let patch = {
8641
8670
  metrics,
8642
8671
  metaData,
@@ -8645,7 +8674,8 @@ async function generateFontData({ fileInput, url, fontKit, fontId, client, commi
8645
8674
  variableInstances: JSON.stringify(variableInstances),
8646
8675
  glyphCount,
8647
8676
  opentypeFeatures: { chars: opentypeFeatures },
8648
- characterSet: { chars: characterSet }
8677
+ characterSet: { chars: characterSet },
8678
+ ...cedarsPlus ? { cedarsPlus } : {}
8649
8679
  };
8650
8680
  console.log("Font data patch:", Object.keys(patch));
8651
8681
  if (commit) patch = await client.patch(fontId).set(patch).commit({ autoGenerateArrayKeys: true });
@@ -8654,6 +8684,7 @@ async function generateFontData({ fileInput, url, fontKit, fontId, client, commi
8654
8684
  var init_generateFontData = __esm({
8655
8685
  "src/utils/generateFontData.js"() {
8656
8686
  init_parseFont();
8687
+ init_computeCedarsProfile();
8657
8688
  init_fontHelpers();
8658
8689
  }
8659
8690
  });
@@ -12220,6 +12251,9 @@ __export(index_exports, {
12220
12251
  VariableInstanceReferencesInput: () => VariableInstanceReferencesInput,
12221
12252
  addItalicToFontTitle: () => addItalicToFontTitle,
12222
12253
  buildUploadPlan: () => buildUploadPlan,
12254
+ cedarsProfileField: () => cedarsProfileField,
12255
+ computeCedarsProfile: () => computeCedarsProfile,
12256
+ createCedarsProfileField: () => createCedarsProfileField,
12223
12257
  createEmptyPlan: () => createEmptyPlan,
12224
12258
  createFontDecisions: () => createFontDecisions,
12225
12259
  createFontObject: () => createFontObject,
@@ -18454,7 +18488,80 @@ function createStylesField({
18454
18488
  };
18455
18489
  }
18456
18490
 
18491
+ // src/schema/cedarsProfileField.js
18492
+ var AXES = ["contrast", "energy", "details", "axis", "rhythm", "structure"];
18493
+ function titleCase(key) {
18494
+ return key.charAt(0).toUpperCase() + key.slice(1);
18495
+ }
18496
+ var scoreFields = AXES.map((key) => ({
18497
+ title: titleCase(key),
18498
+ name: key,
18499
+ type: "number"
18500
+ }));
18501
+ var labelFields = AXES.map((key) => ({
18502
+ title: titleCase(key),
18503
+ name: key,
18504
+ type: "string"
18505
+ }));
18506
+ var cedarsProfileField = {
18507
+ title: "CEDARS+ Profile",
18508
+ name: "cedarsPlus",
18509
+ type: "object",
18510
+ description: "Auto-computed typographic profile from the font outlines \u2014 Contrast, Energy, Details, Axis, Rhythm, Structure (0\u2013100 each). Read-only; set on upload.",
18511
+ readOnly: true,
18512
+ options: { collapsible: true, collapsed: true },
18513
+ fields: [
18514
+ {
18515
+ title: "Scores",
18516
+ name: "scores",
18517
+ type: "object",
18518
+ options: { collapsible: true, collapsed: false },
18519
+ fields: scoreFields
18520
+ },
18521
+ {
18522
+ title: "Labels",
18523
+ name: "labels",
18524
+ type: "object",
18525
+ options: { collapsible: true, collapsed: true },
18526
+ fields: labelFields
18527
+ },
18528
+ {
18529
+ // Per-axis measurability: false means the axis could not be measured for
18530
+ // this font (a 0 score is a placeholder, not a real low reading).
18531
+ title: "Availability",
18532
+ name: "availability",
18533
+ type: "object",
18534
+ options: { collapsible: true, collapsed: true },
18535
+ fields: AXES.map((key) => ({ title: titleCase(key), name: key, type: "boolean" }))
18536
+ },
18537
+ {
18538
+ title: "+ Descriptors",
18539
+ name: "plus",
18540
+ type: "object",
18541
+ options: { collapsible: true, collapsed: true },
18542
+ fields: [
18543
+ { title: "x-height / cap-height", name: "xHeightRatio", type: "number" },
18544
+ { title: "cap-height / em", name: "capHeightRatio", type: "number" },
18545
+ { title: "Monospaced", name: "isMonospaced", type: "boolean" }
18546
+ ]
18547
+ },
18548
+ {
18549
+ // Normalized [C,E,D,A,R,S] 0..1 vector — kept for similarity search / export.
18550
+ title: "Vector",
18551
+ name: "vector",
18552
+ type: "array",
18553
+ of: [{ type: "number" }]
18554
+ }
18555
+ ]
18556
+ };
18557
+ function createCedarsProfileField({ readOnly = true, group } = {}) {
18558
+ const field2 = { ...cedarsProfileField, readOnly };
18559
+ if (group) field2.group = group;
18560
+ return field2;
18561
+ }
18562
+
18457
18563
  // src/index.js
18564
+ init_computeCedarsProfile();
18458
18565
  init_generateKeywords();
18459
18566
  // Annotate the CommonJS export names for ESM import in node:
18460
18567
  0 && (module.exports = {
@@ -18496,6 +18603,9 @@ init_generateKeywords();
18496
18603
  VariableInstanceReferencesInput,
18497
18604
  addItalicToFontTitle,
18498
18605
  buildUploadPlan,
18606
+ cedarsProfileField,
18607
+ computeCedarsProfile,
18608
+ createCedarsProfileField,
18499
18609
  createEmptyPlan,
18500
18610
  createFontDecisions,
18501
18611
  createFontObject,
package/dist/index.mjs CHANGED
@@ -8589,6 +8589,23 @@ var init_generateCssFile = __esm({
8589
8589
  }
8590
8590
  });
8591
8591
 
8592
+ // src/utils/computeCedarsProfile.js
8593
+ import { analyzeFont } from "@liiift-studio/cedars-engine";
8594
+ function computeCedarsProfile(buffer) {
8595
+ const profile = analyzeFont(buffer);
8596
+ return {
8597
+ scores: profile.scores,
8598
+ labels: profile.labels,
8599
+ availability: profile.availability,
8600
+ plus: profile.plus,
8601
+ vector: profile.vector
8602
+ };
8603
+ }
8604
+ var init_computeCedarsProfile = __esm({
8605
+ "src/utils/computeCedarsProfile.js"() {
8606
+ }
8607
+ });
8608
+
8592
8609
  // src/utils/generateFontData.js
8593
8610
  function buildFontMetadata(font) {
8594
8611
  const metaData = getFontMetadata(font);
@@ -8611,10 +8628,11 @@ async function generateFontData({ fileInput, url, fontKit, fontId, client, commi
8611
8628
  srcUrl = url;
8612
8629
  }
8613
8630
  let font = fontKit;
8631
+ let fontBuffer = null;
8614
8632
  if (!fontKit || fontKit == null) {
8615
- let buffer = await fetch(srcUrl);
8616
- buffer = await buffer.arrayBuffer();
8617
- font = await parseFont(buffer, `${fontId}.ttf`);
8633
+ const res = await fetch(srcUrl);
8634
+ fontBuffer = await res.arrayBuffer();
8635
+ font = await parseFont(fontBuffer, `${fontId}.ttf`);
8618
8636
  }
8619
8637
  const variableAxes = getVariationAxes(font);
8620
8638
  const namedInstances = getNamedInstances(font);
@@ -8641,6 +8659,16 @@ async function generateFontData({ fileInput, url, fontKit, fontId, client, commi
8641
8659
  if (variableAxes && variableInstances && Object.keys(variableInstances).length > 0) {
8642
8660
  variableFont = true;
8643
8661
  }
8662
+ let cedarsPlus = null;
8663
+ try {
8664
+ if (!fontBuffer) {
8665
+ const res = await fetch(srcUrl);
8666
+ fontBuffer = await res.arrayBuffer();
8667
+ }
8668
+ cedarsPlus = computeCedarsProfile(fontBuffer);
8669
+ } catch (err) {
8670
+ console.warn("CEDARS profile computation failed:", (err == null ? void 0 : err.message) || err);
8671
+ }
8644
8672
  let patch = {
8645
8673
  metrics,
8646
8674
  metaData,
@@ -8649,7 +8677,8 @@ async function generateFontData({ fileInput, url, fontKit, fontId, client, commi
8649
8677
  variableInstances: JSON.stringify(variableInstances),
8650
8678
  glyphCount,
8651
8679
  opentypeFeatures: { chars: opentypeFeatures },
8652
- characterSet: { chars: characterSet }
8680
+ characterSet: { chars: characterSet },
8681
+ ...cedarsPlus ? { cedarsPlus } : {}
8653
8682
  };
8654
8683
  console.log("Font data patch:", Object.keys(patch));
8655
8684
  if (commit) patch = await client.patch(fontId).set(patch).commit({ autoGenerateArrayKeys: true });
@@ -8658,6 +8687,7 @@ async function generateFontData({ fileInput, url, fontKit, fontId, client, commi
8658
8687
  var init_generateFontData = __esm({
8659
8688
  "src/utils/generateFontData.js"() {
8660
8689
  init_parseFont();
8690
+ init_computeCedarsProfile();
8661
8691
  init_fontHelpers();
8662
8692
  }
8663
8693
  });
@@ -18353,7 +18383,80 @@ function createStylesField({
18353
18383
  };
18354
18384
  }
18355
18385
 
18386
+ // src/schema/cedarsProfileField.js
18387
+ var AXES = ["contrast", "energy", "details", "axis", "rhythm", "structure"];
18388
+ function titleCase(key) {
18389
+ return key.charAt(0).toUpperCase() + key.slice(1);
18390
+ }
18391
+ var scoreFields = AXES.map((key) => ({
18392
+ title: titleCase(key),
18393
+ name: key,
18394
+ type: "number"
18395
+ }));
18396
+ var labelFields = AXES.map((key) => ({
18397
+ title: titleCase(key),
18398
+ name: key,
18399
+ type: "string"
18400
+ }));
18401
+ var cedarsProfileField = {
18402
+ title: "CEDARS+ Profile",
18403
+ name: "cedarsPlus",
18404
+ type: "object",
18405
+ description: "Auto-computed typographic profile from the font outlines \u2014 Contrast, Energy, Details, Axis, Rhythm, Structure (0\u2013100 each). Read-only; set on upload.",
18406
+ readOnly: true,
18407
+ options: { collapsible: true, collapsed: true },
18408
+ fields: [
18409
+ {
18410
+ title: "Scores",
18411
+ name: "scores",
18412
+ type: "object",
18413
+ options: { collapsible: true, collapsed: false },
18414
+ fields: scoreFields
18415
+ },
18416
+ {
18417
+ title: "Labels",
18418
+ name: "labels",
18419
+ type: "object",
18420
+ options: { collapsible: true, collapsed: true },
18421
+ fields: labelFields
18422
+ },
18423
+ {
18424
+ // Per-axis measurability: false means the axis could not be measured for
18425
+ // this font (a 0 score is a placeholder, not a real low reading).
18426
+ title: "Availability",
18427
+ name: "availability",
18428
+ type: "object",
18429
+ options: { collapsible: true, collapsed: true },
18430
+ fields: AXES.map((key) => ({ title: titleCase(key), name: key, type: "boolean" }))
18431
+ },
18432
+ {
18433
+ title: "+ Descriptors",
18434
+ name: "plus",
18435
+ type: "object",
18436
+ options: { collapsible: true, collapsed: true },
18437
+ fields: [
18438
+ { title: "x-height / cap-height", name: "xHeightRatio", type: "number" },
18439
+ { title: "cap-height / em", name: "capHeightRatio", type: "number" },
18440
+ { title: "Monospaced", name: "isMonospaced", type: "boolean" }
18441
+ ]
18442
+ },
18443
+ {
18444
+ // Normalized [C,E,D,A,R,S] 0..1 vector — kept for similarity search / export.
18445
+ title: "Vector",
18446
+ name: "vector",
18447
+ type: "array",
18448
+ of: [{ type: "number" }]
18449
+ }
18450
+ ]
18451
+ };
18452
+ function createCedarsProfileField({ readOnly = true, group } = {}) {
18453
+ const field2 = { ...cedarsProfileField, readOnly };
18454
+ if (group) field2.group = group;
18455
+ return field2;
18456
+ }
18457
+
18356
18458
  // src/index.js
18459
+ init_computeCedarsProfile();
18357
18460
  init_generateKeywords();
18358
18461
  export {
18359
18462
  BatchUploadFonts,
@@ -18394,6 +18497,9 @@ export {
18394
18497
  VariableInstanceReferencesInput,
18395
18498
  addItalicToFontTitle,
18396
18499
  buildUploadPlan,
18500
+ cedarsProfileField,
18501
+ computeCedarsProfile,
18502
+ createCedarsProfileField,
18397
18503
  createEmptyPlan,
18398
18504
  createFontDecisions,
18399
18505
  createFontObject,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liiift-studio/sanity-font-manager",
3
- "version": "2.8.0",
3
+ "version": "2.10.0",
4
4
  "description": "Sanity Studio plugin — full font management suite with batch upload, format conversion, metadata extraction, CSS generation, collection/pair generation, and script variant support. Supports Sanity v3, v4, and v5.",
5
5
  "license": "MIT",
6
6
  "author": "Liiift Studio",
@@ -50,6 +50,7 @@
50
50
  "link:all": "npm link && cd ../../../sites/darden/sanity && npm link @liiift-studio/sanity-font-manager && cd ../../tdf/sanity && npm link @liiift-studio/sanity-font-manager && cd ../../mckl/cms && npm link @liiift-studio/sanity-font-manager"
51
51
  },
52
52
  "dependencies": {
53
+ "@liiift-studio/cedars-engine": "^2.0.0",
53
54
  "base-64": "^1.0.0",
54
55
  "lib-font": "^3.0.1",
55
56
  "nanoid": "^5.0.0",
package/src/index.js CHANGED
@@ -110,6 +110,10 @@ export { openTypeField, createOpenTypeField } from './schema/openTypeField.js';
110
110
  export { styleCountField } from './schema/styleCountField.js';
111
111
  export { stylisticSetField } from './schema/stylisticSetField.js';
112
112
  export { createStylesField } from './schema/stylesField.js';
113
+ export { cedarsProfileField, createCedarsProfileField } from './schema/cedarsProfileField.js';
114
+
115
+ // CEDARS+ profile computation (auto-tags typefaces on upload)
116
+ export { computeCedarsProfile } from './utils/computeCedarsProfile.js';
113
117
 
114
118
  // Keyword utilities
115
119
  export {
@@ -0,0 +1,91 @@
1
+ // Sanity schema field for the auto-computed CEDARS+ typographic profile.
2
+ // Read-only; populated on font upload by computeCedarsProfile (see utils).
3
+
4
+ // The six CEDARS axes, in canonical order — shared by the scores and labels groups.
5
+ const AXES = ['contrast', 'energy', 'details', 'axis', 'rhythm', 'structure'];
6
+
7
+ // Build a title-cased label from an axis key (e.g. 'contrast' -> 'Contrast').
8
+ function titleCase(key) {
9
+ return key.charAt(0).toUpperCase() + key.slice(1);
10
+ }
11
+
12
+ // Number sub-fields (0..100) for each axis, used by the scores group.
13
+ const scoreFields = AXES.map((key) => ({
14
+ title: titleCase(key),
15
+ name: key,
16
+ type: 'number',
17
+ }));
18
+
19
+ // String sub-fields (human-readable descriptor) for each axis, used by the labels group.
20
+ const labelFields = AXES.map((key) => ({
21
+ title: titleCase(key),
22
+ name: key,
23
+ type: 'string',
24
+ }));
25
+
26
+ // The CEDARS+ profile field definition. Attach to a `font` document schema.
27
+ export const cedarsProfileField = {
28
+ title: 'CEDARS+ Profile',
29
+ name: 'cedarsPlus',
30
+ type: 'object',
31
+ description:
32
+ 'Auto-computed typographic profile from the font outlines — Contrast, Energy, Details, Axis, Rhythm, Structure (0–100 each). Read-only; set on upload.',
33
+ readOnly: true,
34
+ options: { collapsible: true, collapsed: true },
35
+ fields: [
36
+ {
37
+ title: 'Scores',
38
+ name: 'scores',
39
+ type: 'object',
40
+ options: { collapsible: true, collapsed: false },
41
+ fields: scoreFields,
42
+ },
43
+ {
44
+ title: 'Labels',
45
+ name: 'labels',
46
+ type: 'object',
47
+ options: { collapsible: true, collapsed: true },
48
+ fields: labelFields,
49
+ },
50
+ {
51
+ // Per-axis measurability: false means the axis could not be measured for
52
+ // this font (a 0 score is a placeholder, not a real low reading).
53
+ title: 'Availability',
54
+ name: 'availability',
55
+ type: 'object',
56
+ options: { collapsible: true, collapsed: true },
57
+ fields: AXES.map((key) => ({ title: titleCase(key), name: key, type: 'boolean' })),
58
+ },
59
+ {
60
+ title: '+ Descriptors',
61
+ name: 'plus',
62
+ type: 'object',
63
+ options: { collapsible: true, collapsed: true },
64
+ fields: [
65
+ { title: 'x-height / cap-height', name: 'xHeightRatio', type: 'number' },
66
+ { title: 'cap-height / em', name: 'capHeightRatio', type: 'number' },
67
+ { title: 'Monospaced', name: 'isMonospaced', type: 'boolean' },
68
+ ],
69
+ },
70
+ {
71
+ // Normalized [C,E,D,A,R,S] 0..1 vector — kept for similarity search / export.
72
+ title: 'Vector',
73
+ name: 'vector',
74
+ type: 'array',
75
+ of: [{ type: 'number' }],
76
+ },
77
+ ],
78
+ };
79
+
80
+ /**
81
+ * Factory to create a customised CEDARS+ profile field.
82
+ * @param {object} [options]
83
+ * @param {boolean} [options.readOnly=true] - Whether the field is read-only in the Studio.
84
+ * @param {string} [options.group] - Optional field group to assign the field to.
85
+ * @returns {object} Sanity field definition
86
+ */
87
+ export function createCedarsProfileField({ readOnly = true, group } = {}) {
88
+ const field = { ...cedarsProfileField, readOnly };
89
+ if (group) field.group = group;
90
+ return field;
91
+ }
@@ -0,0 +1,20 @@
1
+ // Computes a font's CEDARS+ profile from its raw bytes, shaped for the Sanity
2
+ // `cedarsPlus` field. Thin wrapper around @liiift-studio/cedars-engine.
3
+ import { analyzeFont } from '@liiift-studio/cedars-engine';
4
+
5
+ /**
6
+ * Compute the CEDARS+ profile (Contrast, Energy, Details, Axis, Rhythm, Structure)
7
+ * for a font from its raw outline geometry.
8
+ * @param {ArrayBuffer|Uint8Array} buffer - The font file bytes (OTF/TTF/WOFF; not WOFF2).
9
+ * @returns {{ scores: object, labels: object, availability: object, plus: object, vector: number[] }}
10
+ */
11
+ export function computeCedarsProfile(buffer) {
12
+ const profile = analyzeFont(buffer);
13
+ return {
14
+ scores: profile.scores,
15
+ labels: profile.labels,
16
+ availability: profile.availability,
17
+ plus: profile.plus,
18
+ vector: profile.vector,
19
+ };
20
+ }
@@ -1,6 +1,7 @@
1
1
  // Extracts metadata, metrics, glyph count, OpenType features, and variable axes from a font and optionally patches the Sanity font document
2
2
 
3
3
  import { parseFont } from './parseFont';
4
+ import { computeCedarsProfile } from './computeCedarsProfile';
4
5
  import {
5
6
  getFontMetadata,
6
7
  getFontMetrics,
@@ -43,10 +44,12 @@ export default async function generateFontData({ fileInput, url, fontKit, fontId
43
44
  }
44
45
 
45
46
  let font = fontKit;
47
+ // Hoisted so the CEDARS+ step below can reuse the raw bytes when we fetched them.
48
+ let fontBuffer = null;
46
49
  if (!fontKit || fontKit == null) {
47
- let buffer = await fetch(srcUrl);
48
- buffer = await buffer.arrayBuffer();
49
- font = await parseFont(buffer, `${fontId}.ttf`);
50
+ const res = await fetch(srcUrl);
51
+ fontBuffer = await res.arrayBuffer();
52
+ font = await parseFont(fontBuffer, `${fontId}.ttf`);
50
53
  }
51
54
 
52
55
  const variableAxes = getVariationAxes(font);
@@ -81,6 +84,19 @@ export default async function generateFontData({ fileInput, url, fontKit, fontId
81
84
  variableFont = true;
82
85
  }
83
86
 
87
+ // CEDARS+ profile — best-effort enrichment. Wrapped so a failure here can never
88
+ // block the font upload; the raw bytes are fetched only if we don't already have them.
89
+ let cedarsPlus = null;
90
+ try {
91
+ if (!fontBuffer) {
92
+ const res = await fetch(srcUrl);
93
+ fontBuffer = await res.arrayBuffer();
94
+ }
95
+ cedarsPlus = computeCedarsProfile(fontBuffer);
96
+ } catch (err) {
97
+ console.warn('CEDARS profile computation failed:', err?.message || err);
98
+ }
99
+
84
100
  let patch = {
85
101
  metrics: metrics,
86
102
  metaData: metaData,
@@ -90,6 +106,7 @@ export default async function generateFontData({ fileInput, url, fontKit, fontId
90
106
  glyphCount: glyphCount,
91
107
  opentypeFeatures: { chars: opentypeFeatures },
92
108
  characterSet: { chars: characterSet },
109
+ ...(cedarsPlus ? { cedarsPlus } : {}),
93
110
  };
94
111
 
95
112
  console.log('Font data patch:', Object.keys(patch));