@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 +104 -4
- package/dist/index.mjs +100 -4
- package/package.json +2 -1
- package/src/index.js +4 -0
- package/src/schema/cedarsProfileField.js +82 -0
- package/src/utils/computeCedarsProfile.js +19 -0
- package/src/utils/generateFontData.js +20 -3
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
|
-
|
|
8612
|
-
|
|
8613
|
-
font = await parseFont(
|
|
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
|
-
|
|
8616
|
-
|
|
8617
|
-
font = await parseFont(
|
|
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.
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
font = await parseFont(
|
|
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));
|