@liiift-studio/sanity-font-manager 2.8.0 → 2.9.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,23 @@ 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
+ plus: profile.plus,
8595
+ vector: profile.vector
8596
+ };
8597
+ }
8598
+ var import_cedars_engine;
8599
+ var init_computeCedarsProfile = __esm({
8600
+ "src/utils/computeCedarsProfile.js"() {
8601
+ import_cedars_engine = require("@liiift-studio/cedars-engine");
8602
+ }
8603
+ });
8604
+
8588
8605
  // src/utils/generateFontData.js
8589
8606
  function buildFontMetadata(font) {
8590
8607
  const metaData = getFontMetadata(font);
@@ -8607,10 +8624,11 @@ async function generateFontData({ fileInput, url, fontKit, fontId, client, commi
8607
8624
  srcUrl = url;
8608
8625
  }
8609
8626
  let font = fontKit;
8627
+ let fontBuffer = null;
8610
8628
  if (!fontKit || fontKit == null) {
8611
- let buffer = await fetch(srcUrl);
8612
- buffer = await buffer.arrayBuffer();
8613
- font = await parseFont(buffer, `${fontId}.ttf`);
8629
+ const res = await fetch(srcUrl);
8630
+ fontBuffer = await res.arrayBuffer();
8631
+ font = await parseFont(fontBuffer, `${fontId}.ttf`);
8614
8632
  }
8615
8633
  const variableAxes = getVariationAxes(font);
8616
8634
  const namedInstances = getNamedInstances(font);
@@ -8637,6 +8655,16 @@ async function generateFontData({ fileInput, url, fontKit, fontId, client, commi
8637
8655
  if (variableAxes && variableInstances && Object.keys(variableInstances).length > 0) {
8638
8656
  variableFont = true;
8639
8657
  }
8658
+ let cedarsPlus = null;
8659
+ try {
8660
+ if (!fontBuffer) {
8661
+ const res = await fetch(srcUrl);
8662
+ fontBuffer = await res.arrayBuffer();
8663
+ }
8664
+ cedarsPlus = computeCedarsProfile(fontBuffer);
8665
+ } catch (err) {
8666
+ console.warn("CEDARS profile computation failed:", (err == null ? void 0 : err.message) || err);
8667
+ }
8640
8668
  let patch = {
8641
8669
  metrics,
8642
8670
  metaData,
@@ -8645,7 +8673,8 @@ async function generateFontData({ fileInput, url, fontKit, fontId, client, commi
8645
8673
  variableInstances: JSON.stringify(variableInstances),
8646
8674
  glyphCount,
8647
8675
  opentypeFeatures: { chars: opentypeFeatures },
8648
- characterSet: { chars: characterSet }
8676
+ characterSet: { chars: characterSet },
8677
+ ...cedarsPlus ? { cedarsPlus } : {}
8649
8678
  };
8650
8679
  console.log("Font data patch:", Object.keys(patch));
8651
8680
  if (commit) patch = await client.patch(fontId).set(patch).commit({ autoGenerateArrayKeys: true });
@@ -8654,6 +8683,7 @@ async function generateFontData({ fileInput, url, fontKit, fontId, client, commi
8654
8683
  var init_generateFontData = __esm({
8655
8684
  "src/utils/generateFontData.js"() {
8656
8685
  init_parseFont();
8686
+ init_computeCedarsProfile();
8657
8687
  init_fontHelpers();
8658
8688
  }
8659
8689
  });
@@ -12220,6 +12250,9 @@ __export(index_exports, {
12220
12250
  VariableInstanceReferencesInput: () => VariableInstanceReferencesInput,
12221
12251
  addItalicToFontTitle: () => addItalicToFontTitle,
12222
12252
  buildUploadPlan: () => buildUploadPlan,
12253
+ cedarsProfileField: () => cedarsProfileField,
12254
+ computeCedarsProfile: () => computeCedarsProfile,
12255
+ createCedarsProfileField: () => createCedarsProfileField,
12223
12256
  createEmptyPlan: () => createEmptyPlan,
12224
12257
  createFontDecisions: () => createFontDecisions,
12225
12258
  createFontObject: () => createFontObject,
@@ -18454,7 +18487,71 @@ function createStylesField({
18454
18487
  };
18455
18488
  }
18456
18489
 
18490
+ // src/schema/cedarsProfileField.js
18491
+ var AXES = ["contrast", "energy", "details", "axis", "rhythm", "structure"];
18492
+ function titleCase(key) {
18493
+ return key.charAt(0).toUpperCase() + key.slice(1);
18494
+ }
18495
+ var scoreFields = AXES.map((key) => ({
18496
+ title: titleCase(key),
18497
+ name: key,
18498
+ type: "number"
18499
+ }));
18500
+ var labelFields = AXES.map((key) => ({
18501
+ title: titleCase(key),
18502
+ name: key,
18503
+ type: "string"
18504
+ }));
18505
+ var cedarsProfileField = {
18506
+ title: "CEDARS+ Profile",
18507
+ name: "cedarsPlus",
18508
+ type: "object",
18509
+ description: "Auto-computed typographic profile from the font outlines \u2014 Contrast, Energy, Details, Axis, Rhythm, Structure (0\u2013100 each). Read-only; set on upload.",
18510
+ readOnly: true,
18511
+ options: { collapsible: true, collapsed: true },
18512
+ fields: [
18513
+ {
18514
+ title: "Scores",
18515
+ name: "scores",
18516
+ type: "object",
18517
+ options: { collapsible: true, collapsed: false },
18518
+ fields: scoreFields
18519
+ },
18520
+ {
18521
+ title: "Labels",
18522
+ name: "labels",
18523
+ type: "object",
18524
+ options: { collapsible: true, collapsed: true },
18525
+ fields: labelFields
18526
+ },
18527
+ {
18528
+ title: "+ Descriptors",
18529
+ name: "plus",
18530
+ type: "object",
18531
+ options: { collapsible: true, collapsed: true },
18532
+ fields: [
18533
+ { title: "x-height / cap-height", name: "xHeightRatio", type: "number" },
18534
+ { title: "cap-height / em", name: "capHeightRatio", type: "number" },
18535
+ { title: "Monospaced", name: "isMonospaced", type: "boolean" }
18536
+ ]
18537
+ },
18538
+ {
18539
+ // Normalized [C,E,D,A,R,S] 0..1 vector — kept for similarity search / export.
18540
+ title: "Vector",
18541
+ name: "vector",
18542
+ type: "array",
18543
+ of: [{ type: "number" }]
18544
+ }
18545
+ ]
18546
+ };
18547
+ function createCedarsProfileField({ readOnly = true, group } = {}) {
18548
+ const field2 = { ...cedarsProfileField, readOnly };
18549
+ if (group) field2.group = group;
18550
+ return field2;
18551
+ }
18552
+
18457
18553
  // src/index.js
18554
+ init_computeCedarsProfile();
18458
18555
  init_generateKeywords();
18459
18556
  // Annotate the CommonJS export names for ESM import in node:
18460
18557
  0 && (module.exports = {
@@ -18496,6 +18593,9 @@ init_generateKeywords();
18496
18593
  VariableInstanceReferencesInput,
18497
18594
  addItalicToFontTitle,
18498
18595
  buildUploadPlan,
18596
+ cedarsProfileField,
18597
+ computeCedarsProfile,
18598
+ createCedarsProfileField,
18499
18599
  createEmptyPlan,
18500
18600
  createFontDecisions,
18501
18601
  createFontObject,
package/dist/index.mjs CHANGED
@@ -8589,6 +8589,22 @@ 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
+ plus: profile.plus,
8600
+ vector: profile.vector
8601
+ };
8602
+ }
8603
+ var init_computeCedarsProfile = __esm({
8604
+ "src/utils/computeCedarsProfile.js"() {
8605
+ }
8606
+ });
8607
+
8592
8608
  // src/utils/generateFontData.js
8593
8609
  function buildFontMetadata(font) {
8594
8610
  const metaData = getFontMetadata(font);
@@ -8611,10 +8627,11 @@ async function generateFontData({ fileInput, url, fontKit, fontId, client, commi
8611
8627
  srcUrl = url;
8612
8628
  }
8613
8629
  let font = fontKit;
8630
+ let fontBuffer = null;
8614
8631
  if (!fontKit || fontKit == null) {
8615
- let buffer = await fetch(srcUrl);
8616
- buffer = await buffer.arrayBuffer();
8617
- font = await parseFont(buffer, `${fontId}.ttf`);
8632
+ const res = await fetch(srcUrl);
8633
+ fontBuffer = await res.arrayBuffer();
8634
+ font = await parseFont(fontBuffer, `${fontId}.ttf`);
8618
8635
  }
8619
8636
  const variableAxes = getVariationAxes(font);
8620
8637
  const namedInstances = getNamedInstances(font);
@@ -8641,6 +8658,16 @@ async function generateFontData({ fileInput, url, fontKit, fontId, client, commi
8641
8658
  if (variableAxes && variableInstances && Object.keys(variableInstances).length > 0) {
8642
8659
  variableFont = true;
8643
8660
  }
8661
+ let cedarsPlus = null;
8662
+ try {
8663
+ if (!fontBuffer) {
8664
+ const res = await fetch(srcUrl);
8665
+ fontBuffer = await res.arrayBuffer();
8666
+ }
8667
+ cedarsPlus = computeCedarsProfile(fontBuffer);
8668
+ } catch (err) {
8669
+ console.warn("CEDARS profile computation failed:", (err == null ? void 0 : err.message) || err);
8670
+ }
8644
8671
  let patch = {
8645
8672
  metrics,
8646
8673
  metaData,
@@ -8649,7 +8676,8 @@ async function generateFontData({ fileInput, url, fontKit, fontId, client, commi
8649
8676
  variableInstances: JSON.stringify(variableInstances),
8650
8677
  glyphCount,
8651
8678
  opentypeFeatures: { chars: opentypeFeatures },
8652
- characterSet: { chars: characterSet }
8679
+ characterSet: { chars: characterSet },
8680
+ ...cedarsPlus ? { cedarsPlus } : {}
8653
8681
  };
8654
8682
  console.log("Font data patch:", Object.keys(patch));
8655
8683
  if (commit) patch = await client.patch(fontId).set(patch).commit({ autoGenerateArrayKeys: true });
@@ -8658,6 +8686,7 @@ async function generateFontData({ fileInput, url, fontKit, fontId, client, commi
8658
8686
  var init_generateFontData = __esm({
8659
8687
  "src/utils/generateFontData.js"() {
8660
8688
  init_parseFont();
8689
+ init_computeCedarsProfile();
8661
8690
  init_fontHelpers();
8662
8691
  }
8663
8692
  });
@@ -18353,7 +18382,71 @@ function createStylesField({
18353
18382
  };
18354
18383
  }
18355
18384
 
18385
+ // src/schema/cedarsProfileField.js
18386
+ var AXES = ["contrast", "energy", "details", "axis", "rhythm", "structure"];
18387
+ function titleCase(key) {
18388
+ return key.charAt(0).toUpperCase() + key.slice(1);
18389
+ }
18390
+ var scoreFields = AXES.map((key) => ({
18391
+ title: titleCase(key),
18392
+ name: key,
18393
+ type: "number"
18394
+ }));
18395
+ var labelFields = AXES.map((key) => ({
18396
+ title: titleCase(key),
18397
+ name: key,
18398
+ type: "string"
18399
+ }));
18400
+ var cedarsProfileField = {
18401
+ title: "CEDARS+ Profile",
18402
+ name: "cedarsPlus",
18403
+ type: "object",
18404
+ description: "Auto-computed typographic profile from the font outlines \u2014 Contrast, Energy, Details, Axis, Rhythm, Structure (0\u2013100 each). Read-only; set on upload.",
18405
+ readOnly: true,
18406
+ options: { collapsible: true, collapsed: true },
18407
+ fields: [
18408
+ {
18409
+ title: "Scores",
18410
+ name: "scores",
18411
+ type: "object",
18412
+ options: { collapsible: true, collapsed: false },
18413
+ fields: scoreFields
18414
+ },
18415
+ {
18416
+ title: "Labels",
18417
+ name: "labels",
18418
+ type: "object",
18419
+ options: { collapsible: true, collapsed: true },
18420
+ fields: labelFields
18421
+ },
18422
+ {
18423
+ title: "+ Descriptors",
18424
+ name: "plus",
18425
+ type: "object",
18426
+ options: { collapsible: true, collapsed: true },
18427
+ fields: [
18428
+ { title: "x-height / cap-height", name: "xHeightRatio", type: "number" },
18429
+ { title: "cap-height / em", name: "capHeightRatio", type: "number" },
18430
+ { title: "Monospaced", name: "isMonospaced", type: "boolean" }
18431
+ ]
18432
+ },
18433
+ {
18434
+ // Normalized [C,E,D,A,R,S] 0..1 vector — kept for similarity search / export.
18435
+ title: "Vector",
18436
+ name: "vector",
18437
+ type: "array",
18438
+ of: [{ type: "number" }]
18439
+ }
18440
+ ]
18441
+ };
18442
+ function createCedarsProfileField({ readOnly = true, group } = {}) {
18443
+ const field2 = { ...cedarsProfileField, readOnly };
18444
+ if (group) field2.group = group;
18445
+ return field2;
18446
+ }
18447
+
18356
18448
  // src/index.js
18449
+ init_computeCedarsProfile();
18357
18450
  init_generateKeywords();
18358
18451
  export {
18359
18452
  BatchUploadFonts,
@@ -18394,6 +18487,9 @@ export {
18394
18487
  VariableInstanceReferencesInput,
18395
18488
  addItalicToFontTitle,
18396
18489
  buildUploadPlan,
18490
+ cedarsProfileField,
18491
+ computeCedarsProfile,
18492
+ createCedarsProfileField,
18397
18493
  createEmptyPlan,
18398
18494
  createFontDecisions,
18399
18495
  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.9.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": "^1.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,82 @@
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
+ title: '+ Descriptors',
52
+ name: 'plus',
53
+ type: 'object',
54
+ options: { collapsible: true, collapsed: true },
55
+ fields: [
56
+ { title: 'x-height / cap-height', name: 'xHeightRatio', type: 'number' },
57
+ { title: 'cap-height / em', name: 'capHeightRatio', type: 'number' },
58
+ { title: 'Monospaced', name: 'isMonospaced', type: 'boolean' },
59
+ ],
60
+ },
61
+ {
62
+ // Normalized [C,E,D,A,R,S] 0..1 vector — kept for similarity search / export.
63
+ title: 'Vector',
64
+ name: 'vector',
65
+ type: 'array',
66
+ of: [{ type: 'number' }],
67
+ },
68
+ ],
69
+ };
70
+
71
+ /**
72
+ * Factory to create a customised CEDARS+ profile field.
73
+ * @param {object} [options]
74
+ * @param {boolean} [options.readOnly=true] - Whether the field is read-only in the Studio.
75
+ * @param {string} [options.group] - Optional field group to assign the field to.
76
+ * @returns {object} Sanity field definition
77
+ */
78
+ export function createCedarsProfileField({ readOnly = true, group } = {}) {
79
+ const field = { ...cedarsProfileField, readOnly };
80
+ if (group) field.group = group;
81
+ return field;
82
+ }
@@ -0,0 +1,19 @@
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} buffer - The font file bytes (OTF/TTF/WOFF).
9
+ * @returns {{ scores: object, labels: 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
+ plus: profile.plus,
17
+ vector: profile.vector,
18
+ };
19
+ }
@@ -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));