@phis/contracts 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -11
- package/dist/access.d.ts +50 -0
- package/dist/access.js +65 -0
- package/dist/addon-index.d.ts +1 -0
- package/dist/addon-index.js +1 -0
- package/dist/addon.d.ts +130 -57
- package/dist/addon.js +2 -2
- package/dist/assets.d.ts +165 -0
- package/dist/assets.js +1 -0
- package/dist/capabilities.d.ts +205 -30
- package/dist/capabilities.js +2 -2
- package/dist/catalog.d.ts +104 -0
- package/dist/catalog.js +116 -0
- package/dist/cms.d.ts +120 -0
- package/dist/cms.js +270 -0
- package/dist/core.d.ts +14 -6
- package/dist/core.js +16 -8
- package/dist/http.d.ts +33 -0
- package/dist/http.js +33 -0
- package/dist/locale.d.ts +14 -0
- package/dist/locale.js +14 -0
- package/dist/logging.d.ts +49 -0
- package/dist/logging.js +17 -0
- package/dist/media-storage.d.ts +129 -0
- package/dist/media-storage.js +56 -0
- package/dist/media.d.ts +228 -0
- package/dist/media.js +362 -0
- package/dist/queries.d.ts +280 -48
- package/dist/queries.js +10 -1
- package/dist/schema.d.ts +160 -26
- package/dist/schema.js +11 -2
- package/dist/server-capabilities.d.ts +37 -0
- package/dist/server-capabilities.js +17 -0
- package/dist/signals.d.ts +58 -0
- package/dist/signals.js +118 -0
- package/dist/site-groups.d.ts +38 -0
- package/dist/site-groups.js +54 -0
- package/package.json +43 -3
- package/dist/index.d.ts +0 -6
- package/dist/index.js +0 -6
package/dist/media.js
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a Media Space names its kind on the wire.
|
|
3
|
+
*
|
|
4
|
+
* Only the name is shared. phis stores the kind as a smallint and that number is its own business --
|
|
5
|
+
* the site UI never sees it, so putting it here would place a storage detail in a contract.
|
|
6
|
+
*
|
|
7
|
+
* What made this worth writing down is that the translation between the two was incomplete: phis knew
|
|
8
|
+
* four kinds and mapped three, falling back to `"site"` for the rest, while the UI declared its own
|
|
9
|
+
* union of the same three. An Add-on Space therefore reached the administration surface calling itself
|
|
10
|
+
* a Site Space, in the one view that shows Add-on Spaces at all. With the names here, the map on the
|
|
11
|
+
* server side is a total function from its own enum onto this one, and a kind that is added and not
|
|
12
|
+
* mapped stops compiling.
|
|
13
|
+
*/
|
|
14
|
+
export const PHI_MEDIA_SPACE_KINDS = ["site", "user", "group", "addon"];
|
|
15
|
+
/**
|
|
16
|
+
* The kinds a Module may declare a need for.
|
|
17
|
+
*
|
|
18
|
+
* The Site Space always exists and is governed by the Core Media role matrix, so declaring it could
|
|
19
|
+
* only repeat what is already true. An Add-on Space belongs to its Add-on and comes with installing
|
|
20
|
+
* it, rather than being asked for by a Module.
|
|
21
|
+
*/
|
|
22
|
+
export const PHI_DECLARABLE_MEDIA_SPACE_KINDS = ["user", "group"];
|
|
23
|
+
export function isPhiDeclarableMediaSpaceKind(value) {
|
|
24
|
+
return PHI_DECLARABLE_MEDIA_SPACE_KINDS.includes(value);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The display properties an Asset may carry, as bits of `media_assets.presentation_flags`.
|
|
28
|
+
*
|
|
29
|
+
* Shared because the number itself travels: the site UI sends a mask as a list filter and an array of
|
|
30
|
+
* these values when metadata is saved, and phis both answers the filter with `flags & mask <> 0` and
|
|
31
|
+
* refuses a value it does not know. Two sides that must agree on a number is what this package is for,
|
|
32
|
+
* and the copy that lived on each side had already drifted -- phis still carried `Private`, `Archived`
|
|
33
|
+
* and `Restricted` long after lifecycle and delivery had become columns of their own.
|
|
34
|
+
*
|
|
35
|
+
* Display only. Access is `delivery_policy` and `lifecycle_status`, never a bit in here.
|
|
36
|
+
*
|
|
37
|
+
* Bits 1 and 2 are free, and before v1 a bit may be given a new meaning rather than kept vacant.
|
|
38
|
+
*/
|
|
39
|
+
export const PhiMediaAssetFlags = {
|
|
40
|
+
/** Offered as a background. What an Asset is for; where it came from is `meta.source`. */
|
|
41
|
+
Background: 1 << 0,
|
|
42
|
+
Featured: 1 << 3,
|
|
43
|
+
Locked: 1 << 4,
|
|
44
|
+
Mask: 1 << 5,
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* The rest of the Media vocabulary both sides speak.
|
|
48
|
+
*
|
|
49
|
+
* Kind names travel as `kind` in the list contract and are stored on the Asset; Folder flags travel in
|
|
50
|
+
* the Folder payload; a variant key is a number in a delivery URL and the name phis generates the
|
|
51
|
+
* rendition under. None of it is one side's private business, and all of it stood twice -- once here in
|
|
52
|
+
* spirit, once in each repository in fact -- until a bit value drifted and showed what that costs.
|
|
53
|
+
*
|
|
54
|
+
* The two resolvers come along because a vocabulary without its total function is only half a contract:
|
|
55
|
+
* a content type must land on the same kind in the browser and on the server, or an upload is filed as
|
|
56
|
+
* one thing and listed as another.
|
|
57
|
+
*/
|
|
58
|
+
export const PhiMediaFolderFlags = {
|
|
59
|
+
Collapsed: 1 << 0,
|
|
60
|
+
Hidden: 1 << 1,
|
|
61
|
+
Locked: 1 << 2,
|
|
62
|
+
};
|
|
63
|
+
export const PhiMediaKind = {
|
|
64
|
+
Image: "image",
|
|
65
|
+
Video: "video",
|
|
66
|
+
Audio: "audio",
|
|
67
|
+
Pdf: "pdf",
|
|
68
|
+
Markdown: "markdown",
|
|
69
|
+
Document: "document",
|
|
70
|
+
Archive: "archive",
|
|
71
|
+
/*
|
|
72
|
+
* A typeface, kept because a Site owns its lettering the way it owns its pictures.
|
|
73
|
+
*
|
|
74
|
+
* Neither `Document` nor `Binary` would do. A font is not read and not handed over as a download: a
|
|
75
|
+
* stylesheet loads it, and the Theme that names it has to be able to find it again years later --
|
|
76
|
+
* including after the Module that once brought it was switched off. A kind of its own is what makes
|
|
77
|
+
* that a query rather than a guess about content types.
|
|
78
|
+
*/
|
|
79
|
+
Font: "font",
|
|
80
|
+
/*
|
|
81
|
+
* Arbitrary bytes, offered as a download and nothing else.
|
|
82
|
+
*
|
|
83
|
+
* Separate from `Other` because the two mean opposite things to a declaration: `Binary` is a Module
|
|
84
|
+
* saying it wants executables and disk images, while `Other` is the answer for a type nothing has
|
|
85
|
+
* classified. Without it, a Module that legitimately distributes an installer would have to declare
|
|
86
|
+
* the catch-all to get one.
|
|
87
|
+
*/
|
|
88
|
+
Binary: "binary",
|
|
89
|
+
Other: "other",
|
|
90
|
+
};
|
|
91
|
+
export const PhiImageAssetVariantKey = {
|
|
92
|
+
Thumbnail: 0,
|
|
93
|
+
Preview: 1,
|
|
94
|
+
Banner: 2,
|
|
95
|
+
Header: 3,
|
|
96
|
+
Card: 4,
|
|
97
|
+
Hero: 5,
|
|
98
|
+
Avatar: 6,
|
|
99
|
+
Logo: 7,
|
|
100
|
+
Landscape: 8,
|
|
101
|
+
Portrait: 9,
|
|
102
|
+
};
|
|
103
|
+
export const PhiImageAssetVariantKeyName = {
|
|
104
|
+
[PhiImageAssetVariantKey.Thumbnail]: "thumbnail",
|
|
105
|
+
[PhiImageAssetVariantKey.Preview]: "preview",
|
|
106
|
+
[PhiImageAssetVariantKey.Banner]: "banner",
|
|
107
|
+
[PhiImageAssetVariantKey.Header]: "header",
|
|
108
|
+
[PhiImageAssetVariantKey.Card]: "card",
|
|
109
|
+
[PhiImageAssetVariantKey.Hero]: "hero",
|
|
110
|
+
[PhiImageAssetVariantKey.Avatar]: "avatar",
|
|
111
|
+
[PhiImageAssetVariantKey.Logo]: "logo",
|
|
112
|
+
[PhiImageAssetVariantKey.Landscape]: "landscape",
|
|
113
|
+
[PhiImageAssetVariantKey.Portrait]: "portrait",
|
|
114
|
+
};
|
|
115
|
+
export function normalizePhiMediaKind(kind) {
|
|
116
|
+
const normalized = (kind ?? "").trim().toLowerCase();
|
|
117
|
+
if (normalized === PhiMediaKind.Image ||
|
|
118
|
+
normalized === PhiMediaKind.Video ||
|
|
119
|
+
normalized === PhiMediaKind.Audio ||
|
|
120
|
+
normalized === PhiMediaKind.Pdf ||
|
|
121
|
+
normalized === PhiMediaKind.Markdown ||
|
|
122
|
+
normalized === PhiMediaKind.Document ||
|
|
123
|
+
normalized === PhiMediaKind.Archive ||
|
|
124
|
+
normalized === PhiMediaKind.Font ||
|
|
125
|
+
normalized === PhiMediaKind.Binary) {
|
|
126
|
+
return normalized;
|
|
127
|
+
}
|
|
128
|
+
return PhiMediaKind.Other;
|
|
129
|
+
}
|
|
130
|
+
export function resolvePhiMediaKindFromContentType(contentType) {
|
|
131
|
+
const normalized = (contentType ?? "").trim().toLowerCase();
|
|
132
|
+
if (!normalized) {
|
|
133
|
+
return PhiMediaKind.Other;
|
|
134
|
+
}
|
|
135
|
+
if (normalized.startsWith("image/")) {
|
|
136
|
+
return PhiMediaKind.Image;
|
|
137
|
+
}
|
|
138
|
+
if (normalized.startsWith("video/")) {
|
|
139
|
+
return PhiMediaKind.Video;
|
|
140
|
+
}
|
|
141
|
+
if (normalized.startsWith("audio/")) {
|
|
142
|
+
return PhiMediaKind.Audio;
|
|
143
|
+
}
|
|
144
|
+
/*
|
|
145
|
+
* `font/*` is the registered tree (RFC 8081); the `application/...` spellings below it are what older
|
|
146
|
+
* tools still send for the same bytes.
|
|
147
|
+
*/
|
|
148
|
+
if (normalized.startsWith("font/") ||
|
|
149
|
+
normalized === "application/font-woff" ||
|
|
150
|
+
normalized === "application/x-font-woff" ||
|
|
151
|
+
normalized === "application/x-font-ttf" ||
|
|
152
|
+
normalized === "application/x-font-otf" ||
|
|
153
|
+
normalized === "application/x-font-truetype" ||
|
|
154
|
+
normalized === "application/x-font-opentype" ||
|
|
155
|
+
normalized === "application/vnd.ms-fontobject") {
|
|
156
|
+
return PhiMediaKind.Font;
|
|
157
|
+
}
|
|
158
|
+
if (normalized === "application/pdf") {
|
|
159
|
+
return PhiMediaKind.Pdf;
|
|
160
|
+
}
|
|
161
|
+
if (normalized === "text/markdown") {
|
|
162
|
+
return PhiMediaKind.Markdown;
|
|
163
|
+
}
|
|
164
|
+
if (normalized === "application/msword" ||
|
|
165
|
+
normalized === "application/vnd.openxmlformats-officedocument.wordprocessingml.document" ||
|
|
166
|
+
normalized === "application/rtf" ||
|
|
167
|
+
normalized === "text/plain" ||
|
|
168
|
+
normalized === "application/vnd.oasis.opendocument.text") {
|
|
169
|
+
return PhiMediaKind.Document;
|
|
170
|
+
}
|
|
171
|
+
if (normalized === "application/zip" ||
|
|
172
|
+
normalized === "application/x-7z-compressed" ||
|
|
173
|
+
normalized === "application/x-rar-compressed" ||
|
|
174
|
+
normalized === "application/x-tar") {
|
|
175
|
+
return PhiMediaKind.Archive;
|
|
176
|
+
}
|
|
177
|
+
/*
|
|
178
|
+
* `application/octet-stream` belongs here rather than with the unclassified: an unlabelled blob is
|
|
179
|
+
* bytes, and a Module declaring `binary` is saying it accepts exactly that.
|
|
180
|
+
*/
|
|
181
|
+
if (normalized === "application/octet-stream" ||
|
|
182
|
+
normalized === "application/x-msdownload" ||
|
|
183
|
+
normalized === "application/vnd.microsoft.portable-executable" ||
|
|
184
|
+
normalized === "application/x-executable" ||
|
|
185
|
+
normalized === "application/x-mach-binary" ||
|
|
186
|
+
normalized === "application/x-msi" ||
|
|
187
|
+
normalized === "application/x-apple-diskimage" ||
|
|
188
|
+
normalized === "application/vnd.debian.binary-package" ||
|
|
189
|
+
normalized === "application/x-rpm") {
|
|
190
|
+
return PhiMediaKind.Binary;
|
|
191
|
+
}
|
|
192
|
+
return PhiMediaKind.Other;
|
|
193
|
+
}
|
|
194
|
+
export function normalizePhiImageAssetVariantKey(key) {
|
|
195
|
+
const parsed = typeof key === "number"
|
|
196
|
+
? key
|
|
197
|
+
: Number.parseInt(typeof key === "string" ? key.trim() : "", 10);
|
|
198
|
+
if (Number.isInteger(parsed) &&
|
|
199
|
+
Object.prototype.hasOwnProperty.call(PhiImageAssetVariantKeyName, parsed)) {
|
|
200
|
+
return parsed;
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
export function resolvePhiImageAssetVariantKeyName(key) {
|
|
205
|
+
if (typeof key !== "number" || !Number.isInteger(key)) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
return PhiImageAssetVariantKeyName[key] ?? null;
|
|
209
|
+
}
|
|
210
|
+
export function resolvePhiImageAssetVariantSpec(key) {
|
|
211
|
+
switch (key) {
|
|
212
|
+
case PhiImageAssetVariantKey.Thumbnail:
|
|
213
|
+
return { width: 256, height: 256, fit: "cover", quality: 80, format: "webp" };
|
|
214
|
+
case PhiImageAssetVariantKey.Preview:
|
|
215
|
+
return { width: 640, height: 360, fit: "cover", quality: 82, format: "webp" };
|
|
216
|
+
case PhiImageAssetVariantKey.Banner:
|
|
217
|
+
return { width: 1600, height: 89, fit: "cover", quality: 82, format: "webp" };
|
|
218
|
+
case PhiImageAssetVariantKey.Header:
|
|
219
|
+
return { width: 1920, height: 144, fit: "cover", quality: 82, format: "webp" };
|
|
220
|
+
case PhiImageAssetVariantKey.Card:
|
|
221
|
+
return { width: 960, height: 640, fit: "cover", quality: 82, format: "webp" };
|
|
222
|
+
case PhiImageAssetVariantKey.Hero:
|
|
223
|
+
return { width: 1600, height: 900, fit: "cover", quality: 84, format: "webp" };
|
|
224
|
+
case PhiImageAssetVariantKey.Avatar:
|
|
225
|
+
return { width: 100, height: 100, fit: "cover", quality: 80, format: "webp" };
|
|
226
|
+
case PhiImageAssetVariantKey.Logo:
|
|
227
|
+
return { width: 512, height: 256, fit: "contain", quality: 82, format: "webp" };
|
|
228
|
+
case PhiImageAssetVariantKey.Landscape:
|
|
229
|
+
return { width: 640, height: 396, fit: "cover", quality: 82, format: "webp" };
|
|
230
|
+
case PhiImageAssetVariantKey.Portrait:
|
|
231
|
+
return { width: 396, height: 640, fit: "cover", quality: 82, format: "webp" };
|
|
232
|
+
default:
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* The two closed vocabularies that decide whether an Asset may be delivered at all.
|
|
238
|
+
*
|
|
239
|
+
* The numbers themselves reach the site UI: an Asset's detail payload carries `lifecycleStatus` and
|
|
240
|
+
* `deliveryPolicy` as they stand in the row, and the UI decides from them whether `next/image` may be
|
|
241
|
+
* pointed at the original. phis evaluates `delivery_policy` before any physical lookup, so a value that
|
|
242
|
+
* means one thing on one side and another on the other would be a visibility bug, not a display bug.
|
|
243
|
+
*
|
|
244
|
+
* Unlike the Space kind next to them in phis, which stays a storage detail because the wire carries its
|
|
245
|
+
* name instead of its number.
|
|
246
|
+
*/
|
|
247
|
+
export const PhiMediaLifecycleStatus = {
|
|
248
|
+
Pending: 1,
|
|
249
|
+
Ready: 2,
|
|
250
|
+
Archived: 3,
|
|
251
|
+
Quarantined: 4,
|
|
252
|
+
Failed: 5,
|
|
253
|
+
};
|
|
254
|
+
export const PhiMediaDeliveryPolicy = {
|
|
255
|
+
Public: 1,
|
|
256
|
+
Authenticated: 2,
|
|
257
|
+
User: 3,
|
|
258
|
+
Group: 4,
|
|
259
|
+
Internal: 5,
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* The cuts a typeface can be delivered in.
|
|
263
|
+
*
|
|
264
|
+
* The number stands in a delivery URL, so it is shared; the named cuts follow the unicode ranges Google
|
|
265
|
+
* Fonts divides its families along, so a face declared here splits where fonts are commonly split.
|
|
266
|
+
* `Rest` is not a range but everything a typeface has outside all of them -- its own characters, listed
|
|
267
|
+
* per font in `PhiFontCoverage.restRanges`, so a page never loses a glyph and never fetches a cut for a
|
|
268
|
+
* character the font does not have.
|
|
269
|
+
*/
|
|
270
|
+
export const PhiFontSubsetKey = {
|
|
271
|
+
Latin: 0,
|
|
272
|
+
LatinExt: 1,
|
|
273
|
+
Cyrillic: 2,
|
|
274
|
+
CyrillicExt: 3,
|
|
275
|
+
Greek: 4,
|
|
276
|
+
GreekExt: 5,
|
|
277
|
+
Vietnamese: 6,
|
|
278
|
+
Math: 7,
|
|
279
|
+
Symbols: 8,
|
|
280
|
+
Rest: 15,
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* Raised whenever the ranges below or the way a cut is produced changes, so every stored cut is made
|
|
284
|
+
* again rather than served under a rule that no longer describes it.
|
|
285
|
+
*/
|
|
286
|
+
export const PHI_FONT_SUBSET_VERSION = 1;
|
|
287
|
+
const range = (text) => text.split(",").map((part) => {
|
|
288
|
+
const [start, end] = part.trim().replace(/^U\+/u, "").split("-");
|
|
289
|
+
const first = Number.parseInt(start, 16);
|
|
290
|
+
return [first, end ? Number.parseInt(end, 16) : first];
|
|
291
|
+
});
|
|
292
|
+
/** The named cuts, as Google Fonts' CSS2 API states them for a Latin, Cyrillic and Greek family. */
|
|
293
|
+
export const PHI_FONT_SUBSET_RANGES = {
|
|
294
|
+
[PhiFontSubsetKey.Latin]: range("U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"),
|
|
295
|
+
[PhiFontSubsetKey.LatinExt]: range("U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF"),
|
|
296
|
+
[PhiFontSubsetKey.Cyrillic]: range("U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116"),
|
|
297
|
+
[PhiFontSubsetKey.CyrillicExt]: range("U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F"),
|
|
298
|
+
[PhiFontSubsetKey.Greek]: range("U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF"),
|
|
299
|
+
[PhiFontSubsetKey.GreekExt]: range("U+1F00-1FFF"),
|
|
300
|
+
[PhiFontSubsetKey.Vietnamese]: range("U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB"),
|
|
301
|
+
[PhiFontSubsetKey.Math]: range("U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF"),
|
|
302
|
+
[PhiFontSubsetKey.Symbols]: range("U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF"),
|
|
303
|
+
};
|
|
304
|
+
export function isPhiCodepointInRanges(codepoint, ranges) {
|
|
305
|
+
return ranges.some(([start, end]) => codepoint >= start && codepoint <= end);
|
|
306
|
+
}
|
|
307
|
+
/** A `unicode-range` descriptor value. */
|
|
308
|
+
export function formatPhiUnicodeRange(ranges) {
|
|
309
|
+
return ranges
|
|
310
|
+
.map(([start, end]) => {
|
|
311
|
+
const hex = (value) => value.toString(16).toUpperCase().padStart(4, "0");
|
|
312
|
+
return start === end ? `U+${hex(start)}` : `U+${hex(start)}-${hex(end)}`;
|
|
313
|
+
})
|
|
314
|
+
.join(", ");
|
|
315
|
+
}
|
|
316
|
+
/** The ranges a cut covers for one typeface: the shared list for a named cut, the font's own for `Rest`. */
|
|
317
|
+
export function resolvePhiFontSubsetRanges(key, coverage) {
|
|
318
|
+
return key === PhiFontSubsetKey.Rest ? coverage.restRanges : PHI_FONT_SUBSET_RANGES[key];
|
|
319
|
+
}
|
|
320
|
+
export function isPhiFontSubsetKey(value) {
|
|
321
|
+
return typeof value === "number" && Object.values(PhiFontSubsetKey).includes(value);
|
|
322
|
+
}
|
|
323
|
+
/*
|
|
324
|
+
* Which cuts a page in one language needs before it paints, beyond `Latin` -- which every page needs,
|
|
325
|
+
* because spaces, digits and most punctuation live there whatever the script.
|
|
326
|
+
*
|
|
327
|
+
* This decides what is preloaded and nothing else: every cut a font covers is still declared, so a
|
|
328
|
+
* glyph outside the list is fetched when it appears rather than missing. A language not listed here
|
|
329
|
+
* preloads `Latin` alone, which is the right answer for most Latin-script languages and a harmless one
|
|
330
|
+
* for a script this list does not name -- its cut is then discovered by the browser, one round-trip later.
|
|
331
|
+
*/
|
|
332
|
+
const PHI_FONT_SUBSET_PRELOAD_BY_LANGUAGE = {
|
|
333
|
+
// Latin script with letters beyond Latin-1: č, ł, ő, ș, ğ, ž and their kin.
|
|
334
|
+
az: [PhiFontSubsetKey.LatinExt], bs: [PhiFontSubsetKey.LatinExt], cs: [PhiFontSubsetKey.LatinExt],
|
|
335
|
+
cy: [PhiFontSubsetKey.LatinExt], et: [PhiFontSubsetKey.LatinExt], hr: [PhiFontSubsetKey.LatinExt],
|
|
336
|
+
hu: [PhiFontSubsetKey.LatinExt], lt: [PhiFontSubsetKey.LatinExt], lv: [PhiFontSubsetKey.LatinExt],
|
|
337
|
+
mt: [PhiFontSubsetKey.LatinExt], pl: [PhiFontSubsetKey.LatinExt], ro: [PhiFontSubsetKey.LatinExt],
|
|
338
|
+
sk: [PhiFontSubsetKey.LatinExt], sl: [PhiFontSubsetKey.LatinExt], tr: [PhiFontSubsetKey.LatinExt],
|
|
339
|
+
vi: [PhiFontSubsetKey.Vietnamese],
|
|
340
|
+
be: [PhiFontSubsetKey.Cyrillic], bg: [PhiFontSubsetKey.Cyrillic], mk: [PhiFontSubsetKey.Cyrillic],
|
|
341
|
+
ru: [PhiFontSubsetKey.Cyrillic], sr: [PhiFontSubsetKey.Cyrillic], uk: [PhiFontSubsetKey.Cyrillic],
|
|
342
|
+
// Cyrillic with letters the basic block lacks: қ, ң, ө, ү, ҳ.
|
|
343
|
+
kk: [PhiFontSubsetKey.Cyrillic, PhiFontSubsetKey.CyrillicExt], ky: [PhiFontSubsetKey.Cyrillic, PhiFontSubsetKey.CyrillicExt],
|
|
344
|
+
mn: [PhiFontSubsetKey.Cyrillic, PhiFontSubsetKey.CyrillicExt], tg: [PhiFontSubsetKey.Cyrillic, PhiFontSubsetKey.CyrillicExt],
|
|
345
|
+
el: [PhiFontSubsetKey.Greek],
|
|
346
|
+
};
|
|
347
|
+
/**
|
|
348
|
+
* The cuts to preload for a page in `locale`, `Latin` first, restricted to what the font covers.
|
|
349
|
+
*
|
|
350
|
+
* `locale` is a BCP 47 tag; its script subtag overrides the language's usual one, so `sr-Latn` preloads
|
|
351
|
+
* `LatinExt` where `sr` preloads `Cyrillic`.
|
|
352
|
+
*/
|
|
353
|
+
export function resolvePhiFontPreloadSubsetKeys(locale, coverage) {
|
|
354
|
+
const [language = "", ...subtags] = (locale ?? "").trim().toLowerCase().split(/[-_]/);
|
|
355
|
+
const script = subtags.find((subtag) => subtag.length === 4);
|
|
356
|
+
const extra = script === "latn"
|
|
357
|
+
? [PhiFontSubsetKey.LatinExt]
|
|
358
|
+
: script === "cyrl"
|
|
359
|
+
? [PhiFontSubsetKey.Cyrillic]
|
|
360
|
+
: PHI_FONT_SUBSET_PRELOAD_BY_LANGUAGE[language] ?? [];
|
|
361
|
+
return [PhiFontSubsetKey.Latin, ...extra].filter((key) => coverage.subsets.includes(key));
|
|
362
|
+
}
|