@liiift-studio/sanity-font-manager 2.3.19 → 2.5.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.
Files changed (62) hide show
  1. package/README.md +437 -437
  2. package/dist/UploadModal-6LIX7XOK.js +6 -0
  3. package/dist/UploadModal-NME2W53V.mjs +6 -0
  4. package/dist/chunk-646WCBRR.mjs +7276 -0
  5. package/dist/chunk-FH4QKHOH.js +7276 -0
  6. package/dist/index.js +747 -1675
  7. package/dist/index.mjs +400 -1237
  8. package/package.json +85 -85
  9. package/src/components/BatchUploadFonts.jsx +653 -639
  10. package/src/components/BulkActions.jsx +99 -0
  11. package/src/components/ExistingDocumentResolver.jsx +152 -0
  12. package/src/components/FontReviewCard.jsx +415 -0
  13. package/src/components/FontScriptUploaderComponent.jsx +463 -463
  14. package/src/components/GenerateCollectionsPairsComponent.jsx +259 -259
  15. package/src/components/KeyValueInput.jsx +95 -95
  16. package/src/components/KeyValueReferenceInput.jsx +254 -254
  17. package/src/components/NestedObjectArraySelector.jsx +146 -146
  18. package/src/components/PriceInput.jsx +26 -26
  19. package/src/components/PrimaryCollectionGeneratorTypeface.jsx +116 -116
  20. package/src/components/RegenerateSubfamiliesComponent.jsx +185 -185
  21. package/src/components/SetOTF.jsx +87 -87
  22. package/src/components/SingleUploaderTool.jsx +672 -673
  23. package/src/components/StatusDisplay.jsx +26 -26
  24. package/src/components/StyleCountInput.jsx +16 -16
  25. package/src/components/UpdateScriptsComponent.jsx +76 -76
  26. package/src/components/UploadButton.jsx +43 -43
  27. package/src/components/UploadModal.jsx +268 -0
  28. package/src/components/UploadScriptsComponent.jsx +539 -537
  29. package/src/components/UploadStep1Settings.jsx +272 -0
  30. package/src/components/UploadStep2Review.jsx +472 -0
  31. package/src/components/UploadStep3Execute.jsx +234 -0
  32. package/src/components/UploadSummary.jsx +196 -0
  33. package/src/components/VariableInstanceReferencesInput.jsx +190 -190
  34. package/src/hooks/useNestedObjects.js +92 -92
  35. package/src/hooks/useSanityClient.js +9 -9
  36. package/src/index.js +115 -70
  37. package/src/schema/openTypeField.js +1945 -1945
  38. package/src/schema/styleCountField.js +12 -12
  39. package/src/schema/stylesField.js +268 -268
  40. package/src/schema/stylisticSetField.js +301 -301
  41. package/src/utils/buildUploadPlan.js +325 -0
  42. package/src/utils/executeUploadPlan.js +437 -0
  43. package/src/utils/executionReducer.js +56 -0
  44. package/src/utils/fontHelpers.js +267 -0
  45. package/src/utils/generateCssFile.js +207 -205
  46. package/src/utils/generateFontData.js +98 -145
  47. package/src/utils/generateFontFile.js +38 -38
  48. package/src/utils/generateKeywords.js +185 -185
  49. package/src/utils/generateSubset.js +45 -45
  50. package/src/utils/getEmptyFontKit.js +101 -99
  51. package/src/utils/parseFont.js +55 -0
  52. package/src/utils/parseVariableFontInstances.js +211 -211
  53. package/src/utils/planReducer.js +517 -0
  54. package/src/utils/planTypes.js +183 -0
  55. package/src/utils/processFontFiles.js +529 -477
  56. package/src/utils/regenerateFontData.js +146 -146
  57. package/src/utils/resolveExistingFont.js +87 -0
  58. package/src/utils/sanitizeForSanityId.js +65 -65
  59. package/src/utils/updateFontPrices.js +94 -94
  60. package/src/utils/updateTypefaceDocument.js +149 -160
  61. package/src/utils/uploadFontFiles.js +405 -316
  62. package/src/utils/utils.js +24 -24
@@ -0,0 +1,267 @@
1
+ // Shared helpers for extracting data from lib-font parsed fonts — the ONLY code that touches font.opentype.tables.*
2
+
3
+ /**
4
+ * Name record lookup cache — avoids repeated linear scans of nameRecords.
5
+ * Keyed by font instance (WeakMap), values are { [nameID]: string } maps.
6
+ * @type {WeakMap<object, Object.<number, string>>}
7
+ */
8
+ const nameCache = new WeakMap();
9
+
10
+ /**
11
+ * Get a name table string by numeric name ID.
12
+ * Prefers Windows/Unicode/English (platform 3, language 0x0409),
13
+ * falls back to Mac/Roman/English (platform 1, language 0),
14
+ * then first available record.
15
+ *
16
+ * @param {object} font - lib-font Font instance
17
+ * @param {number} nameID - OpenType name ID (0=copyright, 1=family, 2=subfamily, 4=fullName, 6=postscript, 16=prefFamily, 17=prefSubfamily)
18
+ * @returns {string} Decoded name string, or empty string if not found
19
+ */
20
+ export function getNameString(font, nameID) {
21
+ if (!nameCache.has(font)) nameCache.set(font, {});
22
+ const cache = nameCache.get(font);
23
+ if (nameID in cache) return cache[nameID];
24
+
25
+ const records = font.opentype?.tables?.name?.nameRecords || [];
26
+
27
+ // Priority 1: Windows Unicode English
28
+ const win = records.find(r => r.nameID === nameID && r.platformID === 3 && r.languageID === 0x0409);
29
+ if (win?.string) { cache[nameID] = win.string; return win.string; }
30
+
31
+ // Priority 2: Mac Roman English
32
+ const mac = records.find(r => r.nameID === nameID && r.platformID === 1 && r.languageID === 0);
33
+ if (mac?.string) { cache[nameID] = mac.string; return mac.string; }
34
+
35
+ // Priority 3: First record with this nameID
36
+ const any = records.find(r => r.nameID === nameID);
37
+ const result = any?.string || '';
38
+ cache[nameID] = result;
39
+ return result;
40
+ }
41
+
42
+ /**
43
+ * Get all OpenType feature tags from GSUB and GPOS tables.
44
+ * Traverses scripts → langsys → features, deduplicates, and caches.
45
+ * Equivalent to fontkit's font.availableFeatures.
46
+ *
47
+ * @param {object} font - lib-font Font instance
48
+ * @returns {string[]} Array of unique 4-character feature tag strings (e.g. ['kern', 'liga', 'smcp'])
49
+ */
50
+ export function getAllFeatureTags(font) {
51
+ const tags = new Set();
52
+ const tables = font.opentype?.tables;
53
+ for (const layoutTable of [tables?.GSUB, tables?.GPOS]) {
54
+ if (!layoutTable) continue;
55
+ try {
56
+ for (const scriptTag of layoutTable.getSupportedScripts()) {
57
+ const script = layoutTable.getScriptTable(scriptTag);
58
+ for (const langTag of layoutTable.getSupportedLangSys(script)) {
59
+ const langsys = layoutTable.getLangSysTable(script, langTag);
60
+ for (const feature of layoutTable.getFeatures(langsys)) {
61
+ tags.add(feature.featureTag.trim());
62
+ }
63
+ }
64
+ }
65
+ } catch (err) {
66
+ console.warn(`Error reading ${layoutTable === tables.GSUB ? 'GSUB' : 'GPOS'} features:`, err.message);
67
+ }
68
+ }
69
+ return [...tags];
70
+ }
71
+
72
+ /**
73
+ * Get character set as array of code points from the cmap table.
74
+ * Uses Windows/Unicode BMP subtable (platform 3, encoding 1).
75
+ *
76
+ * @param {object} font - lib-font Font instance
77
+ * @returns {number[]} Array of supported Unicode code points
78
+ */
79
+ export function getCharacterSet(font) {
80
+ const cmap = font.opentype?.tables?.cmap;
81
+ if (!cmap) return [];
82
+ try {
83
+ return cmap.getSupportedCharCodes(3, 1);
84
+ } catch {
85
+ return [];
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Build a variation axis map from fvar table.
91
+ * Filters out degenerate axes (min === max). Returns null if not a variable font.
92
+ *
93
+ * @param {object} font - lib-font Font instance
94
+ * @returns {{ [tag: string]: { min: number, max: number, default: number, name: string } } | null}
95
+ */
96
+ export function getVariationAxes(font) {
97
+ const fvar = font.opentype?.tables?.fvar;
98
+ if (!fvar?.axes) return null;
99
+
100
+ const axes = {};
101
+ for (const axis of fvar.axes) {
102
+ if (axis.minValue === axis.maxValue) continue;
103
+ axes[axis.tag] = {
104
+ min: axis.minValue,
105
+ max: axis.maxValue,
106
+ default: axis.defaultValue,
107
+ name: getNameString(font, axis.axisNameID) || axis.tag,
108
+ };
109
+ }
110
+ return Object.keys(axes).length > 0 ? axes : null;
111
+ }
112
+
113
+ /**
114
+ * Get named instances from fvar table.
115
+ * Resolves subfamilyNameID and postScriptNameID via the name table.
116
+ *
117
+ * @param {object} font - lib-font Font instance
118
+ * @returns {Array<{ name: string, coordinates: number[], postScriptName: string }>}
119
+ */
120
+ export function getNamedInstances(font) {
121
+ const fvar = font.opentype?.tables?.fvar;
122
+ if (!fvar?.instances) return [];
123
+ return fvar.instances.map(inst => ({
124
+ name: getNameString(font, inst.subfamilyNameID),
125
+ coordinates: inst.coordinates,
126
+ postScriptName: getNameString(font, inst.postScriptNameID || 0),
127
+ }));
128
+ }
129
+
130
+ /**
131
+ * Build font metrics object matching the Sanity document shape.
132
+ * Uses OS/2 typo metrics when USE_TYPO_METRICS bit is set, otherwise hhea.
133
+ *
134
+ * @param {object} font - lib-font Font instance
135
+ * @returns {{ unitsPerEm: number, ascender: number, descender: number, lineGap: number, underlinePosition: number, underlineThickness: number, italicAngle: number, capHeight: number, xHeight: number, boundingBox: { xMin: number, yMin: number, xMax: number, yMax: number } }}
136
+ */
137
+ export function getFontMetrics(font) {
138
+ const tables = font.opentype?.tables;
139
+ const os2 = tables?.['OS/2'];
140
+ const head = tables?.head;
141
+ const post = tables?.post;
142
+ const hhea = tables?.hhea;
143
+
144
+ // USE_TYPO_METRICS flag (fsSelection bit 7) — when set, use OS/2 typo metrics
145
+ const useTypo = os2 ? (os2.fsSelection & 0x80) !== 0 : false;
146
+
147
+ return {
148
+ unitsPerEm: head?.unitsPerEm || 1000,
149
+ ascender: useTypo ? (os2?.sTypoAscender || 0) : (hhea?.ascender ?? os2?.sTypoAscender ?? 0),
150
+ descender: useTypo ? (os2?.sTypoDescender || 0) : (hhea?.descender ?? os2?.sTypoDescender ?? 0),
151
+ lineGap: useTypo ? (os2?.sTypoLineGap || 0) : (hhea?.lineGap ?? os2?.sTypoLineGap ?? 0),
152
+ underlinePosition: post?.underlinePosition || 0,
153
+ underlineThickness: post?.underlineThickness || 0,
154
+ italicAngle: post?.italicAngle || 0,
155
+ capHeight: (os2?.version >= 2) ? (os2?.sCapHeight || 0) : 0,
156
+ xHeight: (os2?.version >= 2) ? (os2?.sxHeight || 0) : 0,
157
+ boundingBox: {
158
+ xMin: head?.xMin || 0,
159
+ yMin: head?.yMin || 0,
160
+ xMax: head?.xMax || 0,
161
+ yMax: head?.yMax || 0,
162
+ },
163
+ };
164
+ }
165
+
166
+ /**
167
+ * Build font metadata object matching the Sanity document shape.
168
+ *
169
+ * @param {object} font - lib-font Font instance
170
+ * @returns {{ postscriptName: string, fullName: string, familyName: string, subfamilyName: string, copyright: string, version: string, genDate: string }}
171
+ */
172
+ export function getFontMetadata(font) {
173
+ return {
174
+ postscriptName: getNameString(font, 6),
175
+ fullName: getNameString(font, 4),
176
+ familyName: getNameString(font, 1),
177
+ subfamilyName: getNameString(font, 2),
178
+ copyright: getNameString(font, 0),
179
+ version: getNameString(font, 5),
180
+ genDate: new Date().toISOString(),
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Get the OS/2 usWeightClass value.
186
+ *
187
+ * @param {object} font - lib-font Font instance
188
+ * @returns {number|null} Weight class (1-1000) or null if OS/2 table is missing
189
+ */
190
+ export function getWeightClass(font) {
191
+ return font.opentype?.tables?.['OS/2']?.usWeightClass || null;
192
+ }
193
+
194
+ /**
195
+ * Get the OS/2 fsSelection flags as a raw uint16.
196
+ *
197
+ * @param {object} font - lib-font Font instance
198
+ * @returns {number} fsSelection bitmask (0 if OS/2 table is missing)
199
+ */
200
+ export function getFsSelection(font) {
201
+ return font.opentype?.tables?.['OS/2']?.fsSelection || 0;
202
+ }
203
+
204
+ /**
205
+ * Get the head macStyle flags as a uint16 bitmask.
206
+ * lib-font returns macStyle as a bit array (big-endian order: index 15 = bit 0).
207
+ * This helper converts it back to a standard uint16 for bitwise testing.
208
+ *
209
+ * @param {object} font - lib-font Font instance
210
+ * @returns {number} macStyle bitmask (0 if head table is missing)
211
+ */
212
+ export function getMacStyle(font) {
213
+ const macStyle = font.opentype?.tables?.head?.macStyle;
214
+ if (!macStyle) return 0;
215
+ // lib-font returns a bit array or a number depending on version
216
+ if (typeof macStyle === 'number') return macStyle;
217
+ // Convert bit array (big-endian) to uint16: index 15 = bit 0, index 14 = bit 1, etc.
218
+ if (typeof macStyle === 'object') {
219
+ let value = 0;
220
+ for (let i = 0; i < 16; i++) {
221
+ if (macStyle[i]) value |= (1 << (15 - i));
222
+ }
223
+ return value;
224
+ }
225
+ return 0;
226
+ }
227
+
228
+ /**
229
+ * Get the post table italic angle.
230
+ *
231
+ * @param {object} font - lib-font Font instance
232
+ * @returns {number} Italic angle in degrees (0 for upright fonts)
233
+ */
234
+ export function getItalicAngle(font) {
235
+ return font.opentype?.tables?.post?.italicAngle || 0;
236
+ }
237
+
238
+ /**
239
+ * Get glyph count from maxp table.
240
+ *
241
+ * @param {object} font - lib-font Font instance
242
+ * @returns {number} Number of glyphs
243
+ */
244
+ export function getGlyphCount(font) {
245
+ return font.opentype?.tables?.maxp?.numGlyphs || 0;
246
+ }
247
+
248
+ /**
249
+ * Get the OS/2 sFamilyClass value for font category detection.
250
+ *
251
+ * @param {object} font - lib-font Font instance
252
+ * @returns {number} sFamilyClass value (0 if missing)
253
+ */
254
+ export function getFamilyClass(font) {
255
+ return font.opentype?.tables?.['OS/2']?.sFamilyClass || 0;
256
+ }
257
+
258
+ /**
259
+ * Escape a font name for safe interpolation into CSS font-family declarations.
260
+ * Prevents CSS injection via crafted name table strings.
261
+ *
262
+ * @param {string} name - Raw font name from the name table
263
+ * @returns {string} Escaped name safe for CSS string context
264
+ */
265
+ export function escapeCssFontName(name) {
266
+ return name.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/;/g, '');
267
+ }