@pro-laico/payload-fonts 0.0.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 (88) hide show
  1. package/LICENSE.md +22 -0
  2. package/README.md +19 -0
  3. package/dist/access/authd.d.ts +19 -0
  4. package/dist/access/authd.d.ts.map +1 -0
  5. package/dist/access/authd.js +18 -0
  6. package/dist/access/authd.js.map +1 -0
  7. package/dist/collections/font.d.ts +29 -0
  8. package/dist/collections/font.d.ts.map +1 -0
  9. package/dist/collections/font.js +327 -0
  10. package/dist/collections/font.js.map +1 -0
  11. package/dist/collections/fontOptimized.d.ts +20 -0
  12. package/dist/collections/fontOptimized.d.ts.map +1 -0
  13. package/dist/collections/fontOptimized.js +111 -0
  14. package/dist/collections/fontOptimized.js.map +1 -0
  15. package/dist/collections/fontOriginal.d.ts +25 -0
  16. package/dist/collections/fontOriginal.d.ts.map +1 -0
  17. package/dist/collections/fontOriginal.js +49 -0
  18. package/dist/collections/fontOriginal.js.map +1 -0
  19. package/dist/components/DevFonts.d.ts +52 -0
  20. package/dist/components/DevFonts.d.ts.map +1 -0
  21. package/dist/components/DevFonts.js +75 -0
  22. package/dist/components/DevFonts.js.map +1 -0
  23. package/dist/components/admin/FontOriginalUpload.d.ts +15 -0
  24. package/dist/components/admin/FontOriginalUpload.d.ts.map +1 -0
  25. package/dist/components/admin/FontOriginalUpload.js +22 -0
  26. package/dist/components/admin/FontOriginalUpload.js.map +1 -0
  27. package/dist/components/admin/FontOriginalUpload.scss +9 -0
  28. package/dist/endpoints/exportFonts.d.ts +49 -0
  29. package/dist/endpoints/exportFonts.d.ts.map +1 -0
  30. package/dist/endpoints/exportFonts.js +162 -0
  31. package/dist/endpoints/exportFonts.js.map +1 -0
  32. package/dist/extractFonts.d.ts +20 -0
  33. package/dist/extractFonts.d.ts.map +1 -0
  34. package/dist/extractFonts.js +18 -0
  35. package/dist/extractFonts.js.map +1 -0
  36. package/dist/fields/font.d.ts +14 -0
  37. package/dist/fields/font.d.ts.map +1 -0
  38. package/dist/fields/font.js +37 -0
  39. package/dist/fields/font.js.map +1 -0
  40. package/dist/globals/fontSet.d.ts +15 -0
  41. package/dist/globals/fontSet.d.ts.map +1 -0
  42. package/dist/globals/fontSet.js +25 -0
  43. package/dist/globals/fontSet.js.map +1 -0
  44. package/dist/hooks/optimizeFont.d.ts +56 -0
  45. package/dist/hooks/optimizeFont.d.ts.map +1 -0
  46. package/dist/hooks/optimizeFont.js +185 -0
  47. package/dist/hooks/optimizeFont.js.map +1 -0
  48. package/dist/hooks/optimizeFromOriginals.d.ts +43 -0
  49. package/dist/hooks/optimizeFromOriginals.d.ts.map +1 -0
  50. package/dist/hooks/optimizeFromOriginals.js +313 -0
  51. package/dist/hooks/optimizeFromOriginals.js.map +1 -0
  52. package/dist/index.d.ts +8 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +14 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/lib/activeFonts.d.ts +64 -0
  57. package/dist/lib/activeFonts.d.ts.map +1 -0
  58. package/dist/lib/activeFonts.js +139 -0
  59. package/dist/lib/activeFonts.js.map +1 -0
  60. package/dist/lib/families.d.ts +38 -0
  61. package/dist/lib/families.d.ts.map +1 -0
  62. package/dist/lib/families.js +44 -0
  63. package/dist/lib/families.js.map +1 -0
  64. package/dist/lib/mergeConfig.d.ts +28 -0
  65. package/dist/lib/mergeConfig.d.ts.map +1 -0
  66. package/dist/lib/mergeConfig.js +73 -0
  67. package/dist/lib/mergeConfig.js.map +1 -0
  68. package/dist/lib/refs.d.ts +8 -0
  69. package/dist/lib/refs.d.ts.map +1 -0
  70. package/dist/lib/refs.js +11 -0
  71. package/dist/lib/refs.js.map +1 -0
  72. package/dist/lib/uploadBytes.d.ts +26 -0
  73. package/dist/lib/uploadBytes.d.ts.map +1 -0
  74. package/dist/lib/uploadBytes.js +98 -0
  75. package/dist/lib/uploadBytes.js.map +1 -0
  76. package/dist/plugin.d.ts +57 -0
  77. package/dist/plugin.d.ts.map +1 -0
  78. package/dist/plugin.js +93 -0
  79. package/dist/plugin.js.map +1 -0
  80. package/dist/scripts/cli.d.ts +3 -0
  81. package/dist/scripts/cli.d.ts.map +1 -0
  82. package/dist/scripts/cli.js +13 -0
  83. package/dist/scripts/cli.js.map +1 -0
  84. package/dist/scripts/downloadFonts.d.ts +37 -0
  85. package/dist/scripts/downloadFonts.d.ts.map +1 -0
  86. package/dist/scripts/downloadFonts.js +216 -0
  87. package/dist/scripts/downloadFonts.js.map +1 -0
  88. package/package.json +98 -0
@@ -0,0 +1,313 @@
1
+ import { refId } from "../lib/refs.js";
2
+ import { readUploadBytes } from "../lib/uploadBytes.js";
3
+ import { detectMetadata, isSubsetterLoadError, resolveCharsetText, subsetToWoff2 } from "./optimizeFont.js";
4
+ /** True when a delete failed because the doc is already gone — the goal state, not a problem.
5
+ * Happens routinely when another path deleted it first (e.g. a seed run clears `fontOriginal`
6
+ * directly, then clearing `font` fires this cascade at the same ids). */ const isNotFound = (err)=>err?.status === 404 || err instanceof Error && err.name === 'NotFound';
7
+ // Surface the bundled-subsetter load failure (see isSubsetterLoadError) as ONE loud, actionable
8
+ // log instead of only the generic per-font warning.
9
+ let warnedSubsetterLoad = false;
10
+ /**
11
+ * Every `fontOriginal` id a typeface doc references across all of its slots. Each original
12
+ * belongs to exactly one typeface — enforced by the create-only upload slots (UI) and the
13
+ * `rejectSharedOriginals` guard (data layer) — so cleanup can delete a de-referenced or
14
+ * deleted original outright, with no shared-original / concurrent-delete hazard.
15
+ */ export const originalIdsFromDoc = (data)=>{
16
+ const ids = [];
17
+ const variable = data.variable ?? {};
18
+ for (const r of [
19
+ variable.upright,
20
+ variable.italic
21
+ ]){
22
+ const id = refId(r);
23
+ if (id != null) ids.push(id);
24
+ }
25
+ for (const row of Array.isArray(data.weights) ? data.weights : []){
26
+ const id = refId(row.file);
27
+ if (id != null) ids.push(id);
28
+ }
29
+ return ids;
30
+ };
31
+ /** Read the font doc's slots into the set of originals to optimize. */ const desiredFromDoc = (data)=>{
32
+ const desired = [];
33
+ const variable = data.variable ?? {};
34
+ const uprightId = refId(variable.upright);
35
+ const italicId = refId(variable.italic);
36
+ if (uprightId != null) desired.push({
37
+ originalId: uprightId,
38
+ style: 'normal',
39
+ isVariable: true
40
+ });
41
+ if (italicId != null) desired.push({
42
+ originalId: italicId,
43
+ style: 'italic',
44
+ isVariable: true
45
+ });
46
+ // Variable and weights are mutually exclusive (enforced on the collection); only read the
47
+ // weights array when no variable file is present.
48
+ if (uprightId == null && italicId == null) {
49
+ const weights = Array.isArray(data.weights) ? data.weights : [];
50
+ for (const row of weights){
51
+ const fid = refId(row.file);
52
+ if (fid != null) desired.push({
53
+ originalId: fid,
54
+ style: row.style === 'italic' ? 'italic' : 'normal',
55
+ isVariable: false,
56
+ weight: row.weight
57
+ });
58
+ }
59
+ }
60
+ return desired;
61
+ };
62
+ /**
63
+ * `afterChange` for the `Font` typeface: reconcile its served `fontOptimized` files
64
+ * against the `fontOriginal` files referenced in its slots.
65
+ *
66
+ * Keyed by source-original id: a newly-referenced original is fetched, subsetted to WOFF2
67
+ * (variable weight range read from the `wght` axis; static weight/style from the row) and
68
+ * created; an original that's gone has its optimized deleted; an unchanged original is kept
69
+ * (only its weight/style metadata is synced if the row changed). Best-effort — a single bad
70
+ * font logs a warning and is skipped, never failing the typeface save. Touches only
71
+ * `fontOptimized`, so it can't re-trigger this hook.
72
+ */ export const optimizeFromOriginalsHook = (opts = {})=>{
73
+ const charsetText = resolveCharsetText(opts.charset);
74
+ const originalSlug = opts.originalSlug || 'fontOriginal';
75
+ const optimizedSlug = opts.optimizedSlug || 'fontOptimized';
76
+ return async ({ doc, operation, previousDoc, req })=>{
77
+ const data = doc;
78
+ const fontId = data.id;
79
+ const desired = desiredFromDoc(data);
80
+ // Forward the request's auth so reading the original succeeds when it lives on cloud
81
+ // storage served through Payload's own access-controlled file route — an unauthenticated
82
+ // fetch 403s. `origin`/`referer` are required too: Payload's CSRF protection rejects a
83
+ // cookie-authenticated request whose Origin isn't allow-listed.
84
+ const fwdHeaders = {};
85
+ for (const h of [
86
+ 'cookie',
87
+ 'authorization',
88
+ 'origin',
89
+ 'referer'
90
+ ]){
91
+ const v = req.headers?.get(h);
92
+ if (v) fwdHeaders[h] = v;
93
+ }
94
+ try {
95
+ const existing = await req.payload.find({
96
+ collection: optimizedSlug,
97
+ where: {
98
+ font: {
99
+ equals: fontId
100
+ }
101
+ },
102
+ depth: 0,
103
+ limit: 1000,
104
+ overrideAccess: true,
105
+ req
106
+ });
107
+ const byOriginal = new Map();
108
+ for (const d of existing.docs){
109
+ const oid = refId(d.original);
110
+ if (oid != null) byOriginal.set(oid, d);
111
+ }
112
+ const desiredIds = new Set(desired.map((d)=>d.originalId));
113
+ // Create new, or sync metadata on a kept one whose row changed.
114
+ for (const d of desired){
115
+ const current = byOriginal.get(d.originalId);
116
+ if (current) {
117
+ const weightDrift = !d.isVariable && d.weight && String(current.weight ?? '') !== d.weight;
118
+ if (weightDrift || String(current.style ?? '') !== d.style) {
119
+ await req.payload.update({
120
+ collection: optimizedSlug,
121
+ id: current.id,
122
+ data: {
123
+ weight: d.weight ?? current.weight,
124
+ style: d.style
125
+ },
126
+ overrideAccess: true,
127
+ req
128
+ });
129
+ }
130
+ continue;
131
+ }
132
+ try {
133
+ const original = await req.payload.findByID({
134
+ collection: originalSlug,
135
+ id: d.originalId,
136
+ depth: 0,
137
+ overrideAccess: true,
138
+ req
139
+ });
140
+ const bytes = await readUploadBytes(req.payload, originalSlug, original, {
141
+ headers: fwdHeaders
142
+ });
143
+ if (!bytes) {
144
+ req.payload.logger.warn(`Font optimize: could not read original ${d.originalId}`);
145
+ continue;
146
+ }
147
+ const meta = await detectMetadata(bytes);
148
+ const woff2 = await subsetToWoff2(bytes, charsetText);
149
+ // Variable: the intrinsic axis range wins. Static: the row's weight wins, detection fills a blank.
150
+ const weight = d.isVariable ? meta?.weight ?? d.weight : d.weight ?? meta?.weight;
151
+ // An upright variable file whose axes also cover italics (ital / negative slnt) is
152
+ // flagged so the serving layers can emit an italic face from the same file.
153
+ const italCapable = Boolean(d.style === 'normal' && meta?.italCapable);
154
+ const baseName = (original.filename || `font-${d.originalId}`).replace(/\.[^.]+$/, '');
155
+ // Shallow-cloned req: createLocalReq mutates `.file` on the req it's given; the clone
156
+ // keeps that off the parent while sharing the transaction.
157
+ await req.payload.create({
158
+ collection: optimizedSlug,
159
+ req: {
160
+ ...req
161
+ },
162
+ overrideAccess: true,
163
+ data: {
164
+ font: fontId,
165
+ original: d.originalId,
166
+ weight,
167
+ style: d.style,
168
+ isVariable: meta?.isVariable ?? d.isVariable,
169
+ italCapable,
170
+ ...italCapable && meta?.obliqueAngle ? {
171
+ obliqueAngle: meta.obliqueAngle
172
+ } : {}
173
+ },
174
+ file: {
175
+ data: woff2,
176
+ name: `${baseName}.woff2`,
177
+ mimetype: 'font/woff2',
178
+ size: woff2.length
179
+ }
180
+ });
181
+ } catch (err) {
182
+ if (isSubsetterLoadError(err) && !warnedSubsetterLoad) {
183
+ warnedSubsetterLoad = true;
184
+ req.payload.logger.error("[payload-fonts] The font subsetter failed to LOAD (subset-font / harfbuzz wasm / fontkit) — uploaded fonts are NOT being subsetted, so no web fonts will be served. In Next.js the wasm/native deps were bundled: add `serverExternalPackages: ['subset-font', 'harfbuzzjs', 'fontkit']` to your next.config. Docs: https://payload-plugins.prolaico.com/docs/plugins/payload-fonts");
185
+ }
186
+ req.payload.logger.warn({
187
+ msg: `Font optimization failed for original ${d.originalId}`,
188
+ err
189
+ });
190
+ }
191
+ }
192
+ // Delete optimized files whose source original is no longer referenced.
193
+ for (const [oid, d] of byOriginal){
194
+ if (desiredIds.has(oid)) continue;
195
+ try {
196
+ await req.payload.delete({
197
+ collection: optimizedSlug,
198
+ id: d.id,
199
+ overrideAccess: true,
200
+ req
201
+ });
202
+ } catch (err) {
203
+ if (!isNotFound(err)) req.payload.logger.warn({
204
+ msg: 'Could not delete stale optimized font',
205
+ err
206
+ });
207
+ }
208
+ }
209
+ // Delete originals this update de-referenced (a swapped/removed slot file). Originals
210
+ // aren't shared (create-only slots), so this is always safe.
211
+ if (operation === 'update' && previousDoc) {
212
+ const stillReferenced = new Set(originalIdsFromDoc(data));
213
+ for (const oid of originalIdsFromDoc(previousDoc)){
214
+ if (stillReferenced.has(oid)) continue;
215
+ try {
216
+ await req.payload.delete({
217
+ collection: originalSlug,
218
+ id: oid,
219
+ overrideAccess: true,
220
+ req
221
+ });
222
+ } catch (err) {
223
+ if (!isNotFound(err)) req.payload.logger.warn({
224
+ msg: `Could not delete de-referenced font original ${oid}`,
225
+ err
226
+ });
227
+ }
228
+ }
229
+ }
230
+ } catch (err) {
231
+ req.payload.logger.warn({
232
+ msg: 'Font optimize reconcile failed',
233
+ err
234
+ });
235
+ }
236
+ return doc;
237
+ };
238
+ };
239
+ /**
240
+ * `beforeDelete` for the `Font` typeface: cascade-delete its served `fontOptimized` files and
241
+ * the `fontOriginal` files its slots referenced, so nothing orphans in storage. Best-effort.
242
+ *
243
+ * It runs `beforeDelete` (not after) on purpose: deleting the `font` doc triggers Payload's
244
+ * dangling-reference cleanup, which nulls `fontOptimized.font` — so by `afterDelete` the served
245
+ * files can no longer be found by their owning typeface. Here the relationship is still intact,
246
+ * so we resolve them by `font` first (and read the originals off the doc's own upload slots).
247
+ */ export const cleanupFontAssetsHook = (opts = {})=>{
248
+ const originalSlug = opts.originalSlug || 'fontOriginal';
249
+ const optimizedSlug = opts.optimizedSlug || 'fontOptimized';
250
+ return async ({ collection, id, req })=>{
251
+ // Load the doc so we can read its `fontOriginal` slot ids (the font's own upload fields).
252
+ let data;
253
+ try {
254
+ data = await req.payload.findByID({
255
+ collection: collection.slug,
256
+ id,
257
+ depth: 0,
258
+ overrideAccess: true,
259
+ req
260
+ });
261
+ } catch {
262
+ // gone already / not found — fall through, optimized cleanup below still runs by `font`
263
+ }
264
+ // Delete the served files, found by owning typeface while the relationship still exists.
265
+ try {
266
+ const optimized = await req.payload.find({
267
+ collection: optimizedSlug,
268
+ where: {
269
+ font: {
270
+ equals: id
271
+ }
272
+ },
273
+ depth: 0,
274
+ limit: 1000,
275
+ overrideAccess: true,
276
+ req
277
+ });
278
+ for (const d of optimized.docs){
279
+ await req.payload.delete({
280
+ collection: optimizedSlug,
281
+ id: d.id,
282
+ overrideAccess: true,
283
+ req
284
+ });
285
+ }
286
+ } catch (err) {
287
+ req.payload.logger.warn({
288
+ msg: 'Could not delete optimized fonts',
289
+ err
290
+ });
291
+ }
292
+ // Delete the originals this typeface referenced (create-only slots → never shared).
293
+ if (data) {
294
+ for (const oid of originalIdsFromDoc(data)){
295
+ try {
296
+ await req.payload.delete({
297
+ collection: originalSlug,
298
+ id: oid,
299
+ overrideAccess: true,
300
+ req
301
+ });
302
+ } catch (err) {
303
+ if (!isNotFound(err)) req.payload.logger.warn({
304
+ msg: 'Could not delete font original',
305
+ err
306
+ });
307
+ }
308
+ }
309
+ }
310
+ };
311
+ };
312
+
313
+ //# sourceMappingURL=optimizeFromOriginals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/optimizeFromOriginals.ts"],"sourcesContent":["import type { CollectionAfterChangeHook, CollectionBeforeDeleteHook, CollectionSlug } from 'payload'\n\nimport { refId } from '../lib/refs'\nimport { readUploadBytes } from '../lib/uploadBytes'\nimport { type Charset, detectMetadata, isSubsetterLoadError, resolveCharsetText, subsetToWoff2 } from './optimizeFont'\n\ntype Ref = string | number | { id?: string | number } | null | undefined\n\n/** True when a delete failed because the doc is already gone — the goal state, not a problem.\n * Happens routinely when another path deleted it first (e.g. a seed run clears `fontOriginal`\n * directly, then clearing `font` fires this cascade at the same ids). */\nconst isNotFound = (err: unknown): boolean => (err as { status?: number })?.status === 404 || (err instanceof Error && err.name === 'NotFound')\n\n// Surface the bundled-subsetter load failure (see isSubsetterLoadError) as ONE loud, actionable\n// log instead of only the generic per-font warning.\nlet warnedSubsetterLoad = false\n\n/**\n * Every `fontOriginal` id a typeface doc references across all of its slots. Each original\n * belongs to exactly one typeface — enforced by the create-only upload slots (UI) and the\n * `rejectSharedOriginals` guard (data layer) — so cleanup can delete a de-referenced or\n * deleted original outright, with no shared-original / concurrent-delete hazard.\n */\nexport const originalIdsFromDoc = (data: Record<string, unknown>): Array<string | number> => {\n const ids: Array<string | number> = []\n const variable = (data.variable ?? {}) as { upright?: Ref; italic?: Ref }\n for (const r of [variable.upright, variable.italic]) {\n const id = refId(r)\n if (id != null) ids.push(id)\n }\n for (const row of (Array.isArray(data.weights) ? data.weights : []) as Array<{ file?: Ref }>) {\n const id = refId(row.file)\n if (id != null) ids.push(id)\n }\n return ids\n}\n\n/** A weight/style file the typeface should have an optimized WOFF2 for. */\ninterface Desired {\n originalId: string | number\n style: 'normal' | 'italic'\n isVariable: boolean\n /** Static rows carry an explicit weight; variable files derive a range from the binary. */\n weight?: string\n}\n\n/** Read the font doc's slots into the set of originals to optimize. */\nconst desiredFromDoc = (data: Record<string, unknown>): Desired[] => {\n const desired: Desired[] = []\n const variable = (data.variable ?? {}) as { upright?: Ref; italic?: Ref }\n const uprightId = refId(variable.upright)\n const italicId = refId(variable.italic)\n if (uprightId != null) desired.push({ originalId: uprightId, style: 'normal', isVariable: true })\n if (italicId != null) desired.push({ originalId: italicId, style: 'italic', isVariable: true })\n // Variable and weights are mutually exclusive (enforced on the collection); only read the\n // weights array when no variable file is present.\n if (uprightId == null && italicId == null) {\n const weights = Array.isArray(data.weights) ? data.weights : []\n for (const row of weights as Array<{ weight?: string; style?: string; file?: Ref }>) {\n const fid = refId(row.file)\n if (fid != null)\n desired.push({ originalId: fid, style: row.style === 'italic' ? 'italic' : 'normal', isVariable: false, weight: row.weight })\n }\n }\n return desired\n}\n\nexport interface OptimizeFromOriginalsOptions {\n /** Characters to keep, or a preset name ('latin' | 'latin-ext'). Default 'latin'. */\n charset?: Charset\n /** Slug of the archival original collection. Default 'fontOriginal'. */\n originalSlug?: string\n /** Slug of the optimized (served) collection. Default 'fontOptimized'. */\n optimizedSlug?: string\n}\n\n/**\n * `afterChange` for the `Font` typeface: reconcile its served `fontOptimized` files\n * against the `fontOriginal` files referenced in its slots.\n *\n * Keyed by source-original id: a newly-referenced original is fetched, subsetted to WOFF2\n * (variable weight range read from the `wght` axis; static weight/style from the row) and\n * created; an original that's gone has its optimized deleted; an unchanged original is kept\n * (only its weight/style metadata is synced if the row changed). Best-effort — a single bad\n * font logs a warning and is skipped, never failing the typeface save. Touches only\n * `fontOptimized`, so it can't re-trigger this hook.\n */\nexport const optimizeFromOriginalsHook = (opts: OptimizeFromOriginalsOptions = {}): CollectionAfterChangeHook => {\n const charsetText = resolveCharsetText(opts.charset)\n const originalSlug = (opts.originalSlug || 'fontOriginal') as CollectionSlug\n const optimizedSlug = (opts.optimizedSlug || 'fontOptimized') as CollectionSlug\n\n return async ({ doc, operation, previousDoc, req }) => {\n const data = doc as Record<string, unknown>\n const fontId = data.id as string | number\n const desired = desiredFromDoc(data)\n\n // Forward the request's auth so reading the original succeeds when it lives on cloud\n // storage served through Payload's own access-controlled file route — an unauthenticated\n // fetch 403s. `origin`/`referer` are required too: Payload's CSRF protection rejects a\n // cookie-authenticated request whose Origin isn't allow-listed.\n const fwdHeaders: Record<string, string> = {}\n for (const h of ['cookie', 'authorization', 'origin', 'referer']) {\n const v = req.headers?.get(h)\n if (v) fwdHeaders[h] = v\n }\n\n try {\n const existing = await req.payload.find({\n collection: optimizedSlug,\n where: { font: { equals: fontId } },\n depth: 0,\n limit: 1000,\n overrideAccess: true,\n req,\n })\n const byOriginal = new Map<string | number, Record<string, unknown>>()\n for (const d of existing.docs as unknown as Array<Record<string, unknown>>) {\n const oid = refId(d.original as Ref)\n if (oid != null) byOriginal.set(oid, d)\n }\n const desiredIds = new Set(desired.map((d) => d.originalId))\n\n // Create new, or sync metadata on a kept one whose row changed.\n for (const d of desired) {\n const current = byOriginal.get(d.originalId)\n if (current) {\n const weightDrift = !d.isVariable && d.weight && String(current.weight ?? '') !== d.weight\n if (weightDrift || String(current.style ?? '') !== d.style) {\n await req.payload.update({\n collection: optimizedSlug,\n id: current.id as string | number,\n data: { weight: d.weight ?? current.weight, style: d.style } as never,\n overrideAccess: true,\n req,\n })\n }\n continue\n }\n try {\n const original = (await req.payload.findByID({\n collection: originalSlug,\n id: d.originalId,\n depth: 0,\n overrideAccess: true,\n req,\n })) as {\n filename?: string | null\n url?: string | null\n }\n const bytes = await readUploadBytes(req.payload, originalSlug, original, { headers: fwdHeaders })\n if (!bytes) {\n req.payload.logger.warn(`Font optimize: could not read original ${d.originalId}`)\n continue\n }\n const meta = await detectMetadata(bytes)\n const woff2 = await subsetToWoff2(bytes, charsetText)\n // Variable: the intrinsic axis range wins. Static: the row's weight wins, detection fills a blank.\n const weight = d.isVariable ? (meta?.weight ?? d.weight) : (d.weight ?? meta?.weight)\n // An upright variable file whose axes also cover italics (ital / negative slnt) is\n // flagged so the serving layers can emit an italic face from the same file.\n const italCapable = Boolean(d.style === 'normal' && meta?.italCapable)\n const baseName = (original.filename || `font-${d.originalId}`).replace(/\\.[^.]+$/, '')\n // Shallow-cloned req: createLocalReq mutates `.file` on the req it's given; the clone\n // keeps that off the parent while sharing the transaction.\n await req.payload.create({\n collection: optimizedSlug,\n req: { ...req },\n overrideAccess: true,\n data: {\n font: fontId,\n original: d.originalId,\n weight,\n style: d.style,\n isVariable: meta?.isVariable ?? d.isVariable,\n italCapable,\n ...(italCapable && meta?.obliqueAngle ? { obliqueAngle: meta.obliqueAngle } : {}),\n } as never,\n file: { data: woff2, name: `${baseName}.woff2`, mimetype: 'font/woff2', size: woff2.length },\n })\n } catch (err) {\n if (isSubsetterLoadError(err) && !warnedSubsetterLoad) {\n warnedSubsetterLoad = true\n req.payload.logger.error(\n \"[payload-fonts] The font subsetter failed to LOAD (subset-font / harfbuzz wasm / fontkit) — uploaded fonts are NOT being subsetted, so no web fonts will be served. In Next.js the wasm/native deps were bundled: add `serverExternalPackages: ['subset-font', 'harfbuzzjs', 'fontkit']` to your next.config. Docs: https://payload-plugins.prolaico.com/docs/plugins/payload-fonts\",\n )\n }\n req.payload.logger.warn({ msg: `Font optimization failed for original ${d.originalId}`, err })\n }\n }\n\n // Delete optimized files whose source original is no longer referenced.\n for (const [oid, d] of byOriginal) {\n if (desiredIds.has(oid)) continue\n try {\n await req.payload.delete({ collection: optimizedSlug, id: d.id as string | number, overrideAccess: true, req })\n } catch (err) {\n if (!isNotFound(err)) req.payload.logger.warn({ msg: 'Could not delete stale optimized font', err })\n }\n }\n\n // Delete originals this update de-referenced (a swapped/removed slot file). Originals\n // aren't shared (create-only slots), so this is always safe.\n if (operation === 'update' && previousDoc) {\n const stillReferenced = new Set(originalIdsFromDoc(data))\n for (const oid of originalIdsFromDoc(previousDoc as Record<string, unknown>)) {\n if (stillReferenced.has(oid)) continue\n try {\n await req.payload.delete({ collection: originalSlug, id: oid, overrideAccess: true, req })\n } catch (err) {\n if (!isNotFound(err)) req.payload.logger.warn({ msg: `Could not delete de-referenced font original ${oid}`, err })\n }\n }\n }\n } catch (err) {\n req.payload.logger.warn({ msg: 'Font optimize reconcile failed', err })\n }\n return doc\n }\n}\n\n/**\n * `beforeDelete` for the `Font` typeface: cascade-delete its served `fontOptimized` files and\n * the `fontOriginal` files its slots referenced, so nothing orphans in storage. Best-effort.\n *\n * It runs `beforeDelete` (not after) on purpose: deleting the `font` doc triggers Payload's\n * dangling-reference cleanup, which nulls `fontOptimized.font` — so by `afterDelete` the served\n * files can no longer be found by their owning typeface. Here the relationship is still intact,\n * so we resolve them by `font` first (and read the originals off the doc's own upload slots).\n */\nexport const cleanupFontAssetsHook = (opts: { originalSlug?: string; optimizedSlug?: string } = {}): CollectionBeforeDeleteHook => {\n const originalSlug = (opts.originalSlug || 'fontOriginal') as CollectionSlug\n const optimizedSlug = (opts.optimizedSlug || 'fontOptimized') as CollectionSlug\n\n return async ({ collection, id, req }) => {\n // Load the doc so we can read its `fontOriginal` slot ids (the font's own upload fields).\n let data: Record<string, unknown> | undefined\n try {\n data = (await req.payload.findByID({\n collection: collection.slug as CollectionSlug,\n id,\n depth: 0,\n overrideAccess: true,\n req,\n })) as unknown as Record<string, unknown>\n } catch {\n // gone already / not found — fall through, optimized cleanup below still runs by `font`\n }\n\n // Delete the served files, found by owning typeface while the relationship still exists.\n try {\n const optimized = await req.payload.find({\n collection: optimizedSlug,\n where: { font: { equals: id } },\n depth: 0,\n limit: 1000,\n overrideAccess: true,\n req,\n })\n for (const d of optimized.docs as Array<{ id: string | number }>) {\n await req.payload.delete({ collection: optimizedSlug, id: d.id, overrideAccess: true, req })\n }\n } catch (err) {\n req.payload.logger.warn({ msg: 'Could not delete optimized fonts', err })\n }\n\n // Delete the originals this typeface referenced (create-only slots → never shared).\n if (data) {\n for (const oid of originalIdsFromDoc(data)) {\n try {\n await req.payload.delete({ collection: originalSlug, id: oid, overrideAccess: true, req })\n } catch (err) {\n if (!isNotFound(err)) req.payload.logger.warn({ msg: 'Could not delete font original', err })\n }\n }\n }\n }\n}\n"],"names":["refId","readUploadBytes","detectMetadata","isSubsetterLoadError","resolveCharsetText","subsetToWoff2","isNotFound","err","status","Error","name","warnedSubsetterLoad","originalIdsFromDoc","data","ids","variable","r","upright","italic","id","push","row","Array","isArray","weights","file","desiredFromDoc","desired","uprightId","italicId","originalId","style","isVariable","fid","weight","optimizeFromOriginalsHook","opts","charsetText","charset","originalSlug","optimizedSlug","doc","operation","previousDoc","req","fontId","fwdHeaders","h","v","headers","get","existing","payload","find","collection","where","font","equals","depth","limit","overrideAccess","byOriginal","Map","d","docs","oid","original","set","desiredIds","Set","map","current","weightDrift","String","update","findByID","bytes","logger","warn","meta","woff2","italCapable","Boolean","baseName","filename","replace","create","obliqueAngle","mimetype","size","length","error","msg","has","delete","stillReferenced","cleanupFontAssetsHook","slug","optimized"],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export { default, fontsPlugin } from './plugin';
2
+ export { DEFAULT_FONT_FAMILIES } from './lib/families';
3
+ export type { FontFamilyConfig } from './lib/families';
4
+ export { extractFonts } from './extractFonts';
5
+ export { buildFontFaceCss, getActiveFontFaces } from './lib/activeFonts';
6
+ export type { ActiveFace, ActiveTypeface } from './lib/activeFonts';
7
+ export type { ExportFamilyDiagnostics, ExportFontsResponse } from './endpoints/exportFonts';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAI/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AACtD,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAOtD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACxE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAGnE,YAAY,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ // The plugin
2
+ export { default, fontsPlugin } from "./plugin.js";
3
+ // Font families (slots). `FontFamilyConfig` is the `{ key, label?, fallback? }` shape you pass to
4
+ // `fontsPlugin({ families })`; spread `DEFAULT_FONT_FAMILIES` to extend the built-in set.
5
+ export { DEFAULT_FONT_FAMILIES } from "./lib/families.js";
6
+ // Frontend serving. The dev-only runtime component lives at the `@pro-laico/payload-fonts/DevFonts`
7
+ // subpath; `extractFonts` collects the generated next/font/local classes for `<html>`;
8
+ // `getActiveFontFaces` resolves the active `fontSet` selection to its served files (the read behind
9
+ // DevFonts, for custom UIs that list or serve the active fonts) and `buildFontFaceCss` turns them
10
+ // into `@font-face` rules + family variables. `ActiveTypeface`/`ActiveFace` are their shared shape.
11
+ export { extractFonts } from "./extractFonts.js";
12
+ export { buildFontFaceCss, getActiveFontFaces } from "./lib/activeFonts.js";
13
+
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// The plugin\nexport { default, fontsPlugin } from './plugin'\n\n// Font families (slots). `FontFamilyConfig` is the `{ key, label?, fallback? }` shape you pass to\n// `fontsPlugin({ families })`; spread `DEFAULT_FONT_FAMILIES` to extend the built-in set.\nexport { DEFAULT_FONT_FAMILIES } from './lib/families'\nexport type { FontFamilyConfig } from './lib/families'\n\n// Frontend serving. The dev-only runtime component lives at the `@pro-laico/payload-fonts/DevFonts`\n// subpath; `extractFonts` collects the generated next/font/local classes for `<html>`;\n// `getActiveFontFaces` resolves the active `fontSet` selection to its served files (the read behind\n// DevFonts, for custom UIs that list or serve the active fonts) and `buildFontFaceCss` turns them\n// into `@font-face` rules + family variables. `ActiveTypeface`/`ActiveFace` are their shared shape.\nexport { extractFonts } from './extractFonts'\nexport { buildFontFaceCss, getActiveFontFaces } from './lib/activeFonts'\nexport type { ActiveFace, ActiveTypeface } from './lib/activeFonts'\n\n// Response shape of `GET /api/fonts/export` — for custom consumers of the endpoint.\nexport type { ExportFamilyDiagnostics, ExportFontsResponse } from './endpoints/exportFonts'\n"],"names":["default","fontsPlugin","DEFAULT_FONT_FAMILIES","extractFonts","buildFontFaceCss","getActiveFontFaces"],"mappings":"AAAA,aAAa;AACb,SAASA,OAAO,EAAEC,WAAW,QAAQ,cAAU;AAE/C,kGAAkG;AAClG,0FAA0F;AAC1F,SAASC,qBAAqB,QAAQ,oBAAgB;AAGtD,oGAAoG;AACpG,uFAAuF;AACvF,oGAAoG;AACpG,kGAAkG;AAClG,oGAAoG;AACpG,SAASC,YAAY,QAAQ,oBAAgB;AAC7C,SAASC,gBAAgB,EAAEC,kBAAkB,QAAQ,uBAAmB"}
@@ -0,0 +1,64 @@
1
+ import type { Payload } from 'payload';
2
+ /** A family key. `sans`/`serif`/`mono`/`display` by default, but any string when customised. */
3
+ export type FontFamily = string;
4
+ /** One served face. Usually one per `fontOptimized` doc — but an upright variable file whose
5
+ * axes also cover italics (`italCapable`) expands into a second, italic face over the SAME file. */
6
+ export interface ActiveFace {
7
+ filename: string;
8
+ /** A single CSS weight ('400') or a variable range ('100 900'). */
9
+ weight: string;
10
+ style: 'normal' | 'italic';
11
+ /** For slnt-based italics: the positive CSS `oblique` angle (deg). Absent = a true italic
12
+ * (explicit file, or an `ital` axis that `font-style: italic` activates). */
13
+ obliqueAngle?: number;
14
+ }
15
+ /** A raw `fontOptimized` doc's face fields, before ital-capability expansion. */
16
+ type RawFace = ActiveFace & {
17
+ italCapable?: boolean;
18
+ };
19
+ /**
20
+ * Expand one typeface's raw faces into the served set: an upright, ital-capable variable face
21
+ * contributes an extra italic face over the same file — unless the typeface already has an
22
+ * explicit italic file, which always wins. Exported for reuse (the export endpoint applies the
23
+ * same rule) and for tests.
24
+ */
25
+ export declare function expandItalCapableFaces(faces: RawFace[]): ActiveFace[];
26
+ /** The typeface active for a family, plus its served faces. */
27
+ export interface ActiveTypeface {
28
+ family: FontFamily;
29
+ id: string | number;
30
+ faces: ActiveFace[];
31
+ }
32
+ export interface GetActiveFontFacesOptions {
33
+ /** Slug of the standalone font-selection global. @default 'fontSet' */
34
+ fontSetSlug?: string;
35
+ /** Slug of the optimized (served) upload collection. @default 'fontOptimized' */
36
+ optimizedSlug?: string;
37
+ /** Family keys to read. Omit to auto-discover them from the `fontSet` global's own slots. */
38
+ families?: FontFamily[];
39
+ }
40
+ /**
41
+ * Resolve the active `fontSet` selection to each family's served `fontOptimized` files. Used by the
42
+ * {@link DevFonts} component (and available for custom font-serving) to build `@font-face` rules
43
+ * from CMS data at runtime. Returns `[]` if no global / no selection.
44
+ */
45
+ export declare function getActiveFontFaces(payload: Payload, opts?: GetActiveFontFacesOptions): Promise<ActiveTypeface[]>;
46
+ export interface BuildFontFaceCssOptions {
47
+ /** Prefix for the emitted CSS family variables; the capitalised family is appended (`--font-setSans`).
48
+ * Must match the download CLI's `cssVariablePrefix`. @default '--font-set' */
49
+ cssVarPrefix?: string;
50
+ /** Slug used to build the served file URL. @default 'fontOptimized' */
51
+ optimizedSlug?: string;
52
+ /** Per-family CSS fallback stack override (`{ brand: 'Georgia, serif' }`). Falls back to the
53
+ * built-in family defaults, then a generic sans stack. */
54
+ fallbacks?: Record<string, string>;
55
+ }
56
+ /**
57
+ * Build the `@font-face` rules + the `:root` family-variable mapping for a set of active typefaces.
58
+ * Pure (no IO) so it's easy to test; {@link DevFonts} wraps it with the CMS read. The family
59
+ * variables (`--font-setSans`, …) point at each typeface's served family, so the same
60
+ * `font-family: var(--font-setSans)` your app uses in production resolves identically in dev.
61
+ */
62
+ export declare function buildFontFaceCss(typefaces: ActiveTypeface[], opts?: BuildFontFaceCssOptions): string;
63
+ export {};
64
+ //# sourceMappingURL=activeFonts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activeFonts.d.ts","sourceRoot":"","sources":["../../src/lib/activeFonts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA8B,OAAO,EAAE,MAAM,SAAS,CAAA;AAKlE,gGAAgG;AAChG,MAAM,MAAM,UAAU,GAAG,MAAM,CAAA;AAE/B;qGACqG;AACrG,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC1B;kFAC8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,iFAAiF;AACjF,KAAK,OAAO,GAAG,UAAU,GAAG;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAErD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,UAAU,EAAE,CAUrE;AAED,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,UAAU,CAAA;IAClB,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;IACnB,KAAK,EAAE,UAAU,EAAE,CAAA;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iFAAiF;IACjF,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAA;CACxB;AAQD;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE,yBAA8B,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAoD1H;AAWD,MAAM,WAAW,uBAAuB;IACtC;mFAC+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;+DAC2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACnC;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,cAAc,EAAE,EAAE,IAAI,GAAE,uBAA4B,GAAG,MAAM,CAmBxG"}
@@ -0,0 +1,139 @@
1
+ import { refId } from "./refs.js";
2
+ import { DEFAULT_FONT_FAMILIES, familyVarSuffix } from "./families.js";
3
+ /**
4
+ * Expand one typeface's raw faces into the served set: an upright, ital-capable variable face
5
+ * contributes an extra italic face over the same file — unless the typeface already has an
6
+ * explicit italic file, which always wins. Exported for reuse (the export endpoint applies the
7
+ * same rule) and for tests.
8
+ */ export function expandItalCapableFaces(faces) {
9
+ const hasExplicitItalic = faces.some((f)=>f.style === 'italic');
10
+ return faces.flatMap(({ italCapable, ...face })=>{
11
+ // obliqueAngle is only meaningful on the synthesized italic face — never on an upright.
12
+ const { obliqueAngle: _drop, ...upright } = face;
13
+ if (face.style === 'normal' && italCapable && !hasExplicitItalic) {
14
+ return [
15
+ upright,
16
+ {
17
+ ...face,
18
+ style: 'italic'
19
+ }
20
+ ];
21
+ }
22
+ return [
23
+ face.style === 'normal' ? upright : face
24
+ ];
25
+ });
26
+ }
27
+ /** Payload global meta keys that aren't family slots — filtered out when auto-discovering. */ const GLOBAL_META_KEYS = new Set([
28
+ 'id',
29
+ 'globalType',
30
+ 'createdAt',
31
+ 'updatedAt'
32
+ ]);
33
+ /** The family slots on a `fontSet` global doc: its own keys, minus Payload's metadata. */ const familyKeysFromGlobal = (global)=>global ? Object.keys(global).filter((k)=>!k.startsWith('_') && !GLOBAL_META_KEYS.has(k)) : [];
34
+ /**
35
+ * Resolve the active `fontSet` selection to each family's served `fontOptimized` files. Used by the
36
+ * {@link DevFonts} component (and available for custom font-serving) to build `@font-face` rules
37
+ * from CMS data at runtime. Returns `[]` if no global / no selection.
38
+ */ export async function getActiveFontFaces(payload, opts = {}) {
39
+ const fontSetSlug = opts.fontSetSlug ?? 'fontSet';
40
+ const optimizedSlug = opts.optimizedSlug ?? 'fontOptimized';
41
+ let selection;
42
+ try {
43
+ // `as unknown as` — in a project with generated types findGlobal resolves to the concrete
44
+ // fontSet interface, which doesn't structurally overlap a string-keyed record.
45
+ selection = await payload.findGlobal({
46
+ slug: fontSetSlug,
47
+ depth: 0,
48
+ overrideAccess: true
49
+ });
50
+ } catch {
51
+ return [] // fontSet global not registered
52
+ ;
53
+ }
54
+ // Explicit list, else auto-discover the family slots from the global itself — so a custom
55
+ // `families` set works here with no extra config (the global's slots are the source of truth).
56
+ const families = opts.families ?? familyKeysFromGlobal(selection);
57
+ // Resolve each family's selected typeface id once, then fetch every family's served files in a
58
+ // single query (grouped by typeface) rather than one round-trip per family.
59
+ const familyIds = families.map((family)=>({
60
+ family,
61
+ id: refId(selection?.[family])
62
+ })).filter((r)=>r.id != null);
63
+ if (!familyIds.length) return [];
64
+ const uniqueIds = [
65
+ ...new Set(familyIds.map((r)=>r.id))
66
+ ];
67
+ const res = await payload.find({
68
+ collection: optimizedSlug,
69
+ where: {
70
+ font: {
71
+ in: uniqueIds
72
+ }
73
+ },
74
+ depth: 0,
75
+ limit: 1000,
76
+ overrideAccess: true
77
+ });
78
+ const facesByFont = new Map();
79
+ for (const d of res.docs){
80
+ if (typeof d.filename !== 'string') continue;
81
+ const fontId = refId(d.font);
82
+ if (fontId == null) continue;
83
+ const face = {
84
+ filename: d.filename,
85
+ weight: d.weight || '400',
86
+ style: d.style === 'italic' ? 'italic' : 'normal',
87
+ ...d.italCapable ? {
88
+ italCapable: true
89
+ } : {},
90
+ ...typeof d.obliqueAngle === 'number' ? {
91
+ obliqueAngle: d.obliqueAngle
92
+ } : {}
93
+ };
94
+ const bucket = facesByFont.get(fontId);
95
+ if (bucket) bucket.push(face);
96
+ else facesByFont.set(fontId, [
97
+ face
98
+ ]);
99
+ }
100
+ const out = [];
101
+ for (const { family, id } of familyIds){
102
+ const faces = facesByFont.get(id);
103
+ if (faces?.length) out.push({
104
+ family,
105
+ id,
106
+ faces: expandItalCapableFaces(faces)
107
+ });
108
+ }
109
+ return out;
110
+ }
111
+ /** Generic CSS fallback stack for the built-in families, appended after the served family in the
112
+ * family variable. Overridable per family via {@link BuildFontFaceCssOptions.fallbacks}. */ const GENERIC_FALLBACK = Object.fromEntries(DEFAULT_FONT_FAMILIES.map((r)=>[
113
+ r.key,
114
+ r.fallback
115
+ ]));
116
+ /** Used for a custom family with no declared fallback. */ const DEFAULT_FALLBACK = 'ui-sans-serif, system-ui, sans-serif';
117
+ /** Payload serves an upload file at `/api/<slug>/file/<filename>` (public read on `fontOptimized`). */ const faceUrl = (filename, slug)=>`/api/${slug}/file/${encodeURIComponent(filename)}`;
118
+ /**
119
+ * Build the `@font-face` rules + the `:root` family-variable mapping for a set of active typefaces.
120
+ * Pure (no IO) so it's easy to test; {@link DevFonts} wraps it with the CMS read. The family
121
+ * variables (`--font-setSans`, …) point at each typeface's served family, so the same
122
+ * `font-family: var(--font-setSans)` your app uses in production resolves identically in dev.
123
+ */ export function buildFontFaceCss(typefaces, opts = {}) {
124
+ const cssVarPrefix = opts.cssVarPrefix ?? '--font-set';
125
+ const optimizedSlug = opts.optimizedSlug ?? 'fontOptimized';
126
+ const fallbackFor = (family)=>opts.fallbacks?.[family] ?? GENERIC_FALLBACK[family] ?? DEFAULT_FALLBACK;
127
+ if (!typefaces.length) return '';
128
+ const faces = typefaces.flatMap((tf)=>tf.faces.map((f)=>{
129
+ // An italic with an oblique angle rides a `slnt` axis: `font-style: oblique <angle>`
130
+ // maps onto it per CSS Fonts 4 (as `font-style: italic` maps onto an `ital` axis), and
131
+ // italic requests fall back to oblique faces in font matching.
132
+ const fontStyle = f.style === 'italic' && f.obliqueAngle ? `oblique ${f.obliqueAngle}deg` : f.style;
133
+ return `@font-face{font-family:'pl-font-${tf.id}';src:url('${faceUrl(f.filename, optimizedSlug)}') format('woff2');font-weight:${f.weight};font-style:${fontStyle};font-display:swap;}`;
134
+ })).join('');
135
+ const vars = typefaces.map((tf)=>`${cssVarPrefix}${familyVarSuffix(tf.family)}:'pl-font-${tf.id}',${fallbackFor(tf.family)};`).join('');
136
+ return `${faces}:root{${vars}}`;
137
+ }
138
+
139
+ //# sourceMappingURL=activeFonts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/activeFonts.ts"],"sourcesContent":["import type { CollectionSlug, GlobalSlug, Payload } from 'payload'\n\nimport { refId } from './refs'\nimport { DEFAULT_FONT_FAMILIES, familyVarSuffix } from './families'\n\n/** A family key. `sans`/`serif`/`mono`/`display` by default, but any string when customised. */\nexport type FontFamily = string\n\n/** One served face. Usually one per `fontOptimized` doc — but an upright variable file whose\n * axes also cover italics (`italCapable`) expands into a second, italic face over the SAME file. */\nexport interface ActiveFace {\n filename: string\n /** A single CSS weight ('400') or a variable range ('100 900'). */\n weight: string\n style: 'normal' | 'italic'\n /** For slnt-based italics: the positive CSS `oblique` angle (deg). Absent = a true italic\n * (explicit file, or an `ital` axis that `font-style: italic` activates). */\n obliqueAngle?: number\n}\n\n/** A raw `fontOptimized` doc's face fields, before ital-capability expansion. */\ntype RawFace = ActiveFace & { italCapable?: boolean }\n\n/**\n * Expand one typeface's raw faces into the served set: an upright, ital-capable variable face\n * contributes an extra italic face over the same file — unless the typeface already has an\n * explicit italic file, which always wins. Exported for reuse (the export endpoint applies the\n * same rule) and for tests.\n */\nexport function expandItalCapableFaces(faces: RawFace[]): ActiveFace[] {\n const hasExplicitItalic = faces.some((f) => f.style === 'italic')\n return faces.flatMap(({ italCapable, ...face }) => {\n // obliqueAngle is only meaningful on the synthesized italic face — never on an upright.\n const { obliqueAngle: _drop, ...upright } = face\n if (face.style === 'normal' && italCapable && !hasExplicitItalic) {\n return [upright, { ...face, style: 'italic' as const }]\n }\n return [face.style === 'normal' ? upright : face]\n })\n}\n\n/** The typeface active for a family, plus its served faces. */\nexport interface ActiveTypeface {\n family: FontFamily\n id: string | number\n faces: ActiveFace[]\n}\n\nexport interface GetActiveFontFacesOptions {\n /** Slug of the standalone font-selection global. @default 'fontSet' */\n fontSetSlug?: string\n /** Slug of the optimized (served) upload collection. @default 'fontOptimized' */\n optimizedSlug?: string\n /** Family keys to read. Omit to auto-discover them from the `fontSet` global's own slots. */\n families?: FontFamily[]\n}\n\n/** Payload global meta keys that aren't family slots — filtered out when auto-discovering. */\nconst GLOBAL_META_KEYS = new Set(['id', 'globalType', 'createdAt', 'updatedAt'])\n/** The family slots on a `fontSet` global doc: its own keys, minus Payload's metadata. */\nconst familyKeysFromGlobal = (global: Record<string, unknown> | null | undefined): FontFamily[] =>\n global ? Object.keys(global).filter((k) => !k.startsWith('_') && !GLOBAL_META_KEYS.has(k)) : []\n\n/**\n * Resolve the active `fontSet` selection to each family's served `fontOptimized` files. Used by the\n * {@link DevFonts} component (and available for custom font-serving) to build `@font-face` rules\n * from CMS data at runtime. Returns `[]` if no global / no selection.\n */\nexport async function getActiveFontFaces(payload: Payload, opts: GetActiveFontFacesOptions = {}): Promise<ActiveTypeface[]> {\n const fontSetSlug = (opts.fontSetSlug ?? 'fontSet') as GlobalSlug\n const optimizedSlug = (opts.optimizedSlug ?? 'fontOptimized') as CollectionSlug\n\n let selection: Partial<Record<FontFamily, unknown>>\n try {\n // `as unknown as` — in a project with generated types findGlobal resolves to the concrete\n // fontSet interface, which doesn't structurally overlap a string-keyed record.\n selection = (await payload.findGlobal({ slug: fontSetSlug, depth: 0, overrideAccess: true })) as unknown as Partial<\n Record<FontFamily, unknown>\n >\n } catch {\n return [] // fontSet global not registered\n }\n\n // Explicit list, else auto-discover the family slots from the global itself — so a custom\n // `families` set works here with no extra config (the global's slots are the source of truth).\n const families = opts.families ?? familyKeysFromGlobal(selection as Record<string, unknown>)\n\n // Resolve each family's selected typeface id once, then fetch every family's served files in a\n // single query (grouped by typeface) rather than one round-trip per family.\n const familyIds = families\n .map((family) => ({ family, id: refId(selection?.[family]) }))\n .filter((r): r is { family: FontFamily; id: string | number } => r.id != null)\n if (!familyIds.length) return []\n\n const uniqueIds = [...new Set(familyIds.map((r) => r.id))]\n const res = await payload.find({ collection: optimizedSlug, where: { font: { in: uniqueIds } }, depth: 0, limit: 1000, overrideAccess: true })\n\n const facesByFont = new Map<string | number, RawFace[]>()\n for (const d of res.docs as unknown as Array<Record<string, unknown>>) {\n if (typeof d.filename !== 'string') continue\n const fontId = refId(d.font)\n if (fontId == null) continue\n const face: RawFace = {\n filename: d.filename,\n weight: (d.weight as string) || '400',\n style: d.style === 'italic' ? 'italic' : 'normal',\n ...(d.italCapable ? { italCapable: true } : {}),\n ...(typeof d.obliqueAngle === 'number' ? { obliqueAngle: d.obliqueAngle } : {}),\n }\n const bucket = facesByFont.get(fontId)\n if (bucket) bucket.push(face)\n else facesByFont.set(fontId, [face])\n }\n\n const out: ActiveTypeface[] = []\n for (const { family, id } of familyIds) {\n const faces = facesByFont.get(id)\n if (faces?.length) out.push({ family, id, faces: expandItalCapableFaces(faces) })\n }\n return out\n}\n\n/** Generic CSS fallback stack for the built-in families, appended after the served family in the\n * family variable. Overridable per family via {@link BuildFontFaceCssOptions.fallbacks}. */\nconst GENERIC_FALLBACK: Record<string, string> = Object.fromEntries(DEFAULT_FONT_FAMILIES.map((r) => [r.key, r.fallback]))\n/** Used for a custom family with no declared fallback. */\nconst DEFAULT_FALLBACK = 'ui-sans-serif, system-ui, sans-serif'\n\n/** Payload serves an upload file at `/api/<slug>/file/<filename>` (public read on `fontOptimized`). */\nconst faceUrl = (filename: string, slug: string) => `/api/${slug}/file/${encodeURIComponent(filename)}`\n\nexport interface BuildFontFaceCssOptions {\n /** Prefix for the emitted CSS family variables; the capitalised family is appended (`--font-setSans`).\n * Must match the download CLI's `cssVariablePrefix`. @default '--font-set' */\n cssVarPrefix?: string\n /** Slug used to build the served file URL. @default 'fontOptimized' */\n optimizedSlug?: string\n /** Per-family CSS fallback stack override (`{ brand: 'Georgia, serif' }`). Falls back to the\n * built-in family defaults, then a generic sans stack. */\n fallbacks?: Record<string, string>\n}\n\n/**\n * Build the `@font-face` rules + the `:root` family-variable mapping for a set of active typefaces.\n * Pure (no IO) so it's easy to test; {@link DevFonts} wraps it with the CMS read. The family\n * variables (`--font-setSans`, …) point at each typeface's served family, so the same\n * `font-family: var(--font-setSans)` your app uses in production resolves identically in dev.\n */\nexport function buildFontFaceCss(typefaces: ActiveTypeface[], opts: BuildFontFaceCssOptions = {}): string {\n const cssVarPrefix = opts.cssVarPrefix ?? '--font-set'\n const optimizedSlug = opts.optimizedSlug ?? 'fontOptimized'\n const fallbackFor = (family: FontFamily) => opts.fallbacks?.[family] ?? GENERIC_FALLBACK[family] ?? DEFAULT_FALLBACK\n if (!typefaces.length) return ''\n\n const faces = typefaces\n .flatMap((tf) =>\n tf.faces.map((f) => {\n // An italic with an oblique angle rides a `slnt` axis: `font-style: oblique <angle>`\n // maps onto it per CSS Fonts 4 (as `font-style: italic` maps onto an `ital` axis), and\n // italic requests fall back to oblique faces in font matching.\n const fontStyle = f.style === 'italic' && f.obliqueAngle ? `oblique ${f.obliqueAngle}deg` : f.style\n return `@font-face{font-family:'pl-font-${tf.id}';src:url('${faceUrl(f.filename, optimizedSlug)}') format('woff2');font-weight:${f.weight};font-style:${fontStyle};font-display:swap;}`\n }),\n )\n .join('')\n const vars = typefaces.map((tf) => `${cssVarPrefix}${familyVarSuffix(tf.family)}:'pl-font-${tf.id}',${fallbackFor(tf.family)};`).join('')\n return `${faces}:root{${vars}}`\n}\n"],"names":["refId","DEFAULT_FONT_FAMILIES","familyVarSuffix","expandItalCapableFaces","faces","hasExplicitItalic","some","f","style","flatMap","italCapable","face","obliqueAngle","_drop","upright","GLOBAL_META_KEYS","Set","familyKeysFromGlobal","global","Object","keys","filter","k","startsWith","has","getActiveFontFaces","payload","opts","fontSetSlug","optimizedSlug","selection","findGlobal","slug","depth","overrideAccess","families","familyIds","map","family","id","r","length","uniqueIds","res","find","collection","where","font","in","limit","facesByFont","Map","d","docs","filename","fontId","weight","bucket","get","push","set","out","GENERIC_FALLBACK","fromEntries","key","fallback","DEFAULT_FALLBACK","faceUrl","encodeURIComponent","buildFontFaceCss","typefaces","cssVarPrefix","fallbackFor","fallbacks","tf","fontStyle","join","vars"],"mappings":"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"}