@liiift-studio/sanity-font-manager 2.3.19 → 2.4.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/README.md +437 -437
- package/dist/index.js +103 -48
- package/dist/index.mjs +103 -48
- package/package.json +85 -85
- package/src/components/BatchUploadFonts.jsx +640 -639
- package/src/components/FontScriptUploaderComponent.jsx +463 -463
- package/src/components/GenerateCollectionsPairsComponent.jsx +259 -259
- package/src/components/KeyValueInput.jsx +95 -95
- package/src/components/KeyValueReferenceInput.jsx +254 -254
- package/src/components/NestedObjectArraySelector.jsx +146 -146
- package/src/components/PriceInput.jsx +26 -26
- package/src/components/PrimaryCollectionGeneratorTypeface.jsx +116 -116
- package/src/components/RegenerateSubfamiliesComponent.jsx +185 -185
- package/src/components/SetOTF.jsx +87 -87
- package/src/components/SingleUploaderTool.jsx +673 -673
- package/src/components/StatusDisplay.jsx +26 -26
- package/src/components/StyleCountInput.jsx +16 -16
- package/src/components/UpdateScriptsComponent.jsx +76 -76
- package/src/components/UploadButton.jsx +43 -43
- package/src/components/UploadScriptsComponent.jsx +537 -537
- package/src/components/VariableInstanceReferencesInput.jsx +190 -190
- package/src/hooks/useNestedObjects.js +92 -92
- package/src/hooks/useSanityClient.js +9 -9
- package/src/index.js +70 -70
- package/src/schema/openTypeField.js +1945 -1945
- package/src/schema/styleCountField.js +12 -12
- package/src/schema/stylesField.js +268 -268
- package/src/schema/stylisticSetField.js +301 -301
- package/src/utils/generateCssFile.js +205 -205
- package/src/utils/generateFontData.js +145 -145
- package/src/utils/generateFontFile.js +38 -38
- package/src/utils/generateKeywords.js +185 -185
- package/src/utils/generateSubset.js +45 -45
- package/src/utils/getEmptyFontKit.js +99 -99
- package/src/utils/parseVariableFontInstances.js +211 -211
- package/src/utils/processFontFiles.js +487 -477
- package/src/utils/regenerateFontData.js +146 -146
- package/src/utils/sanitizeForSanityId.js +65 -65
- package/src/utils/updateFontPrices.js +94 -94
- package/src/utils/updateTypefaceDocument.js +149 -160
- package/src/utils/uploadFontFiles.js +115 -26
- package/src/utils/utils.js +24 -24
|
@@ -1,477 +1,487 @@
|
|
|
1
|
-
// Reads font files via FileReader, parses with fontkit, and builds the fontsObjects map — exports individual weight/style extraction helpers
|
|
2
|
-
|
|
3
|
-
import * as fontkit from 'fontkit';
|
|
4
|
-
import { nanoid } from 'nanoid';
|
|
5
|
-
import { expandAbbreviations, removeWeightNames, reverseSpellingLookup } from './generateKeywords';
|
|
6
|
-
import { sanitizeForSanityId } from './sanitizeForSanityId';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Reads a font file and returns its content as a Uint8Array.
|
|
10
|
-
* @param {File} file
|
|
11
|
-
* @returns {Promise<Uint8Array>}
|
|
12
|
-
*/
|
|
13
|
-
export const readFontFile = (file) => {
|
|
14
|
-
return new Promise((resolve, reject) => {
|
|
15
|
-
const reader = new FileReader();
|
|
16
|
-
reader.onload = (event) => { resolve(new Uint8Array(event.target.result)); };
|
|
17
|
-
reader.onerror = (error) => { reject(error); };
|
|
18
|
-
reader.readAsArrayBuffer(file);
|
|
19
|
-
});
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Processes an array of font files and extracts metadata for each.
|
|
24
|
-
* @param {File[]} files
|
|
25
|
-
* @param {string} title - Typeface title
|
|
26
|
-
* @param {string[]} weightKeywordList
|
|
27
|
-
* @param {string[]} italicKeywordList
|
|
28
|
-
* @param {Function} setStatus
|
|
29
|
-
* @param {boolean} preserveShortenedNames - Skip abbreviation expansion when true
|
|
30
|
-
* @param {boolean} preserveFileNames - Preserve original filename capitalization when true
|
|
31
|
-
* @returns {Promise<Object>}
|
|
32
|
-
*/
|
|
33
|
-
export const processFontFiles = async (
|
|
34
|
-
files,
|
|
35
|
-
title,
|
|
36
|
-
weightKeywordList,
|
|
37
|
-
italicKeywordList,
|
|
38
|
-
setStatus,
|
|
39
|
-
preserveShortenedNames = false,
|
|
40
|
-
preserveFileNames = false,
|
|
41
|
-
) => {
|
|
42
|
-
let failedFiles = [];
|
|
43
|
-
let subfamilies = {};
|
|
44
|
-
let fontsObjects = {};
|
|
45
|
-
let newPreferredStyle = { weight: -100, style: 'Italic', _ref: '' };
|
|
46
|
-
|
|
47
|
-
for (let i = 0; i < files.length; i++) {
|
|
48
|
-
const file = files[i];
|
|
49
|
-
const fontBuffer = await readFontFile(file);
|
|
50
|
-
const font = fontkit.create(fontBuffer);
|
|
51
|
-
|
|
52
|
-
console.log('File name: ', file.name);
|
|
53
|
-
|
|
54
|
-
if (file.name.endsWith('.woff2') || file.name.endsWith('.woff')) {
|
|
55
|
-
await handleWebfontMetadata(file, font, files);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
font,
|
|
60
|
-
title,
|
|
61
|
-
weightKeywordList,
|
|
62
|
-
italicKeywordList,
|
|
63
|
-
preserveShortenedNames,
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (preserveFileNames) {
|
|
70
|
-
originalFilename = file.name.replace(/\.(ttf|otf|woff2?|eot|svg)$/i, '');
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
italicKW = [...new Set(italicKW)];
|
|
343
|
-
|
|
344
|
-
if (italicKW.length
|
|
345
|
-
italicKW = ['Italic'];
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
if (/
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
return
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
console.log('
|
|
475
|
-
console.log('Font
|
|
476
|
-
console.log('
|
|
477
|
-
|
|
1
|
+
// Reads font files via FileReader, parses with fontkit, and builds the fontsObjects map — exports individual weight/style extraction helpers
|
|
2
|
+
|
|
3
|
+
import * as fontkit from 'fontkit';
|
|
4
|
+
import { nanoid } from 'nanoid';
|
|
5
|
+
import { expandAbbreviations, removeWeightNames, reverseSpellingLookup } from './generateKeywords';
|
|
6
|
+
import { sanitizeForSanityId } from './sanitizeForSanityId';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Reads a font file and returns its content as a Uint8Array.
|
|
10
|
+
* @param {File} file
|
|
11
|
+
* @returns {Promise<Uint8Array>}
|
|
12
|
+
*/
|
|
13
|
+
export const readFontFile = (file) => {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const reader = new FileReader();
|
|
16
|
+
reader.onload = (event) => { resolve(new Uint8Array(event.target.result)); };
|
|
17
|
+
reader.onerror = (error) => { reject(error); };
|
|
18
|
+
reader.readAsArrayBuffer(file);
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Processes an array of font files and extracts metadata for each.
|
|
24
|
+
* @param {File[]} files
|
|
25
|
+
* @param {string} title - Typeface title
|
|
26
|
+
* @param {string[]} weightKeywordList
|
|
27
|
+
* @param {string[]} italicKeywordList
|
|
28
|
+
* @param {Function} setStatus
|
|
29
|
+
* @param {boolean} preserveShortenedNames - Skip abbreviation expansion when true
|
|
30
|
+
* @param {boolean} preserveFileNames - Preserve original filename capitalization when true
|
|
31
|
+
* @returns {Promise<Object>}
|
|
32
|
+
*/
|
|
33
|
+
export const processFontFiles = async (
|
|
34
|
+
files,
|
|
35
|
+
title,
|
|
36
|
+
weightKeywordList,
|
|
37
|
+
italicKeywordList,
|
|
38
|
+
setStatus,
|
|
39
|
+
preserveShortenedNames = false,
|
|
40
|
+
preserveFileNames = false,
|
|
41
|
+
) => {
|
|
42
|
+
let failedFiles = [];
|
|
43
|
+
let subfamilies = {};
|
|
44
|
+
let fontsObjects = {};
|
|
45
|
+
let newPreferredStyle = { weight: -100, style: 'Italic', _ref: '' };
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < files.length; i++) {
|
|
48
|
+
const file = files[i];
|
|
49
|
+
const fontBuffer = await readFontFile(file);
|
|
50
|
+
const font = fontkit.create(fontBuffer);
|
|
51
|
+
|
|
52
|
+
console.log('File name: ', file.name);
|
|
53
|
+
|
|
54
|
+
if (file.name.endsWith('.woff2') || file.name.endsWith('.woff')) {
|
|
55
|
+
await handleWebfontMetadata(file, font, files);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let { weightName, subfamilyName, fontTitle, style, italicKW, variableFont } = extractFontMetadata(
|
|
59
|
+
font,
|
|
60
|
+
title,
|
|
61
|
+
weightKeywordList,
|
|
62
|
+
italicKeywordList,
|
|
63
|
+
preserveShortenedNames,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
let id;
|
|
67
|
+
let originalFilename = null;
|
|
68
|
+
|
|
69
|
+
if (preserveFileNames) {
|
|
70
|
+
originalFilename = file.name.replace(/\.(ttf|otf|woff2?|eot|svg)$/i, '');
|
|
71
|
+
// Normalize filename: hyphens to spaces, split camelCase boundaries, collapse whitespace
|
|
72
|
+
const normalizedName = originalFilename
|
|
73
|
+
.replace(/-/g, ' ')
|
|
74
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
75
|
+
.replace(/\s+/g, ' ')
|
|
76
|
+
.trim();
|
|
77
|
+
fontTitle = normalizedName;
|
|
78
|
+
id = sanitizeForSanityId(normalizedName);
|
|
79
|
+
} else {
|
|
80
|
+
id = sanitizeForSanityId(fontTitle);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
logFontInfo(id, fontTitle, font, file.name, subfamilyName, style, weightName, variableFont, italicKW);
|
|
84
|
+
|
|
85
|
+
subfamilies[id] = subfamilyName;
|
|
86
|
+
|
|
87
|
+
if (fontsObjects[id]) {
|
|
88
|
+
fontsObjects[id].files = [...fontsObjects[id].files, file];
|
|
89
|
+
if (preserveFileNames && originalFilename) {
|
|
90
|
+
fontsObjects[id].originalFilename = originalFilename;
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
fontsObjects[id] = createFontObject(
|
|
94
|
+
id,
|
|
95
|
+
fontTitle,
|
|
96
|
+
title,
|
|
97
|
+
font,
|
|
98
|
+
variableFont,
|
|
99
|
+
weightName,
|
|
100
|
+
subfamilyName,
|
|
101
|
+
file,
|
|
102
|
+
preserveFileNames ? originalFilename : null,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
fontsObjects = sortFontObjects(fontsObjects);
|
|
108
|
+
const uniqueSubfamilies = [...new Set(Object.values(subfamilies))];
|
|
109
|
+
|
|
110
|
+
console.log('Subfamilies:', subfamilies);
|
|
111
|
+
console.log('Unique subfamilies:', uniqueSubfamilies, uniqueSubfamilies.length);
|
|
112
|
+
console.log('Font objects:', fontsObjects);
|
|
113
|
+
|
|
114
|
+
return { fontsObjects, subfamilies, uniqueSubfamilies, newPreferredStyle, failedFiles };
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Patches webfont name records from a matching TTF when woff/woff2 metadata is missing.
|
|
119
|
+
* @param {File} file
|
|
120
|
+
* @param {Object} font
|
|
121
|
+
* @param {File[]} files
|
|
122
|
+
*/
|
|
123
|
+
const handleWebfontMetadata = async (file, font, files) => {
|
|
124
|
+
if (
|
|
125
|
+
!font?.name?.records?.fullName ||
|
|
126
|
+
font?.name?.records?.fullName === '' ||
|
|
127
|
+
!/^[A-Z0-9]+$/.test(font?.name?.records?.fullName)
|
|
128
|
+
) {
|
|
129
|
+
const ttfFile = files.find(f => f.name === file.name.replace('.woff2', '.ttf').replace('.woff', '.ttf'));
|
|
130
|
+
if (ttfFile) {
|
|
131
|
+
const ttfFileBuffer = await readFontFile(ttfFile);
|
|
132
|
+
const ttfFileData = fontkit.create(ttfFileBuffer);
|
|
133
|
+
if (ttfFileData) font.name.records = ttfFileData?.name?.records;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Extracts and normalises metadata from a fontkit font object.
|
|
140
|
+
* @param {Object} font
|
|
141
|
+
* @param {string} title
|
|
142
|
+
* @param {string[]} weightKeywordList
|
|
143
|
+
* @param {string[]} italicKeywordList
|
|
144
|
+
* @param {boolean} preserveShortenedNames
|
|
145
|
+
* @returns {Object}
|
|
146
|
+
*/
|
|
147
|
+
export const extractFontMetadata = (font, title, weightKeywordList, italicKeywordList, preserveShortenedNames = false) => {
|
|
148
|
+
let weightName = extractWeightName(font, italicKeywordList);
|
|
149
|
+
if (!preserveShortenedNames) {
|
|
150
|
+
weightName = expandAbbreviations(weightName);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if ((weightName === '' || weightName.toLowerCase() === 'roman') && font?.name?.records?.fullName) {
|
|
154
|
+
weightName = extractWeightFromFullName(font, title);
|
|
155
|
+
if (!preserveShortenedNames) {
|
|
156
|
+
weightName = expandAbbreviations(weightName);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const variableFont = font?.variationAxes && Object.keys(font.variationAxes).length > 0;
|
|
161
|
+
|
|
162
|
+
let subfamilyName = font?.name?.records?.fullName?.en?.replace(title.trim(), '').trim() ||
|
|
163
|
+
font.subfamilyName.trim().replace(title.trim(), '').trim();
|
|
164
|
+
|
|
165
|
+
if (!preserveShortenedNames) {
|
|
166
|
+
subfamilyName = expandAbbreviations(subfamilyName);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let fontTitle = font?.fullName.trim();
|
|
170
|
+
let style = (font?.italicAngle !== 0 || font?.fullName.toLowerCase().includes('italic')) ? 'Italic' : 'Regular';
|
|
171
|
+
|
|
172
|
+
const italicKW = processItalicKeywords(font, fontTitle, italicKeywordList);
|
|
173
|
+
|
|
174
|
+
subfamilyName = processSubfamilyName(subfamilyName, weightKeywordList, italicKW, preserveShortenedNames);
|
|
175
|
+
fontTitle = formatFontTitle(fontTitle, preserveShortenedNames);
|
|
176
|
+
|
|
177
|
+
subfamilyName = subfamilyName === '' ? 'Regular' : subfamilyName.replace(/\s+/g, ' ').trim();
|
|
178
|
+
|
|
179
|
+
if (subfamilyName !== '') {
|
|
180
|
+
weightName = weightName
|
|
181
|
+
.replace(`${subfamilyName} `, '')
|
|
182
|
+
.replace(` ${subfamilyName}`, '')
|
|
183
|
+
.trim();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (variableFont) {
|
|
187
|
+
if (!fontTitle.toLowerCase().includes('vf')) {
|
|
188
|
+
fontTitle = fontTitle + ' VF';
|
|
189
|
+
}
|
|
190
|
+
subfamilyName = '';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!(variableFont && fontTitle.toLowerCase().includes('italic'))) {
|
|
194
|
+
fontTitle = addItalicToFontTitle(font, fontTitle, italicKW, style, preserveShortenedNames);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return { weightName, subfamilyName, fontTitle, style, italicKW, variableFont };
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Extracts the weight name from a font's preferred subfamily or subfamily record.
|
|
202
|
+
* Returns "Variable" for variable fonts.
|
|
203
|
+
* @param {Object} font
|
|
204
|
+
* @param {string[]} italicKW
|
|
205
|
+
* @returns {string}
|
|
206
|
+
*/
|
|
207
|
+
export const extractWeightName = (font, italicKW) => {
|
|
208
|
+
let weightName = font?.name?.records?.preferredSubfamily || font?.name?.records?.fontSubfamily;
|
|
209
|
+
|
|
210
|
+
if (typeof weightName === 'object') {
|
|
211
|
+
weightName = weightName?.en ||
|
|
212
|
+
(weightName.constructor === Object ? weightName[Object.keys(weightName)[0]] : null);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (font?.variationAxes && Object.keys(font.variationAxes).length > 0) {
|
|
216
|
+
return 'Variable';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (italicKW) {
|
|
220
|
+
italicKW.forEach(keyword => {
|
|
221
|
+
const kwRegex = new RegExp(`\\b${keyword.trim()}\\b`, 'i');
|
|
222
|
+
if (kwRegex.test(weightName)) {
|
|
223
|
+
weightName = weightName.replace(kwRegex, '').trim();
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return weightName?.toString()
|
|
229
|
+
.replace('Italic', '')
|
|
230
|
+
.replace('It', '')
|
|
231
|
+
.replace('Slanted', '')
|
|
232
|
+
.replace('Slant', '')
|
|
233
|
+
.replace('Backslant', '')
|
|
234
|
+
.trim();
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Extracts a weight name from the font's full name record when subfamily is empty or "Roman".
|
|
239
|
+
* @param {Object} font
|
|
240
|
+
* @param {string} title
|
|
241
|
+
* @returns {string}
|
|
242
|
+
*/
|
|
243
|
+
export const extractWeightFromFullName = (font, title) => {
|
|
244
|
+
let weightName = font?.name?.records?.fullName;
|
|
245
|
+
weightName = weightName?.en
|
|
246
|
+
? weightName.en
|
|
247
|
+
: (weightName?.constructor === Object ? weightName[Object.keys(weightName)[0]] : weightName);
|
|
248
|
+
weightName = weightName?.replace(title + ' ', '').replace(title, '').trim();
|
|
249
|
+
weightName = weightName?.replace('Italic', '').replace('It', '').replace('Slanted', '').replace('Slant', '').trim();
|
|
250
|
+
return weightName;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Strips weight and italic keywords from a subfamily name string.
|
|
255
|
+
* @param {string} subfamilyName
|
|
256
|
+
* @param {string[]} weightKeywordList
|
|
257
|
+
* @param {string[]} italicKeywordList
|
|
258
|
+
* @param {boolean} preserveShortenedNames
|
|
259
|
+
* @returns {string}
|
|
260
|
+
*/
|
|
261
|
+
export const processSubfamilyName = (subfamilyName, weightKeywordList, italicKeywordList, preserveShortenedNames = false) => {
|
|
262
|
+
weightKeywordList.forEach(keyword => {
|
|
263
|
+
const kwRegex = new RegExp(`\\b${keyword.trim()}\\b`, 'i');
|
|
264
|
+
if (kwRegex.test(subfamilyName)) {
|
|
265
|
+
subfamilyName = subfamilyName.replace(kwRegex, '').trim();
|
|
266
|
+
}
|
|
267
|
+
subfamilyName = removeWeightNames(subfamilyName) || subfamilyName;
|
|
268
|
+
if (!preserveShortenedNames) {
|
|
269
|
+
subfamilyName = expandAbbreviations(subfamilyName);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
italicKeywordList.forEach(keyword => {
|
|
274
|
+
const kwRegex = new RegExp(`\\b${keyword.trim()}\\b`, 'i');
|
|
275
|
+
if (kwRegex.test(subfamilyName)) {
|
|
276
|
+
subfamilyName = subfamilyName.replace(kwRegex, '').trim();
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
return subfamilyName;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Collects italic keywords present in a font's full name.
|
|
285
|
+
* @param {Object} font
|
|
286
|
+
* @param {string} fontTitle
|
|
287
|
+
* @param {string[]} italicKeywordList
|
|
288
|
+
* @returns {string[]}
|
|
289
|
+
*/
|
|
290
|
+
export const processItalicKeywords = (font, fontTitle, italicKeywordList) => {
|
|
291
|
+
let italicKW = [];
|
|
292
|
+
|
|
293
|
+
italicKeywordList.forEach(keyword => {
|
|
294
|
+
const kw = keyword.trim();
|
|
295
|
+
const kwRegex = new RegExp(`\\b${kw}\\b`, 'i');
|
|
296
|
+
if (kwRegex.test(fontTitle)) {
|
|
297
|
+
fontTitle = fontTitle.replace(kwRegex, '').trim();
|
|
298
|
+
italicKW.push(kw);
|
|
299
|
+
}
|
|
300
|
+
if (font?.fullName && typeof font.fullName === 'string' && font.fullName.toLowerCase().includes(kw.toLowerCase())) {
|
|
301
|
+
if (!italicKW.includes(kw)) italicKW.push(kw);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
return italicKW;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Normalises and title-cases a font title, optionally expanding abbreviations.
|
|
310
|
+
* @param {string} fontTitle
|
|
311
|
+
* @param {boolean} preserveShortenedNames
|
|
312
|
+
* @returns {string}
|
|
313
|
+
*/
|
|
314
|
+
export const formatFontTitle = (fontTitle, preserveShortenedNames = false) => {
|
|
315
|
+
const hasItalic = fontTitle.toLowerCase().includes('italic');
|
|
316
|
+
fontTitle = fontTitle.replace(/-/g, ' ');
|
|
317
|
+
|
|
318
|
+
return fontTitle.replace(/\s+/g, ' ').trim().split(' ').map(word => {
|
|
319
|
+
if (hasItalic && word.toLowerCase() === 'italic') return 'Italic';
|
|
320
|
+
let fullWord = word;
|
|
321
|
+
if (!preserveShortenedNames) {
|
|
322
|
+
fullWord = reverseSpellingLookup(word) || word;
|
|
323
|
+
}
|
|
324
|
+
return fullWord[0].toUpperCase() + fullWord.slice(1);
|
|
325
|
+
}).join(' ');
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Appends any italic keywords to the font title that aren't already present.
|
|
330
|
+
* @param {Object} font
|
|
331
|
+
* @param {string} fontTitle
|
|
332
|
+
* @param {string[]} italicKW
|
|
333
|
+
* @param {string} style
|
|
334
|
+
* @param {boolean} preserveShortenedNames
|
|
335
|
+
* @returns {string}
|
|
336
|
+
*/
|
|
337
|
+
export const addItalicToFontTitle = (font, fontTitle, italicKW, style, preserveShortenedNames = false) => {
|
|
338
|
+
const hasItalicAngle = font?.italicAngle !== 0;
|
|
339
|
+
const hasItalicInName = font?.fullName.toLowerCase().includes('italic');
|
|
340
|
+
|
|
341
|
+
if (italicKW.length > 0 || hasItalicAngle || hasItalicInName) {
|
|
342
|
+
italicKW = [...new Set(italicKW)];
|
|
343
|
+
|
|
344
|
+
if (italicKW.length === 0 && (hasItalicAngle || hasItalicInName)) {
|
|
345
|
+
italicKW = ['Italic'];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (!preserveShortenedNames) {
|
|
349
|
+
italicKW = italicKW.map(item => reverseSpellingLookup(item) || item);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
italicKW = [...new Set(italicKW)];
|
|
353
|
+
|
|
354
|
+
if (italicKW.length > 1 && italicKW.includes('Italic')) {
|
|
355
|
+
italicKW = ['Italic'];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const fontTitleLower = fontTitle.toLowerCase();
|
|
359
|
+
italicKW = italicKW.filter(keyword => {
|
|
360
|
+
const keywordLower = keyword.toLowerCase();
|
|
361
|
+
const kwRegex = new RegExp(`\\b${keywordLower}\\b`);
|
|
362
|
+
const isSubstring = fontTitleLower.split(' ').some(word =>
|
|
363
|
+
word.includes(keywordLower) || keywordLower.includes(word)
|
|
364
|
+
);
|
|
365
|
+
return !kwRegex.test(fontTitleLower) && !isSubstring;
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
if (italicKW.length > 0) {
|
|
369
|
+
fontTitle = fontTitle.trim() + ' ' + italicKW.join(' ');
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return fontTitle;
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Builds a font object ready for staging and upload.
|
|
378
|
+
* originalFilename is stored temporarily and deleted before saving to Sanity.
|
|
379
|
+
* @param {string} id
|
|
380
|
+
* @param {string} fontTitle
|
|
381
|
+
* @param {string} title
|
|
382
|
+
* @param {Object} font
|
|
383
|
+
* @param {boolean} variableFont
|
|
384
|
+
* @param {string} weightName
|
|
385
|
+
* @param {string} subfamilyName
|
|
386
|
+
* @param {File} file
|
|
387
|
+
* @param {string|null} originalFilename
|
|
388
|
+
* @returns {Object}
|
|
389
|
+
*/
|
|
390
|
+
export const createFontObject = (id, fontTitle, title, font, variableFont, weightName, subfamilyName, file, originalFilename = null) => {
|
|
391
|
+
const fontObject = {
|
|
392
|
+
_key: nanoid(),
|
|
393
|
+
_id: id,
|
|
394
|
+
title: fontTitle,
|
|
395
|
+
slug: { _type: 'slug', current: id },
|
|
396
|
+
typefaceName: title,
|
|
397
|
+
style: (font?.italicAngle !== 0 || font?.fullName.toLowerCase().includes('italic')) ? 'Italic' : 'Regular',
|
|
398
|
+
variableFont: variableFont,
|
|
399
|
+
weightName: weightName,
|
|
400
|
+
subfamily: subfamilyName,
|
|
401
|
+
normalWeight: true,
|
|
402
|
+
weight: Number(determineWeight(font, weightName)),
|
|
403
|
+
fileInput: {},
|
|
404
|
+
files: [file],
|
|
405
|
+
fontKit: font,
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
if (originalFilename) {
|
|
409
|
+
fontObject.originalFilename = originalFilename;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return fontObject;
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Determines a numeric CSS weight value for a font.
|
|
417
|
+
* @param {Object} font
|
|
418
|
+
* @param {string} weightName
|
|
419
|
+
* @returns {number}
|
|
420
|
+
*/
|
|
421
|
+
export const determineWeight = (font, weightName) => {
|
|
422
|
+
if (font['OS/2']?.usWeightClass) {
|
|
423
|
+
return Number(font['OS/2'].usWeightClass);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const wn = weightName?.toLowerCase() || '';
|
|
427
|
+
|
|
428
|
+
if (/hairline|extra thin|extrathin/.test(wn)) return 100;
|
|
429
|
+
if (/thin|extra light|extralight/.test(wn)) return 200;
|
|
430
|
+
if (/light|book/.test(wn)) return 300;
|
|
431
|
+
if (/regular|normal/.test(wn)) return 400;
|
|
432
|
+
if (/medium/.test(wn)) return 500;
|
|
433
|
+
if (/semi bold|semibold/.test(wn)) return 600;
|
|
434
|
+
if (/extra bold|extrabold/.test(wn)) return 800;
|
|
435
|
+
if (/bold/.test(wn)) return 700;
|
|
436
|
+
if (/black|ultra/.test(wn)) return 900;
|
|
437
|
+
|
|
438
|
+
return 400;
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Sorts a map of font objects by ascending weight, with Regular before Italic at equal weights.
|
|
443
|
+
* @param {Object} fontsObjects
|
|
444
|
+
* @returns {Object}
|
|
445
|
+
*/
|
|
446
|
+
export const sortFontObjects = (fontsObjects) => {
|
|
447
|
+
return Object.fromEntries(
|
|
448
|
+
Object.entries(fontsObjects).sort((a, b) => {
|
|
449
|
+
const weightA = Number(a[1].weight);
|
|
450
|
+
const weightB = Number(b[1].weight);
|
|
451
|
+
if (weightA === weightB) {
|
|
452
|
+
if (a[1].style === 'Regular' && b[1].style === 'Italic') return -1;
|
|
453
|
+
if (a[1].style === 'Italic' && b[1].style === 'Regular') return 1;
|
|
454
|
+
return 0;
|
|
455
|
+
}
|
|
456
|
+
return weightA - weightB;
|
|
457
|
+
})
|
|
458
|
+
);
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Logs font metadata to the console for debugging.
|
|
463
|
+
* @param {string} id
|
|
464
|
+
* @param {string} fontTitle
|
|
465
|
+
* @param {Object} font
|
|
466
|
+
* @param {string} fileName
|
|
467
|
+
* @param {string} subfamilyName
|
|
468
|
+
* @param {string} style
|
|
469
|
+
* @param {string} weightName
|
|
470
|
+
* @param {boolean} variableFont
|
|
471
|
+
* @param {string[]} italicKW
|
|
472
|
+
*/
|
|
473
|
+
export const logFontInfo = (id, fontTitle, font, fileName, subfamilyName, style, weightName, variableFont, italicKW) => {
|
|
474
|
+
console.log('=== Font Info ====');
|
|
475
|
+
console.log('Font id: ', id);
|
|
476
|
+
console.log('Font title: ', fontTitle);
|
|
477
|
+
console.log('Fontkit fullName: ', font.fullName);
|
|
478
|
+
console.log('Fontkit family name: ', font.familyName);
|
|
479
|
+
console.log('File name: ', fileName);
|
|
480
|
+
console.log('Subfamily: ', subfamilyName);
|
|
481
|
+
console.log('Style: ', style);
|
|
482
|
+
console.log('Weight: ', weightName);
|
|
483
|
+
console.log('Variable: ', variableFont);
|
|
484
|
+
console.log('italicKW: ', italicKW);
|
|
485
|
+
console.log('Font italic angle: ', (font?.italicAngle !== 0 || font?.fullName.toLowerCase().includes('italic')) ? 'Italic' : 'Regular');
|
|
486
|
+
console.log('=======');
|
|
487
|
+
};
|