@liiift-studio/sanity-font-manager 2.7.0 → 2.7.1

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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +695 -437
  3. package/dist/index.js +1634 -17839
  4. package/dist/index.mjs +1551 -17745
  5. package/package.json +83 -83
  6. package/src/components/BatchUploadFonts.jsx +655 -655
  7. package/src/components/BulkActions.jsx +99 -99
  8. package/src/components/ExistingDocumentResolver.jsx +152 -152
  9. package/src/components/FontReviewCard.jsx +455 -455
  10. package/src/components/FontScriptUploaderComponent.jsx +463 -463
  11. package/src/components/GenerateCollectionsPairsComponent.jsx +259 -259
  12. package/src/components/KeyValueInput.jsx +95 -95
  13. package/src/components/KeyValueReferenceInput.jsx +267 -267
  14. package/src/components/NestedObjectArraySelector.jsx +146 -146
  15. package/src/components/PriceInput.jsx +26 -26
  16. package/src/components/PrimaryCollectionGeneratorTypeface.jsx +116 -116
  17. package/src/components/RegenerateSubfamiliesComponent.jsx +185 -185
  18. package/src/components/SetOTF.jsx +87 -87
  19. package/src/components/SingleUploaderTool.jsx +674 -674
  20. package/src/components/StatusDisplay.jsx +26 -26
  21. package/src/components/StyleCountInput.jsx +16 -16
  22. package/src/components/UpdateScriptsComponent.jsx +76 -76
  23. package/src/components/UploadButton.jsx +43 -43
  24. package/src/components/UploadModal.jsx +309 -309
  25. package/src/components/UploadScriptsComponent.jsx +539 -539
  26. package/src/components/UploadStep1Settings.jsx +272 -272
  27. package/src/components/UploadStep2Review.jsx +478 -478
  28. package/src/components/UploadStep3Execute.jsx +234 -234
  29. package/src/components/UploadStep3bInstances.jsx +396 -396
  30. package/src/components/UploadSummary.jsx +196 -196
  31. package/src/components/VariableInstanceReferencesInput.jsx +190 -190
  32. package/src/hooks/useNestedObjects.js +92 -92
  33. package/src/hooks/useSanityClient.js +9 -9
  34. package/src/index.js +120 -120
  35. package/src/schema/openTypeField.js +1995 -1995
  36. package/src/schema/styleCountField.js +12 -12
  37. package/src/schema/stylesField.js +302 -302
  38. package/src/schema/stylisticSetField.js +301 -301
  39. package/src/utils/buildUploadPlan.js +326 -326
  40. package/src/utils/executeUploadPlan.js +430 -430
  41. package/src/utils/executionReducer.js +56 -56
  42. package/src/utils/fontHelpers.js +281 -281
  43. package/src/utils/generateCssFile.js +207 -207
  44. package/src/utils/generateFontData.js +98 -98
  45. package/src/utils/generateFontFile.js +38 -38
  46. package/src/utils/generateKeywords.js +185 -185
  47. package/src/utils/generateSubset.js +45 -45
  48. package/src/utils/getEmptyFontKit.js +101 -101
  49. package/src/utils/parseFont.js +56 -56
  50. package/src/utils/parseVariableFontInstances.js +301 -301
  51. package/src/utils/planReducer.js +531 -531
  52. package/src/utils/planTypes.js +183 -183
  53. package/src/utils/processFontFiles.js +530 -530
  54. package/src/utils/regenerateFontData.js +146 -146
  55. package/src/utils/resolveExistingFont.js +87 -87
  56. package/src/utils/retitleFontEntries.js +154 -154
  57. package/src/utils/sanitizeForSanityId.js +65 -65
  58. package/src/utils/setupDecompressors.js +27 -27
  59. package/src/utils/updateFontPrices.js +94 -94
  60. package/src/utils/updateTypefaceDocument.js +162 -162
  61. package/src/utils/uploadFontFiles.js +405 -405
  62. package/src/utils/utils.js +24 -24
@@ -1,207 +1,207 @@
1
- // Builds a @font-face CSS file from a WOFF2 blob — URL or base64 src, variable font axis descriptors, metric-tuned fallback @font-face for CLS reduction
2
-
3
- import base64 from 'base-64';
4
- import { parseFont } from './parseFont';
5
- import { getVariationAxes, getFontMetrics, getFamilyClass, escapeCssFontName } from './fontHelpers';
6
-
7
- function _arrayBufferToBase64(buffer) {
8
- var binary = '';
9
- var bytes = new Uint8Array(buffer);
10
- var len = bytes.byteLength;
11
- for (var i = 0; i < len; i++) {
12
- binary += String.fromCharCode(bytes[i]);
13
- }
14
- return base64.encode(binary);
15
- }
16
-
17
- /**
18
- * Builds CSS @font-face axis descriptors from a variation axis map.
19
- * Accepts a pre-built axis map (from getVariationAxes) rather than a raw font object,
20
- * so callers can share a single parse result.
21
- *
22
- * @param {object|null} axisMap - Keyed axis map { wght: { min, max, default }, ... } or null
23
- * @returns {{ descriptors: string, skipped: string[] }}
24
- */
25
- export function buildVFDescriptors(axisMap) {
26
- const cssAxes = {};
27
- const skipped = [];
28
-
29
- if (!axisMap) return { descriptors: '', skipped: [] };
30
-
31
- try {
32
- for (const [tag, axis] of Object.entries(axisMap)) {
33
- const lo = Math.min(axis.min, axis.max);
34
- const hi = Math.max(axis.min, axis.max);
35
-
36
- // Skip degenerate axes — no actual range
37
- if (lo === hi) { skipped.push(tag); continue; }
38
-
39
- if (tag === 'wght') {
40
- cssAxes['font-weight'] = `${lo} ${hi}`;
41
- } else if (tag === 'wdth') {
42
- // Clamp to CSS font-stretch range (50-200%)
43
- cssAxes['font-stretch'] = `${Math.max(50, lo)}% ${Math.min(200, hi)}%`;
44
- } else if (tag === 'slnt') {
45
- // OpenType slnt is counter-clockwise positive; CSS oblique is clockwise positive
46
- // Negate values and ensure ascending order
47
- cssAxes['font-style'] = `oblique ${-hi}deg ${-lo}deg`;
48
- } else if (tag === 'ital' && !cssAxes['font-style']) {
49
- // ital: only note if font has italic range; slnt takes priority
50
- if (hi > 0) cssAxes['font-style'] = 'italic';
51
- else skipped.push(tag);
52
- } else {
53
- // opsz, GRAD, XTRA, XHGT and all other custom axes have no CSS @font-face descriptor
54
- skipped.push(tag);
55
- }
56
- }
57
- } catch (_) {
58
- // axes unreadable — no descriptors
59
- }
60
-
61
- const descriptors = Object.entries(cssAxes).map(([k, v]) => `${k}:${v}`).join(';') + (Object.keys(cssAxes).length ? ';' : '');
62
- return { descriptors, skipped };
63
- }
64
-
65
- // Cross-platform fallback font stacks by category.
66
- const FALLBACK_STACKS = {
67
- 'sans-serif': "local('Arial'), local('Helvetica Neue'), local('Roboto'), local('Liberation Sans')",
68
- 'serif': "local('Georgia'), local('Times New Roman'), local('Times')",
69
- 'monospace': "local('Courier New'), local('Courier'), local('Menlo'), local('Monaco')",
70
- 'default': "local('Arial'), local('Helvetica Neue'), local('Roboto'), local('Liberation Sans')",
71
- };
72
-
73
- // OS/2 sFamilyClass high byte → FALLBACK_STACKS key.
74
- const FAMILY_CLASS_MAP = {
75
- 1: 'serif', 2: 'serif', 3: 'serif', 4: 'serif', 5: 'serif', 7: 'serif',
76
- 8: 'sans-serif',
77
- };
78
-
79
- // Darden Studio fonts have sFamilyClass: 0 in their OS/2 table,
80
- // so name-based matching is used as the primary signal.
81
- const SERIF_NAMES = /jubilat|corundum|dapifer|birra|daith/i;
82
- const SANS_NAMES = /halyard|gamay|omnes|kit/i;
83
-
84
- /**
85
- * Detects font category from the font name first, then OS/2 sFamilyClass as fallback.
86
- * @param {object} font - lib-font Font instance
87
- * @param {string} fontName
88
- * @returns {string}
89
- */
90
- function detectFontCategory(font, fontName) {
91
- if (fontName && SERIF_NAMES.test(fontName)) return 'serif';
92
- if (fontName && SANS_NAMES.test(fontName)) return 'sans-serif';
93
- try {
94
- const familyClass = getFamilyClass(font);
95
- const highByte = (familyClass >> 8) & 0xFF;
96
- return FAMILY_CLASS_MAP[highByte] ?? 'default';
97
- } catch {
98
- return 'default';
99
- }
100
- }
101
-
102
- /**
103
- * Extracts metric override percentages and detects the category fallback stack.
104
- * Accepts a pre-parsed font to avoid double-parsing.
105
- * @param {object} font - lib-font Font instance
106
- * @param {string} fontName
107
- * @returns {{ fallbackSrc: string, ascentOverride: string, descentOverride: string, lineGapOverride: string }}
108
- */
109
- function calcFallbackData(font, fontName) {
110
- try {
111
- const metrics = getFontMetrics(font);
112
- const upm = metrics.unitsPerEm;
113
- const category = detectFontCategory(font, fontName);
114
- return {
115
- fallbackSrc: FALLBACK_STACKS[category],
116
- ascentOverride: `${(metrics.ascender / upm * 100).toFixed(2)}%`,
117
- descentOverride: `${(Math.abs(metrics.descender) / upm * 100).toFixed(2)}%`,
118
- lineGapOverride: `${(metrics.lineGap / upm * 100).toFixed(2)}%`,
119
- };
120
- } catch (err) {
121
- console.error('Failed to extract fallback font data:', err);
122
- return {
123
- fallbackSrc: FALLBACK_STACKS['default'],
124
- ascentOverride: '100%',
125
- descentOverride: '0%',
126
- lineGapOverride: '0%',
127
- };
128
- }
129
- }
130
-
131
- export default async function generateCssFile({
132
- woff2File,
133
- fileInput,
134
- language = null,
135
- fileName,
136
- fontName,
137
- variableFont,
138
- weight,
139
- style = 'Normal',
140
- client,
141
- }) {
142
- try {
143
- // Read the file once; reuse the same buffer for base64 and font analysis
144
- let arrayBuffer = await woff2File.arrayBuffer();
145
- let b64 = _arrayBufferToBase64(arrayBuffer);
146
-
147
- // Parse once — share result between axis descriptors and fallback metrics
148
- let font = await parseFont(arrayBuffer, fileName + '.woff2');
149
- let { fallbackSrc, ascentOverride, descentOverride, lineGapOverride } = calcFallbackData(font, fontName);
150
-
151
- // Escape font name for CSS injection prevention
152
- const safeFontName = escapeCssFontName(fontName);
153
-
154
- let cssString;
155
- if (variableFont) {
156
- const axisMap = getVariationAxes(font);
157
- let { descriptors, skipped } = buildVFDescriptors(axisMap);
158
- let skipComment = skipped.length
159
- ? `/* axes present but have no @font-face descriptor: ${skipped.join(', ')}` +
160
- (skipped.includes('opsz') ? ' — add font-optical-sizing:auto to your element CSS' : '') +
161
- ' */'
162
- : '';
163
- cssString = `${skipComment}@font-face{font-family:'${safeFontName}';src:url(data:application/font-woff2;charset=utf-8;base64,${b64})format('woff2');${descriptors}font-display:swap;}`;
164
- } else {
165
- let fontStyle = style === 'Italic' ? 'italic' : 'normal';
166
- cssString = `@font-face{font-family:'${safeFontName}';src:url(data:application/font-woff2;charset=utf-8;base64,${b64})format('woff2');font-weight:${weight};font-style:${fontStyle};font-display:swap;}`;
167
- }
168
-
169
- // Fallback @font-face: tunes a system font to match the custom font's metrics for CLS reduction
170
- let fallbackCssString = `@font-face{font-family:'${safeFontName} Fallback';src:${fallbackSrc};ascent-override:${ascentOverride};descent-override:${descentOverride};line-gap-override:${lineGapOverride};}`;
171
-
172
- // Upload as a text buffer (no Buffer polyfill needed — TextEncoder is native)
173
- const cssBytes = new TextEncoder().encode(cssString + fallbackCssString);
174
- let doc = await client.assets.upload('file', new Blob([cssBytes]), { filename: fileName + '.css' });
175
-
176
- let newFileInput = language == null ?
177
- {
178
- ...fileInput,
179
- css: {
180
- _type: 'file',
181
- asset: {
182
- _type: 'reference',
183
- _ref: doc._id,
184
- },
185
- },
186
- }
187
- :
188
- {
189
- ...fileInput,
190
- [language]: {
191
- ...fileInput[language],
192
- css: {
193
- _type: 'file',
194
- asset: {
195
- _type: 'reference',
196
- _ref: doc._id,
197
- },
198
- },
199
- },
200
- };
201
-
202
- return newFileInput;
203
- } catch (err) {
204
- console.error(err);
205
- throw err;
206
- }
207
- }
1
+ // Builds a @font-face CSS file from a WOFF2 blob — URL or base64 src, variable font axis descriptors, metric-tuned fallback @font-face for CLS reduction
2
+
3
+ import base64 from 'base-64';
4
+ import { parseFont } from './parseFont';
5
+ import { getVariationAxes, getFontMetrics, getFamilyClass, escapeCssFontName } from './fontHelpers';
6
+
7
+ function _arrayBufferToBase64(buffer) {
8
+ var binary = '';
9
+ var bytes = new Uint8Array(buffer);
10
+ var len = bytes.byteLength;
11
+ for (var i = 0; i < len; i++) {
12
+ binary += String.fromCharCode(bytes[i]);
13
+ }
14
+ return base64.encode(binary);
15
+ }
16
+
17
+ /**
18
+ * Builds CSS @font-face axis descriptors from a variation axis map.
19
+ * Accepts a pre-built axis map (from getVariationAxes) rather than a raw font object,
20
+ * so callers can share a single parse result.
21
+ *
22
+ * @param {object|null} axisMap - Keyed axis map { wght: { min, max, default }, ... } or null
23
+ * @returns {{ descriptors: string, skipped: string[] }}
24
+ */
25
+ export function buildVFDescriptors(axisMap) {
26
+ const cssAxes = {};
27
+ const skipped = [];
28
+
29
+ if (!axisMap) return { descriptors: '', skipped: [] };
30
+
31
+ try {
32
+ for (const [tag, axis] of Object.entries(axisMap)) {
33
+ const lo = Math.min(axis.min, axis.max);
34
+ const hi = Math.max(axis.min, axis.max);
35
+
36
+ // Skip degenerate axes — no actual range
37
+ if (lo === hi) { skipped.push(tag); continue; }
38
+
39
+ if (tag === 'wght') {
40
+ cssAxes['font-weight'] = `${lo} ${hi}`;
41
+ } else if (tag === 'wdth') {
42
+ // Clamp to CSS font-stretch range (50-200%)
43
+ cssAxes['font-stretch'] = `${Math.max(50, lo)}% ${Math.min(200, hi)}%`;
44
+ } else if (tag === 'slnt') {
45
+ // OpenType slnt is counter-clockwise positive; CSS oblique is clockwise positive
46
+ // Negate values and ensure ascending order
47
+ cssAxes['font-style'] = `oblique ${-hi}deg ${-lo}deg`;
48
+ } else if (tag === 'ital' && !cssAxes['font-style']) {
49
+ // ital: only note if font has italic range; slnt takes priority
50
+ if (hi > 0) cssAxes['font-style'] = 'italic';
51
+ else skipped.push(tag);
52
+ } else {
53
+ // opsz, GRAD, XTRA, XHGT and all other custom axes have no CSS @font-face descriptor
54
+ skipped.push(tag);
55
+ }
56
+ }
57
+ } catch (_) {
58
+ // axes unreadable — no descriptors
59
+ }
60
+
61
+ const descriptors = Object.entries(cssAxes).map(([k, v]) => `${k}:${v}`).join(';') + (Object.keys(cssAxes).length ? ';' : '');
62
+ return { descriptors, skipped };
63
+ }
64
+
65
+ // Cross-platform fallback font stacks by category.
66
+ const FALLBACK_STACKS = {
67
+ 'sans-serif': "local('Arial'), local('Helvetica Neue'), local('Roboto'), local('Liberation Sans')",
68
+ 'serif': "local('Georgia'), local('Times New Roman'), local('Times')",
69
+ 'monospace': "local('Courier New'), local('Courier'), local('Menlo'), local('Monaco')",
70
+ 'default': "local('Arial'), local('Helvetica Neue'), local('Roboto'), local('Liberation Sans')",
71
+ };
72
+
73
+ // OS/2 sFamilyClass high byte → FALLBACK_STACKS key.
74
+ const FAMILY_CLASS_MAP = {
75
+ 1: 'serif', 2: 'serif', 3: 'serif', 4: 'serif', 5: 'serif', 7: 'serif',
76
+ 8: 'sans-serif',
77
+ };
78
+
79
+ // Darden Studio fonts have sFamilyClass: 0 in their OS/2 table,
80
+ // so name-based matching is used as the primary signal.
81
+ const SERIF_NAMES = /jubilat|corundum|dapifer|birra|daith/i;
82
+ const SANS_NAMES = /halyard|gamay|omnes|kit/i;
83
+
84
+ /**
85
+ * Detects font category from the font name first, then OS/2 sFamilyClass as fallback.
86
+ * @param {object} font - lib-font Font instance
87
+ * @param {string} fontName
88
+ * @returns {string}
89
+ */
90
+ function detectFontCategory(font, fontName) {
91
+ if (fontName && SERIF_NAMES.test(fontName)) return 'serif';
92
+ if (fontName && SANS_NAMES.test(fontName)) return 'sans-serif';
93
+ try {
94
+ const familyClass = getFamilyClass(font);
95
+ const highByte = (familyClass >> 8) & 0xFF;
96
+ return FAMILY_CLASS_MAP[highByte] ?? 'default';
97
+ } catch {
98
+ return 'default';
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Extracts metric override percentages and detects the category fallback stack.
104
+ * Accepts a pre-parsed font to avoid double-parsing.
105
+ * @param {object} font - lib-font Font instance
106
+ * @param {string} fontName
107
+ * @returns {{ fallbackSrc: string, ascentOverride: string, descentOverride: string, lineGapOverride: string }}
108
+ */
109
+ function calcFallbackData(font, fontName) {
110
+ try {
111
+ const metrics = getFontMetrics(font);
112
+ const upm = metrics.unitsPerEm;
113
+ const category = detectFontCategory(font, fontName);
114
+ return {
115
+ fallbackSrc: FALLBACK_STACKS[category],
116
+ ascentOverride: `${(metrics.ascender / upm * 100).toFixed(2)}%`,
117
+ descentOverride: `${(Math.abs(metrics.descender) / upm * 100).toFixed(2)}%`,
118
+ lineGapOverride: `${(metrics.lineGap / upm * 100).toFixed(2)}%`,
119
+ };
120
+ } catch (err) {
121
+ console.error('Failed to extract fallback font data:', err);
122
+ return {
123
+ fallbackSrc: FALLBACK_STACKS['default'],
124
+ ascentOverride: '100%',
125
+ descentOverride: '0%',
126
+ lineGapOverride: '0%',
127
+ };
128
+ }
129
+ }
130
+
131
+ export default async function generateCssFile({
132
+ woff2File,
133
+ fileInput,
134
+ language = null,
135
+ fileName,
136
+ fontName,
137
+ variableFont,
138
+ weight,
139
+ style = 'Normal',
140
+ client,
141
+ }) {
142
+ try {
143
+ // Read the file once; reuse the same buffer for base64 and font analysis
144
+ let arrayBuffer = await woff2File.arrayBuffer();
145
+ let b64 = _arrayBufferToBase64(arrayBuffer);
146
+
147
+ // Parse once — share result between axis descriptors and fallback metrics
148
+ let font = await parseFont(arrayBuffer, fileName + '.woff2');
149
+ let { fallbackSrc, ascentOverride, descentOverride, lineGapOverride } = calcFallbackData(font, fontName);
150
+
151
+ // Escape font name for CSS injection prevention
152
+ const safeFontName = escapeCssFontName(fontName);
153
+
154
+ let cssString;
155
+ if (variableFont) {
156
+ const axisMap = getVariationAxes(font);
157
+ let { descriptors, skipped } = buildVFDescriptors(axisMap);
158
+ let skipComment = skipped.length
159
+ ? `/* axes present but have no @font-face descriptor: ${skipped.join(', ')}` +
160
+ (skipped.includes('opsz') ? ' — add font-optical-sizing:auto to your element CSS' : '') +
161
+ ' */'
162
+ : '';
163
+ cssString = `${skipComment}@font-face{font-family:'${safeFontName}';src:url(data:application/font-woff2;charset=utf-8;base64,${b64})format('woff2');${descriptors}font-display:swap;}`;
164
+ } else {
165
+ let fontStyle = style === 'Italic' ? 'italic' : 'normal';
166
+ cssString = `@font-face{font-family:'${safeFontName}';src:url(data:application/font-woff2;charset=utf-8;base64,${b64})format('woff2');font-weight:${weight};font-style:${fontStyle};font-display:swap;}`;
167
+ }
168
+
169
+ // Fallback @font-face: tunes a system font to match the custom font's metrics for CLS reduction
170
+ let fallbackCssString = `@font-face{font-family:'${safeFontName} Fallback';src:${fallbackSrc};ascent-override:${ascentOverride};descent-override:${descentOverride};line-gap-override:${lineGapOverride};}`;
171
+
172
+ // Upload as a text buffer (no Buffer polyfill needed — TextEncoder is native)
173
+ const cssBytes = new TextEncoder().encode(cssString + fallbackCssString);
174
+ let doc = await client.assets.upload('file', new Blob([cssBytes]), { filename: fileName + '.css' });
175
+
176
+ let newFileInput = language == null ?
177
+ {
178
+ ...fileInput,
179
+ css: {
180
+ _type: 'file',
181
+ asset: {
182
+ _type: 'reference',
183
+ _ref: doc._id,
184
+ },
185
+ },
186
+ }
187
+ :
188
+ {
189
+ ...fileInput,
190
+ [language]: {
191
+ ...fileInput[language],
192
+ css: {
193
+ _type: 'file',
194
+ asset: {
195
+ _type: 'reference',
196
+ _ref: doc._id,
197
+ },
198
+ },
199
+ },
200
+ };
201
+
202
+ return newFileInput;
203
+ } catch (err) {
204
+ console.error(err);
205
+ throw err;
206
+ }
207
+ }
@@ -1,98 +1,98 @@
1
- // Extracts metadata, metrics, glyph count, OpenType features, and variable axes from a font and optionally patches the Sanity font document
2
-
3
- import { parseFont } from './parseFont';
4
- import {
5
- getFontMetadata,
6
- getFontMetrics,
7
- getVariationAxes,
8
- getNamedInstances,
9
- getAllFeatureTags,
10
- getGlyphCount,
11
- getCharacterSet,
12
- getNameString,
13
- } from './fontHelpers';
14
-
15
- /**
16
- * Extracts metadata and metrics from a lib-font parsed font into plain objects.
17
- * Uses fontHelpers for all table access — no direct font.opentype.tables usage here.
18
- * @param {object} font - lib-font Font instance
19
- * @returns {{ metaData: Object, metrics: Object }}
20
- */
21
- export function buildFontMetadata(font) {
22
- const metaData = getFontMetadata(font);
23
- // Strip "Version " prefix for consistency with existing Sanity documents
24
- if (metaData.version) {
25
- metaData.version = String(metaData.version).replaceAll('Version ', '');
26
- }
27
- const metrics = getFontMetrics(font);
28
- return { metaData, metrics };
29
- }
30
-
31
- export default async function generateFontData({ fileInput, url, fontKit, fontId, client, commit = true }) {
32
- if (fontId.startsWith('drafts.')) {
33
- fontId = fontId.replace('drafts.', '');
34
- }
35
- console.log('Generate font data:', fontId, commit);
36
-
37
- let srcUrl;
38
- if (!url || url == null) {
39
- srcUrl = await client.fetch(`*[_id == $id]{url}`, { id: fileInput.ttf.asset._ref });
40
- srcUrl = srcUrl[0].url;
41
- } else {
42
- srcUrl = url;
43
- }
44
-
45
- let font = fontKit;
46
- if (!fontKit || fontKit == null) {
47
- let buffer = await fetch(srcUrl);
48
- buffer = await buffer.arrayBuffer();
49
- font = await parseFont(buffer, `${fontId}.ttf`);
50
- }
51
-
52
- const variableAxes = getVariationAxes(font);
53
- const namedInstances = getNamedInstances(font);
54
-
55
- // Build variableInstances as a keyed object matching existing Sanity document shape
56
- let variableInstances = null;
57
- if (namedInstances.length > 0 && variableAxes) {
58
- variableInstances = {};
59
- const axisTags = Object.keys(variableAxes);
60
- for (const inst of namedInstances) {
61
- const key = inst.name || inst.postScriptName || 'Unknown';
62
- const coord = {};
63
- axisTags.forEach((tag, index) => {
64
- coord[tag] = inst.coordinates[index];
65
- });
66
- variableInstances[key] = coord;
67
- }
68
- }
69
-
70
- console.log('Variable instances:', variableInstances);
71
- console.log('Variable axes:', variableAxes);
72
-
73
- const opentypeFeatures = getAllFeatureTags(font);
74
- const glyphCount = getGlyphCount(font);
75
- const characterSet = getCharacterSet(font);
76
-
77
- const { metaData, metrics } = buildFontMetadata(font);
78
-
79
- let variableFont = false;
80
- if (variableAxes && variableInstances && Object.keys(variableInstances).length > 0) {
81
- variableFont = true;
82
- }
83
-
84
- let patch = {
85
- metrics: metrics,
86
- metaData: metaData,
87
- variableFont: variableFont,
88
- variableAxes: JSON.stringify(variableAxes),
89
- variableInstances: JSON.stringify(variableInstances),
90
- glyphCount: glyphCount,
91
- opentypeFeatures: { chars: opentypeFeatures },
92
- characterSet: { chars: characterSet },
93
- };
94
-
95
- console.log('Font data patch:', Object.keys(patch));
96
- if (commit) patch = await client.patch(fontId).set(patch).commit({ autoGenerateArrayKeys: true });
97
- return patch;
98
- }
1
+ // Extracts metadata, metrics, glyph count, OpenType features, and variable axes from a font and optionally patches the Sanity font document
2
+
3
+ import { parseFont } from './parseFont';
4
+ import {
5
+ getFontMetadata,
6
+ getFontMetrics,
7
+ getVariationAxes,
8
+ getNamedInstances,
9
+ getAllFeatureTags,
10
+ getGlyphCount,
11
+ getCharacterSet,
12
+ getNameString,
13
+ } from './fontHelpers';
14
+
15
+ /**
16
+ * Extracts metadata and metrics from a lib-font parsed font into plain objects.
17
+ * Uses fontHelpers for all table access — no direct font.opentype.tables usage here.
18
+ * @param {object} font - lib-font Font instance
19
+ * @returns {{ metaData: Object, metrics: Object }}
20
+ */
21
+ export function buildFontMetadata(font) {
22
+ const metaData = getFontMetadata(font);
23
+ // Strip "Version " prefix for consistency with existing Sanity documents
24
+ if (metaData.version) {
25
+ metaData.version = String(metaData.version).replaceAll('Version ', '');
26
+ }
27
+ const metrics = getFontMetrics(font);
28
+ return { metaData, metrics };
29
+ }
30
+
31
+ export default async function generateFontData({ fileInput, url, fontKit, fontId, client, commit = true }) {
32
+ if (fontId.startsWith('drafts.')) {
33
+ fontId = fontId.replace('drafts.', '');
34
+ }
35
+ console.log('Generate font data:', fontId, commit);
36
+
37
+ let srcUrl;
38
+ if (!url || url == null) {
39
+ srcUrl = await client.fetch(`*[_id == $id]{url}`, { id: fileInput.ttf.asset._ref });
40
+ srcUrl = srcUrl[0].url;
41
+ } else {
42
+ srcUrl = url;
43
+ }
44
+
45
+ let font = fontKit;
46
+ if (!fontKit || fontKit == null) {
47
+ let buffer = await fetch(srcUrl);
48
+ buffer = await buffer.arrayBuffer();
49
+ font = await parseFont(buffer, `${fontId}.ttf`);
50
+ }
51
+
52
+ const variableAxes = getVariationAxes(font);
53
+ const namedInstances = getNamedInstances(font);
54
+
55
+ // Build variableInstances as a keyed object matching existing Sanity document shape
56
+ let variableInstances = null;
57
+ if (namedInstances.length > 0 && variableAxes) {
58
+ variableInstances = {};
59
+ const axisTags = Object.keys(variableAxes);
60
+ for (const inst of namedInstances) {
61
+ const key = inst.name || inst.postScriptName || 'Unknown';
62
+ const coord = {};
63
+ axisTags.forEach((tag, index) => {
64
+ coord[tag] = inst.coordinates[index];
65
+ });
66
+ variableInstances[key] = coord;
67
+ }
68
+ }
69
+
70
+ console.log('Variable instances:', variableInstances);
71
+ console.log('Variable axes:', variableAxes);
72
+
73
+ const opentypeFeatures = getAllFeatureTags(font);
74
+ const glyphCount = getGlyphCount(font);
75
+ const characterSet = getCharacterSet(font);
76
+
77
+ const { metaData, metrics } = buildFontMetadata(font);
78
+
79
+ let variableFont = false;
80
+ if (variableAxes && variableInstances && Object.keys(variableInstances).length > 0) {
81
+ variableFont = true;
82
+ }
83
+
84
+ let patch = {
85
+ metrics: metrics,
86
+ metaData: metaData,
87
+ variableFont: variableFont,
88
+ variableAxes: JSON.stringify(variableAxes),
89
+ variableInstances: JSON.stringify(variableInstances),
90
+ glyphCount: glyphCount,
91
+ opentypeFeatures: { chars: opentypeFeatures },
92
+ characterSet: { chars: characterSet },
93
+ };
94
+
95
+ console.log('Font data patch:', Object.keys(patch));
96
+ if (commit) patch = await client.patch(fontId).set(patch).commit({ autoGenerateArrayKeys: true });
97
+ return patch;
98
+ }