@effindomv2/runtime 0.1.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/LICENSE.md +6 -0
- package/dist/bridge.js +4 -0
- package/dist/bridge.js.map +7 -0
- package/dist/effindom.v2.manifest.json +68 -0
- package/dist/fonts/NotoColorEmoji.ttf +0 -0
- package/dist/fonts/NotoEmoji-Regular.ttf +0 -0
- package/dist/fonts/NotoSans-Bold.ttf +0 -0
- package/dist/fonts/NotoSans-BoldItalic.ttf +0 -0
- package/dist/fonts/NotoSans-Italic.ttf +0 -0
- package/dist/fonts/NotoSans-Regular.ttf +0 -0
- package/dist/fonts/NotoSansMono-Bold.ttf +0 -0
- package/dist/fonts/NotoSansMono-Regular.ttf +0 -0
- package/dist/fonts/NotoSansSymbols2-Regular.ttf +0 -0
- package/dist/harness.js +2 -0
- package/dist/harness.js.map +7 -0
- package/dist/index.html +53 -0
- package/dist/runtime/effindom-core-v2.wasm32-simd.JQXIaRaN0-JahfIVFiSLE49WzzCENvef_2EDEm09nJs.wasm +0 -0
- package/dist/runtime/effindom-core-v2.wasm32-simd.y7RzpkMARiFeRkpgiqKQsAfv4Hf17NYdpni-6aLNhMs.js.symbols +10079 -0
- package/dist/runtime/effindom-core-v2.wasm32-simd.yhT7DGUv4soEv4W91WVZl3T7T_ecKojk5_IcnwL79a0.js +1 -0
- package/dist/runtime/effindom-core-v2.wasm32.JSfMkp9ertJzSZxA-_xz3yacrJUhswxlwbqbJLRIuqw.wasm +0 -0
- package/dist/runtime/effindom-core-v2.wasm32.xNgsQv7dCwf8Uy-PfJSoRNyk9-q1OSogUwkk5g6ZBjk.js.symbols +10088 -0
- package/dist/runtime/effindom-core-v2.wasm32.yhT7DGUv4soEv4W91WVZl3T7T_ecKojk5_IcnwL79a0.js +1 -0
- package/dist/runtime/effindom-core-v2.wasm64-simd.GkByf-CPorNOs1CORny_8JjVk8Z3piiFq92r-uw1Syc.js +1 -0
- package/dist/runtime/effindom-core-v2.wasm64-simd.p4P98oRu2wEWxtRRW8RHr27JhGeWvWlziZXDM_z3Nc4.js.symbols +10286 -0
- package/dist/runtime/effindom-core-v2.wasm64-simd.y75FYXRwhQrpaDGYbZWrohGDv0AmjTb-EjXwOjBIgnM.wasm +0 -0
- package/dist/runtime/effindom-core-v2.wasm64.GkByf-CPorNOs1CORny_8JjVk8Z3piiFq92r-uw1Syc.js +1 -0
- package/dist/runtime/effindom-core-v2.wasm64.emhE1_CJs4_zXp8wiQS_5lYpUQ0OchmXgxksi0ykaBs.js.symbols +10298 -0
- package/dist/runtime/effindom-core-v2.wasm64.sO-Yu70cfN8Qs3a5iEp6cbFPaiOchqcMKUzryu4npNo.wasm +0 -0
- package/dist/runtime/effindom-ui-v2.wasm32-simd.0Mas1XD03eYvemryTioWaZOBuBA5ij7MFlTa8CgEZWs.wasm +0 -0
- package/dist/runtime/effindom-ui-v2.wasm32-simd.ThSDClMnSWdwf9d89JZfYor0G1Z6OxR4lOc75rNRuD4.js.symbols +1890 -0
- package/dist/runtime/effindom-ui-v2.wasm32-simd.wved0xEV4EKXVNBU3Sx7giD4faxD2YII9sQ2N_wCP4I.js +2 -0
- package/dist/runtime/effindom-ui-v2.wasm32.H7kYg99bT9ADGh0uUvj6H9Dk1L058nVFLv_4R79IXW8.js.symbols +1900 -0
- package/dist/runtime/effindom-ui-v2.wasm32.tp53X7nHfG_EUq29naDyElfnqhMw2D1Tr1T-BJAYO7w.wasm +0 -0
- package/dist/runtime/effindom-ui-v2.wasm32.wved0xEV4EKXVNBU3Sx7giD4faxD2YII9sQ2N_wCP4I.js +2 -0
- package/dist/runtime/effindom-ui-v2.wasm64-simd.86tk9Z3xIpgTOykET_8Nn9iUVJnp1AzOHW4fVQRGtQE.wasm +0 -0
- package/dist/runtime/effindom-ui-v2.wasm64-simd.RQaXil22Chu63-vxK9oOuX8wUY044kbo190oYIbBU4M.js.symbols +1918 -0
- package/dist/runtime/effindom-ui-v2.wasm64-simd.ZS1KEAg0XQex-VXkfgpBHE8MIoqPF8qpaf8nOjANb_U.js +2 -0
- package/dist/runtime/effindom-ui-v2.wasm64.YSwpMFbr-Q1SBe0Ze8mub1u1PqsvSz3QIYuA3eaUMME.js.symbols +1924 -0
- package/dist/runtime/effindom-ui-v2.wasm64.ZS1KEAg0XQex-VXkfgpBHE8MIoqPF8qpaf8nOjANb_U.js +2 -0
- package/dist/runtime/effindom-ui-v2.wasm64.ioQ9DuM6gR_EjlfRHdF8EvNPBcKCs0PQbbY9-cjTV6Y.wasm +0 -0
- package/dist/runtime/icudt_minimal.962CX1q0-Nbv-OqXPaub5piYTOLumUk-nEvemcvvnpw.dat +0 -0
- package/package.json +62 -0
- package/scripts/build.sh +279 -0
- package/scripts/build_assets.sh +51 -0
- package/scripts/font_assets.sh +52 -0
- package/scripts/generate_manifest.py +121 -0
- package/scripts/stage_package_assets.sh +42 -0
- package/src/bridge/commit-policy.ts +10 -0
- package/src/bridge/events/canvas-geometry.ts +78 -0
- package/src/bridge/events/key-router.ts +187 -0
- package/src/bridge/events/pointer-router.ts +619 -0
- package/src/bridge/events/semantic-hit-testing.ts +27 -0
- package/src/bridge/events.ts +54 -0
- package/src/bridge/find-dialog.ts +690 -0
- package/src/bridge/find-session.ts +158 -0
- package/src/bridge/font-catalog.ts +51 -0
- package/src/bridge/google-fonts.ts +63 -0
- package/src/bridge/incremental-font-packages.ts +216 -0
- package/src/bridge/init.ts +77 -0
- package/src/bridge/interaction/editor-model.ts +371 -0
- package/src/bridge/interaction/editor-mutations.ts +495 -0
- package/src/bridge/interaction/editor-session.ts +628 -0
- package/src/bridge/interaction/logs.ts +23 -0
- package/src/bridge/interaction/text-encoding.ts +51 -0
- package/src/bridge/interaction.ts +86 -0
- package/src/bridge/local-types.ts +105 -0
- package/src/bridge/platform.ts +68 -0
- package/src/bridge/pointer-move-coalescer.ts +41 -0
- package/src/bridge/pull-to-refresh.ts +124 -0
- package/src/bridge/render-loop.ts +268 -0
- package/src/bridge/runtime/asset-manager.ts +202 -0
- package/src/bridge/runtime/find-controller.ts +269 -0
- package/src/bridge/runtime/font-manager.ts +691 -0
- package/src/bridge/runtime/open-canvas-api.ts +72 -0
- package/src/bridge/runtime/semantic-controller.ts +133 -0
- package/src/bridge/runtime/text-documents.ts +234 -0
- package/src/bridge/runtime.ts +315 -0
- package/src/bridge/touch-gesture.ts +159 -0
- package/src/bridge/utils/assets.ts +572 -0
- package/src/bridge/utils/backends.ts +163 -0
- package/src/bridge/utils/encoding.ts +128 -0
- package/src/bridge/utils/fetch.ts +147 -0
- package/src/bridge/utils/heap.ts +118 -0
- package/src/bridge.ts +93 -0
- package/src/clipboard.ts +139 -0
- package/src/core-types.ts +595 -0
- package/src/find-on-page.ts +284 -0
- package/src/harness.ts +53 -0
- package/src/index.ts +40 -0
- package/src/open-canvas.ts +108 -0
- package/src/runtime-config.ts +96 -0
- package/src/semantic.ts +905 -0
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BridgeFontRegistration,
|
|
3
|
+
BridgeFontStackRegistration,
|
|
4
|
+
BridgeLogs,
|
|
5
|
+
CoreModule,
|
|
6
|
+
IncrementalFontAutoGrowBlockReason,
|
|
7
|
+
IncrementalFontCacheState,
|
|
8
|
+
IncrementalFontPolicy,
|
|
9
|
+
IncrementalFontRuntimeState,
|
|
10
|
+
UiModule,
|
|
11
|
+
} from '../../core-types';
|
|
12
|
+
import { getBridgeAssetUrl, getBuiltInBridgeFont } from '../font-catalog';
|
|
13
|
+
import { fetchGoogleFontShardBytes } from '../google-fonts';
|
|
14
|
+
import {
|
|
15
|
+
resolveIncrementalFontPackageRequests,
|
|
16
|
+
type ResolvedIncrementalFontShardRequest,
|
|
17
|
+
} from '../incremental-font-packages';
|
|
18
|
+
import { writeBytesToHeap } from '../utils/heap';
|
|
19
|
+
|
|
20
|
+
type InternalFontLoadState = 'known' | 'loading' | 'loaded' | 'failed';
|
|
21
|
+
|
|
22
|
+
interface InternalIncrementalFontState {
|
|
23
|
+
sourceUrl: string | null;
|
|
24
|
+
sourceState: 'unknown' | InternalFontLoadState;
|
|
25
|
+
requestedSegmentIds: Set<string>;
|
|
26
|
+
pendingSegmentIds: Set<string>;
|
|
27
|
+
appliedSegmentIds: Set<string>;
|
|
28
|
+
evictedSegmentIds: Set<string>;
|
|
29
|
+
requestedCharactersByFamily: Map<string, Set<string>>;
|
|
30
|
+
revision: number;
|
|
31
|
+
blockedPackageIds: Set<string>;
|
|
32
|
+
lastBlockedReason: IncrementalFontAutoGrowBlockReason | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface InternalIncrementalShardCacheEntry {
|
|
36
|
+
shardKey: string;
|
|
37
|
+
fontId: number;
|
|
38
|
+
sizeBytes: number;
|
|
39
|
+
primaryFontIds: Set<number>;
|
|
40
|
+
lastAccessTick: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface NormalizedIncrementalFontPolicy {
|
|
44
|
+
autoGrow: boolean;
|
|
45
|
+
maxCachedShardFonts: number;
|
|
46
|
+
allowedFontIds: Set<number> | null;
|
|
47
|
+
blockedPackageIds: Set<string> | null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const DEFAULT_INCREMENTAL_FONT_POLICY: IncrementalFontPolicy = {
|
|
51
|
+
autoGrow: true,
|
|
52
|
+
maxCachedShardFonts: 8,
|
|
53
|
+
allowedFontIds: null,
|
|
54
|
+
blockedPackageIds: null,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export class IncrementalFontManager {
|
|
58
|
+
private readonly loadedFonts = new Map<number, string>();
|
|
59
|
+
private readonly fontLoadStates = new Map<number, { readonly url: string; readonly state: InternalFontLoadState }>();
|
|
60
|
+
private readonly incrementalFontStates = new Map<number, InternalIncrementalFontState>();
|
|
61
|
+
private readonly fontFallbacks = new Map<number, number[]>();
|
|
62
|
+
private readonly lazyFontSources = new Map<number, string>();
|
|
63
|
+
private readonly pendingFontLoads = new Map<number, Promise<void>>();
|
|
64
|
+
private readonly pendingIncrementalShardLoads = new Map<string, Promise<number>>();
|
|
65
|
+
private readonly pendingIncrementalFamilyRequests = new Map<string, {
|
|
66
|
+
packageId: string;
|
|
67
|
+
coverageKind: number;
|
|
68
|
+
familyKey: string;
|
|
69
|
+
googleFamily: string;
|
|
70
|
+
characters: Set<string>;
|
|
71
|
+
}>();
|
|
72
|
+
private readonly scheduledIncrementalFamilyFlushes = new Set<string>();
|
|
73
|
+
private readonly incrementalShardCache = new Map<string, InternalIncrementalShardCacheEntry>();
|
|
74
|
+
private readonly shardKeysByPrimaryFont = new Map<number, Set<string>>();
|
|
75
|
+
private readonly evictedShardKeys: string[] = [];
|
|
76
|
+
private nextIncrementalFontId = 0x7F000100;
|
|
77
|
+
private shardAccessTick = 0;
|
|
78
|
+
private incrementalFontPolicy: IncrementalFontPolicy = DEFAULT_INCREMENTAL_FONT_POLICY;
|
|
79
|
+
private normalizedIncrementalFontPolicy: NormalizedIncrementalFontPolicy = {
|
|
80
|
+
autoGrow: DEFAULT_INCREMENTAL_FONT_POLICY.autoGrow,
|
|
81
|
+
maxCachedShardFonts: DEFAULT_INCREMENTAL_FONT_POLICY.maxCachedShardFonts,
|
|
82
|
+
allowedFontIds: null,
|
|
83
|
+
blockedPackageIds: null,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
public constructor(
|
|
87
|
+
private readonly core: CoreModule,
|
|
88
|
+
private readonly ui: UiModule,
|
|
89
|
+
private readonly logs: BridgeLogs,
|
|
90
|
+
private readonly onCommitFrame: () => void,
|
|
91
|
+
) {}
|
|
92
|
+
|
|
93
|
+
private normalizePolicy(policy: IncrementalFontPolicy): NormalizedIncrementalFontPolicy {
|
|
94
|
+
return {
|
|
95
|
+
autoGrow: policy.autoGrow,
|
|
96
|
+
maxCachedShardFonts: Math.max(1, Math.floor(policy.maxCachedShardFonts)),
|
|
97
|
+
allowedFontIds: policy.allowedFontIds === null ? null : new Set(policy.allowedFontIds),
|
|
98
|
+
blockedPackageIds: policy.blockedPackageIds === null ? null : new Set(policy.blockedPackageIds),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private registerFontBytes(fontId: number, bytes: Uint8Array): void {
|
|
103
|
+
const coreBytes = writeBytesToHeap(this.core, bytes);
|
|
104
|
+
const uiBytes = writeBytesToHeap(this.ui, bytes);
|
|
105
|
+
try {
|
|
106
|
+
this.core._ed_register_font(fontId, coreBytes.ptr, coreBytes.len);
|
|
107
|
+
if (this.ui._ui_register_font(fontId, uiBytes.ptr, uiBytes.len) === 0) {
|
|
108
|
+
throw new Error(`Ui rejected font ${String(fontId)}.`);
|
|
109
|
+
}
|
|
110
|
+
this.ui._ui_font_loaded(fontId);
|
|
111
|
+
} finally {
|
|
112
|
+
coreBytes.dispose();
|
|
113
|
+
uiBytes.dispose();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private unregisterFont(fontId: number): void {
|
|
118
|
+
this.core._ed_unregister_font(fontId);
|
|
119
|
+
this.ui._ui_unregister_font(fontId);
|
|
120
|
+
this.loadedFonts.delete(fontId);
|
|
121
|
+
this.fontLoadStates.delete(fontId);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private updateShardAccess(shardKey: string): void {
|
|
125
|
+
const shard = this.incrementalShardCache.get(shardKey);
|
|
126
|
+
if (shard === undefined) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
this.shardAccessTick += 1;
|
|
130
|
+
shard.lastAccessTick = this.shardAccessTick;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private getAutoGrowBlockReason(
|
|
134
|
+
fontId: number,
|
|
135
|
+
packageId: string | null,
|
|
136
|
+
): IncrementalFontAutoGrowBlockReason | null {
|
|
137
|
+
if (!this.normalizedIncrementalFontPolicy.autoGrow) {
|
|
138
|
+
return 'auto-grow-disabled';
|
|
139
|
+
}
|
|
140
|
+
if (
|
|
141
|
+
this.normalizedIncrementalFontPolicy.allowedFontIds !== null
|
|
142
|
+
&& !this.normalizedIncrementalFontPolicy.allowedFontIds.has(fontId)
|
|
143
|
+
) {
|
|
144
|
+
return 'font-not-allowed';
|
|
145
|
+
}
|
|
146
|
+
if (
|
|
147
|
+
packageId !== null
|
|
148
|
+
&& this.normalizedIncrementalFontPolicy.blockedPackageIds !== null
|
|
149
|
+
&& this.normalizedIncrementalFontPolicy.blockedPackageIds.has(packageId)
|
|
150
|
+
) {
|
|
151
|
+
return 'package-blocked';
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private releasePrimaryFontShardReference(primaryFontId: number, shardKey: string, shardFontId: number): void {
|
|
157
|
+
const fontState = this.ensureIncrementalFontState(primaryFontId);
|
|
158
|
+
fontState.appliedSegmentIds.delete(shardKey);
|
|
159
|
+
fontState.evictedSegmentIds.add(shardKey);
|
|
160
|
+
fontState.revision += 1;
|
|
161
|
+
|
|
162
|
+
const trackedShardKeys = this.shardKeysByPrimaryFont.get(primaryFontId);
|
|
163
|
+
trackedShardKeys?.delete(shardKey);
|
|
164
|
+
if (trackedShardKeys !== undefined && trackedShardKeys.size === 0) {
|
|
165
|
+
this.shardKeysByPrimaryFont.delete(primaryFontId);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const fallbacks = this.fontFallbacks.get(primaryFontId);
|
|
169
|
+
if (fallbacks !== undefined) {
|
|
170
|
+
this.fontFallbacks.set(
|
|
171
|
+
primaryFontId,
|
|
172
|
+
fallbacks.filter((fallbackId) => fallbackId !== shardFontId),
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
this.ui._ui_unregister_font_fallback(primaryFontId, shardFontId);
|
|
176
|
+
this.ui._ui_font_loaded(primaryFontId);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private evictIncrementalShard(shardKey: string): void {
|
|
180
|
+
const shard = this.incrementalShardCache.get(shardKey);
|
|
181
|
+
if (shard === undefined) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
for (const primaryFontId of shard.primaryFontIds) {
|
|
185
|
+
this.releasePrimaryFontShardReference(primaryFontId, shardKey, shard.fontId);
|
|
186
|
+
}
|
|
187
|
+
this.incrementalShardCache.delete(shardKey);
|
|
188
|
+
this.evictedShardKeys.push(shardKey);
|
|
189
|
+
if (this.evictedShardKeys.length > 32) {
|
|
190
|
+
this.evictedShardKeys.splice(0, this.evictedShardKeys.length - 32);
|
|
191
|
+
}
|
|
192
|
+
this.unregisterFont(shard.fontId);
|
|
193
|
+
this.onCommitFrame();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private ensureShardCacheCapacityForNewEntry(): void {
|
|
197
|
+
while (this.incrementalShardCache.size >= this.normalizedIncrementalFontPolicy.maxCachedShardFonts) {
|
|
198
|
+
let leastRecentlyUsed: InternalIncrementalShardCacheEntry | null = null;
|
|
199
|
+
for (const shard of this.incrementalShardCache.values()) {
|
|
200
|
+
if (leastRecentlyUsed === null || shard.lastAccessTick < leastRecentlyUsed.lastAccessTick) {
|
|
201
|
+
leastRecentlyUsed = shard;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (leastRecentlyUsed === null) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
this.evictIncrementalShard(leastRecentlyUsed.shardKey);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private trimShardCacheToPolicyLimit(): void {
|
|
212
|
+
while (this.incrementalShardCache.size > this.normalizedIncrementalFontPolicy.maxCachedShardFonts) {
|
|
213
|
+
let leastRecentlyUsed: InternalIncrementalShardCacheEntry | null = null;
|
|
214
|
+
for (const shard of this.incrementalShardCache.values()) {
|
|
215
|
+
if (leastRecentlyUsed === null || shard.lastAccessTick < leastRecentlyUsed.lastAccessTick) {
|
|
216
|
+
leastRecentlyUsed = shard;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (leastRecentlyUsed === null) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
this.evictIncrementalShard(leastRecentlyUsed.shardKey);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private ensureIncrementalFontState(fontId: number): InternalIncrementalFontState {
|
|
227
|
+
let state = this.incrementalFontStates.get(fontId);
|
|
228
|
+
if (state !== undefined) {
|
|
229
|
+
return state;
|
|
230
|
+
}
|
|
231
|
+
state = {
|
|
232
|
+
sourceUrl: null,
|
|
233
|
+
sourceState: 'unknown',
|
|
234
|
+
requestedSegmentIds: new Set<string>(),
|
|
235
|
+
pendingSegmentIds: new Set<string>(),
|
|
236
|
+
appliedSegmentIds: new Set<string>(),
|
|
237
|
+
evictedSegmentIds: new Set<string>(),
|
|
238
|
+
requestedCharactersByFamily: new Map<string, Set<string>>(),
|
|
239
|
+
revision: 0,
|
|
240
|
+
blockedPackageIds: new Set<string>(),
|
|
241
|
+
lastBlockedReason: null,
|
|
242
|
+
};
|
|
243
|
+
this.incrementalFontStates.set(fontId, state);
|
|
244
|
+
return state;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private rememberFontSource(fontId: number, url: string): void {
|
|
248
|
+
const current = this.fontLoadStates.get(fontId);
|
|
249
|
+
if (current !== undefined && current.url === url && (current.state === 'loading' || current.state === 'loaded')) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
this.fontLoadStates.set(fontId, { url, state: 'known' });
|
|
253
|
+
const state = this.ensureIncrementalFontState(fontId);
|
|
254
|
+
const previousUrl = state.sourceUrl;
|
|
255
|
+
state.sourceUrl = url;
|
|
256
|
+
if (state.sourceState === 'unknown' || state.sourceState === 'failed' || previousUrl !== url) {
|
|
257
|
+
state.sourceState = 'known';
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private markFontLoading(fontId: number, url: string): void {
|
|
262
|
+
this.fontLoadStates.set(fontId, { url, state: 'loading' });
|
|
263
|
+
const state = this.ensureIncrementalFontState(fontId);
|
|
264
|
+
state.sourceUrl = url;
|
|
265
|
+
state.sourceState = 'loading';
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private markFontLoaded(fontId: number, url: string): void {
|
|
269
|
+
this.fontLoadStates.set(fontId, { url, state: 'loaded' });
|
|
270
|
+
const state = this.ensureIncrementalFontState(fontId);
|
|
271
|
+
state.sourceUrl = url;
|
|
272
|
+
state.sourceState = 'loaded';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private markFontLoadFailed(fontId: number, url: string): void {
|
|
276
|
+
this.fontLoadStates.set(fontId, { url, state: 'failed' });
|
|
277
|
+
const state = this.ensureIncrementalFontState(fontId);
|
|
278
|
+
state.sourceUrl = url;
|
|
279
|
+
state.sourceState = 'failed';
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
public isFontLoaded(fontId: number, url?: string): boolean {
|
|
283
|
+
const current = this.fontLoadStates.get(fontId);
|
|
284
|
+
return current !== undefined
|
|
285
|
+
&& current.state === 'loaded'
|
|
286
|
+
&& (url === undefined || current.url === url);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private async fetchFontBytes(url: string, replay = false): Promise<Uint8Array> {
|
|
290
|
+
const response = await fetch(url);
|
|
291
|
+
if (!response.ok) {
|
|
292
|
+
const verb = replay ? 'refetch' : 'fetch';
|
|
293
|
+
throw new Error(`Failed to ${verb} font ${url}: ${String(response.status)}`);
|
|
294
|
+
}
|
|
295
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private reportBridgeError(error: unknown): void {
|
|
299
|
+
(window as Window & { __bridgeError?: string }).__bridgeError =
|
|
300
|
+
error instanceof Error ? error.message : String(error);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private applyFontFallbacks(fontId: number, fallbackIds: readonly number[]): void {
|
|
304
|
+
this.fontFallbacks.set(fontId, [...fallbackIds]);
|
|
305
|
+
for (const fallbackId of fallbackIds) {
|
|
306
|
+
this.ui._ui_register_font_fallback(fontId, fallbackId);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private async ensureRegisteredFont(
|
|
311
|
+
fontId: number,
|
|
312
|
+
url: string,
|
|
313
|
+
fallbackIds: readonly number[] = [],
|
|
314
|
+
): Promise<void> {
|
|
315
|
+
this.rememberFontSource(fontId, url);
|
|
316
|
+
const pending = this.pendingFontLoads.get(fontId);
|
|
317
|
+
if (pending !== undefined) {
|
|
318
|
+
await pending;
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const request = this.registerFont(
|
|
322
|
+
fallbackIds.length > 0
|
|
323
|
+
? {
|
|
324
|
+
id: fontId,
|
|
325
|
+
url,
|
|
326
|
+
fallbackIds,
|
|
327
|
+
}
|
|
328
|
+
: {
|
|
329
|
+
id: fontId,
|
|
330
|
+
url,
|
|
331
|
+
},
|
|
332
|
+
).finally(() => {
|
|
333
|
+
this.pendingFontLoads.delete(fontId);
|
|
334
|
+
});
|
|
335
|
+
this.pendingFontLoads.set(fontId, request);
|
|
336
|
+
await request;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private async ensureFontWithFallbacks(fontId: number, visited = new Set<number>()): Promise<void> {
|
|
340
|
+
if (visited.has(fontId)) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
visited.add(fontId);
|
|
344
|
+
if (!this.isFontLoaded(fontId)) {
|
|
345
|
+
const lazyUrl = this.lazyFontSources.get(fontId);
|
|
346
|
+
if (lazyUrl !== undefined) {
|
|
347
|
+
await this.ensureRegisteredFont(fontId, lazyUrl);
|
|
348
|
+
} else {
|
|
349
|
+
const builtInFont = getBuiltInBridgeFont(fontId);
|
|
350
|
+
if (builtInFont !== undefined) {
|
|
351
|
+
await this.ensureRegisteredFont(
|
|
352
|
+
builtInFont.id,
|
|
353
|
+
getBridgeAssetUrl(builtInFont.assetFile),
|
|
354
|
+
builtInFont.fallbackIds,
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const fallbackIds = this.fontFallbacks.get(fontId) ?? [];
|
|
360
|
+
await Promise.all(fallbackIds.map((fallbackId) => this.ensureFontWithFallbacks(fallbackId, visited)));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private registerSingleFontFallback(fontId: number, fallbackFontId: number): void {
|
|
364
|
+
const existingFallbacks = this.fontFallbacks.get(fontId) ?? [];
|
|
365
|
+
if (!existingFallbacks.includes(fallbackFontId)) {
|
|
366
|
+
this.fontFallbacks.set(fontId, [...existingFallbacks, fallbackFontId]);
|
|
367
|
+
}
|
|
368
|
+
this.ui._ui_register_font_fallback(fontId, fallbackFontId);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private async ensureIncrementalShardFont(
|
|
372
|
+
request: ResolvedIncrementalFontShardRequest & { readonly shardKey: string },
|
|
373
|
+
): Promise<number> {
|
|
374
|
+
const cachedShard = this.incrementalShardCache.get(request.shardKey);
|
|
375
|
+
if (cachedShard !== undefined) {
|
|
376
|
+
this.updateShardAccess(request.shardKey);
|
|
377
|
+
return cachedShard.fontId;
|
|
378
|
+
}
|
|
379
|
+
const existing = this.pendingIncrementalShardLoads.get(request.shardKey);
|
|
380
|
+
if (existing !== undefined) {
|
|
381
|
+
return await existing;
|
|
382
|
+
}
|
|
383
|
+
const pending = (async () => {
|
|
384
|
+
this.ensureShardCacheCapacityForNewEntry();
|
|
385
|
+
const fontId = this.nextIncrementalFontId;
|
|
386
|
+
this.nextIncrementalFontId += 1;
|
|
387
|
+
const shard = await fetchGoogleFontShardBytes(request.googleFamily, request.text);
|
|
388
|
+
this.registerFontBytes(fontId, shard.bytes);
|
|
389
|
+
this.loadedFonts.set(fontId, shard.url);
|
|
390
|
+
this.markFontLoaded(fontId, shard.url);
|
|
391
|
+
this.shardAccessTick += 1;
|
|
392
|
+
this.incrementalShardCache.set(request.shardKey, {
|
|
393
|
+
shardKey: request.shardKey,
|
|
394
|
+
fontId,
|
|
395
|
+
sizeBytes: shard.bytes.byteLength,
|
|
396
|
+
primaryFontIds: new Set<number>(),
|
|
397
|
+
lastAccessTick: this.shardAccessTick,
|
|
398
|
+
});
|
|
399
|
+
return fontId;
|
|
400
|
+
})().finally(() => {
|
|
401
|
+
this.pendingIncrementalShardLoads.delete(request.shardKey);
|
|
402
|
+
});
|
|
403
|
+
this.pendingIncrementalShardLoads.set(request.shardKey, pending);
|
|
404
|
+
return await pending;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private async flushPendingIncrementalFamilyRequest(primaryFontId: number, familyKey: string): Promise<void> {
|
|
408
|
+
const pendingKey = `${String(primaryFontId)}:${familyKey}`;
|
|
409
|
+
this.scheduledIncrementalFamilyFlushes.delete(pendingKey);
|
|
410
|
+
const pendingRequest = this.pendingIncrementalFamilyRequests.get(pendingKey);
|
|
411
|
+
if (pendingRequest === undefined) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
this.pendingIncrementalFamilyRequests.delete(pendingKey);
|
|
415
|
+
|
|
416
|
+
const fontState = this.ensureIncrementalFontState(primaryFontId);
|
|
417
|
+
const text = Array.from(pendingRequest.characters.values()).join('');
|
|
418
|
+
if (text.length === 0) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
const shardKey = `${pendingRequest.familyKey}:${text}`;
|
|
422
|
+
fontState.requestedSegmentIds.add(shardKey);
|
|
423
|
+
fontState.pendingSegmentIds.add(shardKey);
|
|
424
|
+
this.logs.incrementalFontPackageRequests.push({
|
|
425
|
+
primaryFontId,
|
|
426
|
+
coverageKind: pendingRequest.coverageKind,
|
|
427
|
+
packageId: pendingRequest.packageId,
|
|
428
|
+
segmentIds: [shardKey],
|
|
429
|
+
sampleText: text,
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
try {
|
|
433
|
+
const shardFontId = await this.ensureIncrementalShardFont({
|
|
434
|
+
packageId: pendingRequest.packageId,
|
|
435
|
+
coverageKind: pendingRequest.coverageKind,
|
|
436
|
+
familyKey: pendingRequest.familyKey,
|
|
437
|
+
googleFamily: pendingRequest.googleFamily,
|
|
438
|
+
text,
|
|
439
|
+
shardKey,
|
|
440
|
+
});
|
|
441
|
+
this.registerSingleFontFallback(primaryFontId, shardFontId);
|
|
442
|
+
const shard = this.incrementalShardCache.get(shardKey);
|
|
443
|
+
if (shard !== undefined) {
|
|
444
|
+
shard.primaryFontIds.add(primaryFontId);
|
|
445
|
+
this.updateShardAccess(shardKey);
|
|
446
|
+
}
|
|
447
|
+
let primaryShardKeys = this.shardKeysByPrimaryFont.get(primaryFontId);
|
|
448
|
+
if (primaryShardKeys === undefined) {
|
|
449
|
+
primaryShardKeys = new Set<string>();
|
|
450
|
+
this.shardKeysByPrimaryFont.set(primaryFontId, primaryShardKeys);
|
|
451
|
+
}
|
|
452
|
+
primaryShardKeys.add(shardKey);
|
|
453
|
+
if (!fontState.appliedSegmentIds.has(shardKey)) {
|
|
454
|
+
fontState.appliedSegmentIds.add(shardKey);
|
|
455
|
+
fontState.revision += 1;
|
|
456
|
+
}
|
|
457
|
+
fontState.pendingSegmentIds.delete(shardKey);
|
|
458
|
+
fontState.evictedSegmentIds.delete(shardKey);
|
|
459
|
+
this.ui._ui_font_loaded(primaryFontId);
|
|
460
|
+
this.onCommitFrame();
|
|
461
|
+
} catch (error: unknown) {
|
|
462
|
+
fontState.requestedSegmentIds.delete(shardKey);
|
|
463
|
+
fontState.pendingSegmentIds.delete(shardKey);
|
|
464
|
+
const requestedCharacters = fontState.requestedCharactersByFamily.get(pendingRequest.familyKey);
|
|
465
|
+
if (requestedCharacters !== undefined) {
|
|
466
|
+
for (const character of text) {
|
|
467
|
+
requestedCharacters.delete(character);
|
|
468
|
+
}
|
|
469
|
+
if (requestedCharacters.size === 0) {
|
|
470
|
+
fontState.requestedCharactersByFamily.delete(pendingRequest.familyKey);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
this.reportBridgeError(error);
|
|
474
|
+
throw error;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
public async ensureFont(fontId: number): Promise<void> {
|
|
479
|
+
await this.ensureFontWithFallbacks(fontId);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
public async ensureBuiltInFont(fontId: number): Promise<void> {
|
|
483
|
+
const builtInFont = getBuiltInBridgeFont(fontId);
|
|
484
|
+
if (builtInFont === undefined) {
|
|
485
|
+
throw new Error(`Unknown built-in font id ${String(fontId)}.`);
|
|
486
|
+
}
|
|
487
|
+
await this.ensureRegisteredFont(
|
|
488
|
+
builtInFont.id,
|
|
489
|
+
getBridgeAssetUrl(builtInFont.assetFile),
|
|
490
|
+
builtInFont.fallbackIds,
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
public getIncrementalFontCacheState(): IncrementalFontCacheState {
|
|
495
|
+
return {
|
|
496
|
+
maxCachedShardFonts: this.incrementalFontPolicy.maxCachedShardFonts,
|
|
497
|
+
cachedShardCount: this.incrementalShardCache.size,
|
|
498
|
+
cachedShardKeys: Array.from(this.incrementalShardCache.keys()),
|
|
499
|
+
evictedShardKeys: [...this.evictedShardKeys],
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
public getIncrementalFontPolicy(): IncrementalFontPolicy {
|
|
504
|
+
return {
|
|
505
|
+
autoGrow: this.incrementalFontPolicy.autoGrow,
|
|
506
|
+
maxCachedShardFonts: this.incrementalFontPolicy.maxCachedShardFonts,
|
|
507
|
+
allowedFontIds: this.incrementalFontPolicy.allowedFontIds === null
|
|
508
|
+
? null
|
|
509
|
+
: [...this.incrementalFontPolicy.allowedFontIds],
|
|
510
|
+
blockedPackageIds: this.incrementalFontPolicy.blockedPackageIds === null
|
|
511
|
+
? null
|
|
512
|
+
: [...this.incrementalFontPolicy.blockedPackageIds],
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
public setIncrementalFontPolicy(policy: Partial<IncrementalFontPolicy>): void {
|
|
517
|
+
const nextPolicy: IncrementalFontPolicy = {
|
|
518
|
+
autoGrow: policy.autoGrow ?? this.incrementalFontPolicy.autoGrow,
|
|
519
|
+
maxCachedShardFonts: policy.maxCachedShardFonts ?? this.incrementalFontPolicy.maxCachedShardFonts,
|
|
520
|
+
allowedFontIds: 'allowedFontIds' in policy
|
|
521
|
+
? (policy.allowedFontIds ?? null)
|
|
522
|
+
: this.incrementalFontPolicy.allowedFontIds,
|
|
523
|
+
blockedPackageIds: 'blockedPackageIds' in policy
|
|
524
|
+
? (policy.blockedPackageIds ?? null)
|
|
525
|
+
: this.incrementalFontPolicy.blockedPackageIds,
|
|
526
|
+
};
|
|
527
|
+
this.normalizedIncrementalFontPolicy = this.normalizePolicy(nextPolicy);
|
|
528
|
+
this.incrementalFontPolicy = {
|
|
529
|
+
...nextPolicy,
|
|
530
|
+
maxCachedShardFonts: this.normalizedIncrementalFontPolicy.maxCachedShardFonts,
|
|
531
|
+
};
|
|
532
|
+
this.trimShardCacheToPolicyLimit();
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
public getIncrementalFontState(fontId: number): IncrementalFontRuntimeState | null {
|
|
536
|
+
const state = this.incrementalFontStates.get(fontId);
|
|
537
|
+
if (state === undefined) {
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
return {
|
|
541
|
+
fontId,
|
|
542
|
+
sourceUrl: state.sourceUrl,
|
|
543
|
+
sourceState: state.sourceState,
|
|
544
|
+
loaded: this.isFontLoaded(fontId),
|
|
545
|
+
requestedSegmentIds: Array.from(state.requestedSegmentIds.values()),
|
|
546
|
+
pendingSegmentIds: Array.from(state.pendingSegmentIds.values()),
|
|
547
|
+
appliedSegmentIds: Array.from(state.appliedSegmentIds.values()),
|
|
548
|
+
evictedSegmentIds: Array.from(state.evictedSegmentIds.values()),
|
|
549
|
+
revision: state.revision,
|
|
550
|
+
autoGrowAllowed: this.getAutoGrowBlockReason(fontId, null) === null,
|
|
551
|
+
blockedPackageIds: Array.from(state.blockedPackageIds.values()),
|
|
552
|
+
lastBlockedReason: state.lastBlockedReason,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
public getClipboardFontUrl(fontId: number): string | null {
|
|
557
|
+
const loadedUrl = this.loadedFonts.get(fontId);
|
|
558
|
+
if (loadedUrl !== undefined) {
|
|
559
|
+
return loadedUrl;
|
|
560
|
+
}
|
|
561
|
+
const trackedUrl = this.fontLoadStates.get(fontId)?.url;
|
|
562
|
+
if (trackedUrl !== undefined) {
|
|
563
|
+
return trackedUrl;
|
|
564
|
+
}
|
|
565
|
+
const lazyUrl = this.lazyFontSources.get(fontId);
|
|
566
|
+
if (lazyUrl !== undefined) {
|
|
567
|
+
return lazyUrl;
|
|
568
|
+
}
|
|
569
|
+
const builtInFont = getBuiltInBridgeFont(fontId);
|
|
570
|
+
return builtInFont === undefined ? null : getBridgeAssetUrl(builtInFont.assetFile);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
public registerLazyFont(fontId: number, url: string): void {
|
|
574
|
+
this.lazyFontSources.set(fontId, url);
|
|
575
|
+
this.rememberFontSource(fontId, url);
|
|
576
|
+
if (this.loadedFonts.get(fontId) !== url) {
|
|
577
|
+
this.loadedFonts.delete(fontId);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
public registerFontFallback(fontId: number, fallbackFontId: number): void {
|
|
582
|
+
this.registerSingleFontFallback(fontId, fallbackFontId);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
public handleMissingFontCoverage(fontId: number, coverageKind: number, sampleText: string): void {
|
|
586
|
+
const fontState = this.ensureIncrementalFontState(fontId);
|
|
587
|
+
const resolvedRequests = resolveIncrementalFontPackageRequests(fontId, coverageKind, sampleText);
|
|
588
|
+
for (const request of resolvedRequests) {
|
|
589
|
+
const blockReason = this.getAutoGrowBlockReason(fontId, request.packageId);
|
|
590
|
+
if (blockReason !== null) {
|
|
591
|
+
fontState.blockedPackageIds.add(request.packageId);
|
|
592
|
+
fontState.lastBlockedReason = blockReason;
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
fontState.blockedPackageIds.delete(request.packageId);
|
|
596
|
+
if (fontState.blockedPackageIds.size === 0) {
|
|
597
|
+
fontState.lastBlockedReason = null;
|
|
598
|
+
}
|
|
599
|
+
let requestedCharacters = fontState.requestedCharactersByFamily.get(request.familyKey);
|
|
600
|
+
if (requestedCharacters === undefined) {
|
|
601
|
+
requestedCharacters = new Set<string>();
|
|
602
|
+
fontState.requestedCharactersByFamily.set(request.familyKey, requestedCharacters);
|
|
603
|
+
}
|
|
604
|
+
const pendingKey = `${String(fontId)}:${request.familyKey}`;
|
|
605
|
+
let pendingRequest = this.pendingIncrementalFamilyRequests.get(pendingKey);
|
|
606
|
+
if (pendingRequest === undefined) {
|
|
607
|
+
pendingRequest = {
|
|
608
|
+
packageId: request.packageId,
|
|
609
|
+
coverageKind: request.coverageKind,
|
|
610
|
+
familyKey: request.familyKey,
|
|
611
|
+
googleFamily: request.googleFamily,
|
|
612
|
+
characters: new Set<string>(),
|
|
613
|
+
};
|
|
614
|
+
this.pendingIncrementalFamilyRequests.set(pendingKey, pendingRequest);
|
|
615
|
+
}
|
|
616
|
+
let hasNovelCharacter = false;
|
|
617
|
+
for (const character of request.text) {
|
|
618
|
+
if (requestedCharacters.has(character)) {
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
requestedCharacters.add(character);
|
|
622
|
+
pendingRequest.characters.add(character);
|
|
623
|
+
hasNovelCharacter = true;
|
|
624
|
+
}
|
|
625
|
+
if (!hasNovelCharacter || this.scheduledIncrementalFamilyFlushes.has(pendingKey)) {
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
this.scheduledIncrementalFamilyFlushes.add(pendingKey);
|
|
629
|
+
queueMicrotask(() => {
|
|
630
|
+
void this.flushPendingIncrementalFamilyRequest(fontId, request.familyKey).catch(() => undefined);
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
public async loadFont(fontId: number, url: string): Promise<void> {
|
|
636
|
+
if (this.isFontLoaded(fontId, url)) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
this.rememberFontSource(fontId, url);
|
|
640
|
+
this.markFontLoading(fontId, url);
|
|
641
|
+
try {
|
|
642
|
+
const bytes = await this.fetchFontBytes(url);
|
|
643
|
+
this.registerFontBytes(fontId, bytes);
|
|
644
|
+
this.loadedFonts.set(fontId, url);
|
|
645
|
+
this.markFontLoaded(fontId, url);
|
|
646
|
+
this.onCommitFrame();
|
|
647
|
+
} catch (error) {
|
|
648
|
+
if (this.loadedFonts.get(fontId) === url) {
|
|
649
|
+
this.loadedFonts.delete(fontId);
|
|
650
|
+
}
|
|
651
|
+
this.markFontLoadFailed(fontId, url);
|
|
652
|
+
throw error;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
public async registerFont(font: BridgeFontRegistration): Promise<void> {
|
|
657
|
+
if (!this.isFontLoaded(font.id, font.url)) {
|
|
658
|
+
await this.loadFont(font.id, font.url);
|
|
659
|
+
}
|
|
660
|
+
if (font.fallbackIds !== undefined) {
|
|
661
|
+
this.applyFontFallbacks(font.id, font.fallbackIds);
|
|
662
|
+
}
|
|
663
|
+
this.onCommitFrame();
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
public async registerFontStack(stack: BridgeFontStackRegistration): Promise<void> {
|
|
667
|
+
const fallbackFonts = stack.fallbacks ?? [];
|
|
668
|
+
await Promise.all([
|
|
669
|
+
this.loadFont(stack.primary.id, stack.primary.url),
|
|
670
|
+
...fallbackFonts.map((fallback) => this.loadFont(fallback.id, fallback.url)),
|
|
671
|
+
]);
|
|
672
|
+
this.applyFontFallbacks(
|
|
673
|
+
stack.primary.id,
|
|
674
|
+
fallbackFonts.map((fallback) => fallback.id),
|
|
675
|
+
);
|
|
676
|
+
this.onCommitFrame();
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
public async replayLoadedFonts(): Promise<void> {
|
|
680
|
+
const fontReloads = Array.from(this.loadedFonts.entries(), async ([fontId, url]) => {
|
|
681
|
+
const bytes = await this.fetchFontBytes(url, true);
|
|
682
|
+
this.registerFontBytes(fontId, bytes);
|
|
683
|
+
});
|
|
684
|
+
await Promise.all(fontReloads);
|
|
685
|
+
for (const [fontId, fallbackIds] of this.fontFallbacks.entries()) {
|
|
686
|
+
for (const fallbackId of fallbackIds) {
|
|
687
|
+
this.ui._ui_register_font_fallback(fontId, fallbackId);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|