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