@psrt/sdk 0.1.1 → 0.1.2
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/dist/browser/index.d.ts +260 -0
- package/dist/browser/index.js +3447 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/index.cjs +2381 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +82 -2
- package/dist/index.d.ts +82 -2
- package/dist/index.js +2374 -31
- package/dist/index.js.map +1 -1
- package/package.json +15 -3
- package/wasm/psrt.wasm +0 -0
package/dist/index.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
// src/runtime/boot.ts
|
|
2
|
-
import { existsSync, readFileSync } from "fs";
|
|
3
|
-
import { dirname, join } from "path";
|
|
4
|
-
import { fileURLToPath, pathToFileURL } from "url";
|
|
5
|
-
|
|
6
1
|
// src/wasm.ts
|
|
7
2
|
var decoder = new TextDecoder();
|
|
8
3
|
var encoder = new TextEncoder();
|
|
@@ -95,11 +90,10 @@ function wireWasmFromGlobal() {
|
|
|
95
90
|
wasmExports = exp;
|
|
96
91
|
}
|
|
97
92
|
|
|
98
|
-
// src/runtime/boot.ts
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
93
|
+
// src/runtime/boot.node.ts
|
|
94
|
+
import { existsSync, readFileSync } from "fs";
|
|
95
|
+
import { dirname, join } from "path";
|
|
96
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
103
97
|
function wasmDir() {
|
|
104
98
|
let dir = dirname(fileURLToPath(import.meta.url));
|
|
105
99
|
for (; ; ) {
|
|
@@ -115,6 +109,20 @@ function wasmDir() {
|
|
|
115
109
|
}
|
|
116
110
|
throw new Error("PSRT core binary not found");
|
|
117
111
|
}
|
|
112
|
+
async function loadGoRuntimeNode() {
|
|
113
|
+
if (typeof globalThis.Go !== "undefined") {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const scriptPath = join(wasmDir(), "wasm_exec.js");
|
|
117
|
+
await import(pathToFileURL(scriptPath).href);
|
|
118
|
+
}
|
|
119
|
+
async function loadCoreBytesNode() {
|
|
120
|
+
const bytes = readFileSync(join(wasmDir(), "psrt.wasm"));
|
|
121
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/runtime/boot.node-entry.ts
|
|
125
|
+
var bootPromise = null;
|
|
118
126
|
function waitForExports() {
|
|
119
127
|
return new Promise((resolve, reject) => {
|
|
120
128
|
let attempts = 0;
|
|
@@ -133,27 +141,8 @@ function waitForExports() {
|
|
|
133
141
|
tick();
|
|
134
142
|
});
|
|
135
143
|
}
|
|
136
|
-
async function loadGoRuntime() {
|
|
137
|
-
if (typeof globalThis.Go !== "undefined") {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
const scriptPath = join(wasmDir(), "wasm_exec.js");
|
|
141
|
-
await import(pathToFileURL(scriptPath).href);
|
|
142
|
-
}
|
|
143
|
-
async function loadCoreBytes() {
|
|
144
|
-
if (isNodeRuntime()) {
|
|
145
|
-
const bytes = readFileSync(join(wasmDir(), "psrt.wasm"));
|
|
146
|
-
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
147
|
-
}
|
|
148
|
-
const url = new URL("../../wasm/psrt.wasm", import.meta.url).href;
|
|
149
|
-
const response = await fetch(url);
|
|
150
|
-
if (!response.ok) {
|
|
151
|
-
throw new Error(`failed to load PSRT core (${response.status})`);
|
|
152
|
-
}
|
|
153
|
-
return response.arrayBuffer();
|
|
154
|
-
}
|
|
155
144
|
async function startCore(bytes) {
|
|
156
|
-
await
|
|
145
|
+
await loadGoRuntimeNode();
|
|
157
146
|
const go = new Go();
|
|
158
147
|
const { instance } = await WebAssembly.instantiate(bytes, go.importObject);
|
|
159
148
|
void go.run(instance);
|
|
@@ -169,7 +158,7 @@ async function bootCore() {
|
|
|
169
158
|
return;
|
|
170
159
|
}
|
|
171
160
|
bootPromise = (async () => {
|
|
172
|
-
const bytes = await
|
|
161
|
+
const bytes = await loadCoreBytesNode();
|
|
173
162
|
await startCore(bytes);
|
|
174
163
|
})();
|
|
175
164
|
return bootPromise;
|
|
@@ -191,10 +180,2360 @@ function formatDocument(doc) {
|
|
|
191
180
|
return invokeFormatDocument(doc);
|
|
192
181
|
}
|
|
193
182
|
|
|
183
|
+
// src/html/text/expandConsts.ts
|
|
184
|
+
function expandConsts(content, consts) {
|
|
185
|
+
if (!consts || !content) return content;
|
|
186
|
+
const keys = Object.keys(consts).sort((a, b) => {
|
|
187
|
+
if (b.length !== a.length) return b.length - a.length;
|
|
188
|
+
return a.localeCompare(b);
|
|
189
|
+
});
|
|
190
|
+
let out = content;
|
|
191
|
+
for (const k of keys) {
|
|
192
|
+
out = out.split(`@${k}@`).join(consts[k]);
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
function compactJsonString(s) {
|
|
197
|
+
const trimmed = s.trim();
|
|
198
|
+
if (!trimmed) return trimmed;
|
|
199
|
+
try {
|
|
200
|
+
return JSON.stringify(JSON.parse(trimmed));
|
|
201
|
+
} catch {
|
|
202
|
+
return trimmed.replace(/\n/g, "").replace(/\r/g, "");
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function expandConstsInStyle(style, consts) {
|
|
206
|
+
const raw = typeof style === "string" ? style.trim() : JSON.stringify(style ?? {});
|
|
207
|
+
if (!raw || raw === "{}") return style;
|
|
208
|
+
const compact = compactJsonString(raw);
|
|
209
|
+
const expanded = expandConsts(compact, consts);
|
|
210
|
+
try {
|
|
211
|
+
JSON.parse(expanded);
|
|
212
|
+
return expanded;
|
|
213
|
+
} catch {
|
|
214
|
+
return style;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/html/assets/dimensions.ts
|
|
219
|
+
var DEFAULT_WIDTH = 1080;
|
|
220
|
+
var DEFAULT_HEIGHT = 1920;
|
|
221
|
+
function dimensionsFromStandard(body) {
|
|
222
|
+
if (body.length < 24) return null;
|
|
223
|
+
if (body[0] === 137 && body[1] === 80 && body[2] === 78 && body[3] === 71) {
|
|
224
|
+
const w = body[16] << 24 | body[17] << 16 | body[18] << 8 | body[19];
|
|
225
|
+
const h = body[20] << 24 | body[21] << 16 | body[22] << 8 | body[23];
|
|
226
|
+
if (w > 0 && h > 0) return { w, h };
|
|
227
|
+
}
|
|
228
|
+
if (body[0] === 255 && body[1] === 216) {
|
|
229
|
+
let i = 2;
|
|
230
|
+
while (i + 9 < body.length) {
|
|
231
|
+
if (body[i] !== 255) {
|
|
232
|
+
i++;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const marker = body[i + 1];
|
|
236
|
+
const len = body[i + 2] << 8 | body[i + 3];
|
|
237
|
+
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
|
|
238
|
+
const h = body[i + 5] << 8 | body[i + 6];
|
|
239
|
+
const w = body[i + 7] << 8 | body[i + 8];
|
|
240
|
+
if (w > 0 && h > 0) return { w, h };
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
i += 2 + len;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (body[0] === 71 && body[1] === 73 && body[2] === 70) {
|
|
247
|
+
const w = body[6] | body[7] << 8;
|
|
248
|
+
const h = body[8] | body[9] << 8;
|
|
249
|
+
if (w > 0 && h > 0) return { w, h };
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
function isWebP(body) {
|
|
254
|
+
return body.length >= 12 && body[0] === 82 && body[1] === 73 && body[2] === 70 && body[3] === 70 && body[8] === 87 && body[9] === 69 && body[10] === 66 && body[11] === 80;
|
|
255
|
+
}
|
|
256
|
+
function webpDimensions(body) {
|
|
257
|
+
if (!isWebP(body)) return null;
|
|
258
|
+
let off = 12;
|
|
259
|
+
while (off + 8 <= body.length) {
|
|
260
|
+
const chunk = String.fromCharCode(body[off], body[off + 1], body[off + 2], body[off + 3]);
|
|
261
|
+
const size = body[off + 4] | body[off + 5] << 8 | body[off + 6] << 16 | body[off + 7] << 24;
|
|
262
|
+
const dataStart = off + 8;
|
|
263
|
+
const dataEnd = dataStart + size;
|
|
264
|
+
if (size < 0 || dataEnd > body.length) break;
|
|
265
|
+
if (chunk === "VP8X" && size >= 10) {
|
|
266
|
+
const w = body[dataStart + 4] | body[dataStart + 5] << 8 | body[dataStart + 6] << 16;
|
|
267
|
+
const h = body[dataStart + 7] | body[dataStart + 8] << 8 | body[dataStart + 9] << 16;
|
|
268
|
+
if (w > 0 && h > 0) return { w: w + 1, h: h + 1 };
|
|
269
|
+
}
|
|
270
|
+
if (chunk === "VP8L" && size >= 5) {
|
|
271
|
+
const b0 = body[dataStart];
|
|
272
|
+
const b1 = body[dataStart + 1];
|
|
273
|
+
const b2 = body[dataStart + 2];
|
|
274
|
+
const b3 = body[dataStart + 3];
|
|
275
|
+
const w = 1 + b0 + (b1 & 63) * 256;
|
|
276
|
+
const h = 1 + (b1 >> 6) + (b2 & 15) * 4 + (b3 & 252) * 2;
|
|
277
|
+
if (w > 0 && h > 0) return { w, h };
|
|
278
|
+
}
|
|
279
|
+
off = dataEnd + (size & 1);
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
function isAVIF(body) {
|
|
284
|
+
if (body.length < 12) return false;
|
|
285
|
+
if (String.fromCharCode(body[4], body[5], body[6], body[7]) !== "ftyp") return false;
|
|
286
|
+
const brand = String.fromCharCode(body[8], body[9], body[10], body[11]);
|
|
287
|
+
return brand === "avif" || brand === "avis" || brand === "mif1" || brand === "MA1A";
|
|
288
|
+
}
|
|
289
|
+
function avifDimensions(body) {
|
|
290
|
+
let found = null;
|
|
291
|
+
const walk = (buf) => {
|
|
292
|
+
for (let off = 0; off + 8 <= buf.length; ) {
|
|
293
|
+
let size = buf[off] << 24 | buf[off + 1] << 16 | buf[off + 2] << 8 | buf[off + 3];
|
|
294
|
+
const boxType = String.fromCharCode(buf[off + 4], buf[off + 5], buf[off + 6], buf[off + 7]);
|
|
295
|
+
let header = 8;
|
|
296
|
+
if (size === 1 && off + 16 <= buf.length) {
|
|
297
|
+
size = Number(
|
|
298
|
+
BigInt(buf[off + 8]) << 56n | BigInt(buf[off + 9]) << 48n | BigInt(buf[off + 10]) << 40n | BigInt(buf[off + 11]) << 32n | BigInt(buf[off + 12]) << 24n | BigInt(buf[off + 13]) << 16n | BigInt(buf[off + 14]) << 8n | BigInt(buf[off + 15])
|
|
299
|
+
);
|
|
300
|
+
header = 16;
|
|
301
|
+
}
|
|
302
|
+
if (size < header || off + size > buf.length) break;
|
|
303
|
+
const payloadStart = off + header;
|
|
304
|
+
const payloadEnd = off + size;
|
|
305
|
+
const payload = buf.slice(payloadStart, payloadEnd);
|
|
306
|
+
if (boxType === "ispe" && payload.length >= 12) {
|
|
307
|
+
const w = payload[4] << 24 | payload[5] << 16 | payload[6] << 8 | payload[7];
|
|
308
|
+
const h = payload[8] << 24 | payload[9] << 16 | payload[10] << 8 | payload[11];
|
|
309
|
+
if (w > 0 && h > 0) {
|
|
310
|
+
found = { w, h };
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const containers = ["meta", "iprp", "moov", "trak", "mdia", "minf", "stbl", "stsd", "ipco"];
|
|
315
|
+
if (containers.includes(boxType)) {
|
|
316
|
+
let child = payload;
|
|
317
|
+
if (payload.length >= 4) child = payload.slice(4);
|
|
318
|
+
if (!walk(child)) return false;
|
|
319
|
+
}
|
|
320
|
+
off += size;
|
|
321
|
+
}
|
|
322
|
+
return true;
|
|
323
|
+
};
|
|
324
|
+
walk(body);
|
|
325
|
+
return found;
|
|
326
|
+
}
|
|
327
|
+
function imageDimensions(body, mime) {
|
|
328
|
+
if (!body || body.length === 0) return { w: DEFAULT_WIDTH, h: DEFAULT_HEIGHT };
|
|
329
|
+
const std = dimensionsFromStandard(body);
|
|
330
|
+
if (std) return std;
|
|
331
|
+
const m = mime.trim().toLowerCase();
|
|
332
|
+
if (m === "image/webp" || isWebP(body)) {
|
|
333
|
+
const d = webpDimensions(body);
|
|
334
|
+
if (d) return d;
|
|
335
|
+
}
|
|
336
|
+
if (m === "image/avif" || isAVIF(body)) {
|
|
337
|
+
const d = avifDimensions(body);
|
|
338
|
+
if (d) return d;
|
|
339
|
+
}
|
|
340
|
+
return { w: DEFAULT_WIDTH, h: DEFAULT_HEIGHT };
|
|
341
|
+
}
|
|
342
|
+
function decodeDataUri(uri) {
|
|
343
|
+
const match = /^data:([^;,]+)?(?:;base64)?,(.*)$/s.exec(uri);
|
|
344
|
+
if (!match) return null;
|
|
345
|
+
const mime = match[1] || "application/octet-stream";
|
|
346
|
+
const data = match[2];
|
|
347
|
+
if (/;base64/i.test(uri.slice(0, uri.indexOf(",") + 1))) {
|
|
348
|
+
const binary = atob(data);
|
|
349
|
+
const bytes2 = new Uint8Array(binary.length);
|
|
350
|
+
for (let i = 0; i < binary.length; i++) bytes2[i] = binary.charCodeAt(i);
|
|
351
|
+
return { mime, bytes: bytes2 };
|
|
352
|
+
}
|
|
353
|
+
const decoded = decodeURIComponent(data);
|
|
354
|
+
const bytes = new TextEncoder().encode(decoded);
|
|
355
|
+
return { mime, bytes };
|
|
356
|
+
}
|
|
357
|
+
function encodeDataUri(mime, bytes) {
|
|
358
|
+
let binary = "";
|
|
359
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
360
|
+
return `data:${mime};base64,${btoa(binary)}`;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// src/html/assets/refs.ts
|
|
364
|
+
function looksLikeHttpUrl(raw) {
|
|
365
|
+
const u = raw.trim().toLowerCase();
|
|
366
|
+
return u.startsWith("http://") || u.startsWith("https://");
|
|
367
|
+
}
|
|
368
|
+
function isDataUri(raw) {
|
|
369
|
+
return raw.startsWith("data:");
|
|
370
|
+
}
|
|
371
|
+
function hasWindowsDrive(s) {
|
|
372
|
+
if (s.length < 2 || s[1] !== ":") return false;
|
|
373
|
+
const c = s[0];
|
|
374
|
+
return c >= "a" && c <= "z" || c >= "A" && c <= "Z";
|
|
375
|
+
}
|
|
376
|
+
function isLocalAssetRef(raw) {
|
|
377
|
+
const trimmed = raw.trim();
|
|
378
|
+
if (!trimmed) return false;
|
|
379
|
+
if (looksLikeHttpUrl(trimmed)) return false;
|
|
380
|
+
const lower = trimmed.toLowerCase();
|
|
381
|
+
if (lower.startsWith("file:")) return true;
|
|
382
|
+
return trimmed.includes("\\") || trimmed.includes("/") || hasWindowsDrive(trimmed);
|
|
383
|
+
}
|
|
384
|
+
function isAssetReference(raw) {
|
|
385
|
+
return looksLikeHttpUrl(raw) || isLocalAssetRef(raw) || isDataUri(raw);
|
|
386
|
+
}
|
|
387
|
+
function resolveAssetReference(raw, consts) {
|
|
388
|
+
return expandConsts(raw.trim(), consts);
|
|
389
|
+
}
|
|
390
|
+
function collectAssetUrls(doc) {
|
|
391
|
+
const consts = doc.consts ?? {};
|
|
392
|
+
const seen = /* @__PURE__ */ new Set();
|
|
393
|
+
const out = [];
|
|
394
|
+
const add = (raw) => {
|
|
395
|
+
const u = resolveAssetReference(raw ?? "", consts);
|
|
396
|
+
if (!u || !isAssetReference(u) || seen.has(u)) return;
|
|
397
|
+
seen.add(u);
|
|
398
|
+
out.push(u);
|
|
399
|
+
};
|
|
400
|
+
for (const page of doc.pages ?? []) {
|
|
401
|
+
add(page.imageUrl);
|
|
402
|
+
for (const t of page.texts ?? []) add(t.imageRef);
|
|
403
|
+
for (const m of page.masks ?? []) add(m.imageRef);
|
|
404
|
+
}
|
|
405
|
+
for (const f of doc.fonts ?? []) add(f);
|
|
406
|
+
return out;
|
|
407
|
+
}
|
|
408
|
+
function assetRef(resolvedUrl, asset, linksOnly) {
|
|
409
|
+
if (linksOnly) return resolvedUrl;
|
|
410
|
+
if (!asset) return resolvedUrl;
|
|
411
|
+
return encodeDataUri(asset.mime, asset.bytes);
|
|
412
|
+
}
|
|
413
|
+
function fontSrcUrl(fontUrl, asset, linksOnly) {
|
|
414
|
+
if (linksOnly) return `url(${fontUrl})`;
|
|
415
|
+
if (!asset) return `url(${fontUrl})`;
|
|
416
|
+
return `url(${encodeDataUri(asset.mime, asset.bytes)})`;
|
|
417
|
+
}
|
|
418
|
+
function buildAssetMap(docs, linksOnly) {
|
|
419
|
+
const list = Array.isArray(docs) ? docs : [docs];
|
|
420
|
+
const map = /* @__PURE__ */ new Map();
|
|
421
|
+
if (linksOnly) return map;
|
|
422
|
+
for (const doc of list) {
|
|
423
|
+
for (const url of collectAssetUrls(doc)) {
|
|
424
|
+
if (isDataUri(url)) {
|
|
425
|
+
const asset = decodeDataUri(url);
|
|
426
|
+
if (asset) map.set(url, asset);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
for (const font of doc.fonts ?? []) {
|
|
430
|
+
const u = resolveAssetReference(font, doc.consts ?? {});
|
|
431
|
+
if (isDataUri(u)) {
|
|
432
|
+
const asset = decodeDataUri(u);
|
|
433
|
+
if (asset) map.set(u, asset);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return map;
|
|
438
|
+
}
|
|
439
|
+
function faceFormat(mime) {
|
|
440
|
+
switch (mime.trim().toLowerCase()) {
|
|
441
|
+
case "font/woff2":
|
|
442
|
+
return " format('woff2')";
|
|
443
|
+
case "font/woff":
|
|
444
|
+
return " format('woff')";
|
|
445
|
+
case "font/ttf":
|
|
446
|
+
return " format('truetype')";
|
|
447
|
+
case "font/otf":
|
|
448
|
+
return " format('opentype')";
|
|
449
|
+
default:
|
|
450
|
+
return "";
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function fontFamilyNameForURL(fontURL, index) {
|
|
454
|
+
try {
|
|
455
|
+
const u = new URL(fontURL);
|
|
456
|
+
const family = u.searchParams.get("family");
|
|
457
|
+
if (family) {
|
|
458
|
+
const name = family.split(":")[0]?.replace(/\+/g, " ");
|
|
459
|
+
if (name) return name;
|
|
460
|
+
}
|
|
461
|
+
} catch {
|
|
462
|
+
}
|
|
463
|
+
return `CompiledFont_${index}`;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// src/html/text/inlineMarkup.ts
|
|
467
|
+
var DELIMS = [
|
|
468
|
+
{ open: "***", close: "***", tagOpen: "<strong><em>", tagClose: "</em></strong>" },
|
|
469
|
+
{ open: "**", close: "**", tagOpen: "<strong>", tagClose: "</strong>" },
|
|
470
|
+
{ open: "*", close: "*", tagOpen: "<em>", tagClose: "</em>" },
|
|
471
|
+
{ open: "_", close: "_", tagOpen: "<u>", tagClose: "</u>" },
|
|
472
|
+
{ open: "~", close: "~", tagOpen: "<s>", tagClose: "</s>" }
|
|
473
|
+
];
|
|
474
|
+
function escapeHtml(s) {
|
|
475
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
476
|
+
}
|
|
477
|
+
function renderSegment(s) {
|
|
478
|
+
let out = "";
|
|
479
|
+
let i = 0;
|
|
480
|
+
while (i < s.length) {
|
|
481
|
+
if (s[i] === "\\" && i + 1 < s.length) {
|
|
482
|
+
out += escapeHtml(s[i + 1]);
|
|
483
|
+
i += 2;
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
let matched = false;
|
|
487
|
+
for (const d of DELIMS) {
|
|
488
|
+
if (!s.startsWith(d.open, i)) continue;
|
|
489
|
+
const innerStart = i + d.open.length;
|
|
490
|
+
const closeAt = s.indexOf(d.close, innerStart);
|
|
491
|
+
if (closeAt <= innerStart) continue;
|
|
492
|
+
out += d.tagOpen + renderSegment(s.slice(innerStart, closeAt)) + d.tagClose;
|
|
493
|
+
i = closeAt + d.close.length;
|
|
494
|
+
matched = true;
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
if (matched) continue;
|
|
498
|
+
out += escapeHtml(s[i]);
|
|
499
|
+
i += 1;
|
|
500
|
+
}
|
|
501
|
+
return out;
|
|
502
|
+
}
|
|
503
|
+
function plainSegment(s) {
|
|
504
|
+
let out = "";
|
|
505
|
+
let i = 0;
|
|
506
|
+
while (i < s.length) {
|
|
507
|
+
if (s[i] === "\\" && i + 1 < s.length) {
|
|
508
|
+
out += s[i + 1];
|
|
509
|
+
i += 2;
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
let matched = false;
|
|
513
|
+
for (const d of DELIMS) {
|
|
514
|
+
if (!s.startsWith(d.open, i)) continue;
|
|
515
|
+
const innerStart = i + d.open.length;
|
|
516
|
+
const closeAt = s.indexOf(d.close, innerStart);
|
|
517
|
+
if (closeAt <= innerStart) continue;
|
|
518
|
+
out += plainSegment(s.slice(innerStart, closeAt));
|
|
519
|
+
i = closeAt + d.close.length;
|
|
520
|
+
matched = true;
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
if (matched) continue;
|
|
524
|
+
out += s[i];
|
|
525
|
+
i += 1;
|
|
526
|
+
}
|
|
527
|
+
return out;
|
|
528
|
+
}
|
|
529
|
+
function renderInlineHTML(content) {
|
|
530
|
+
if (!content) return "";
|
|
531
|
+
return content.split("\n").map((line) => renderSegment(line)).join("<br/>");
|
|
532
|
+
}
|
|
533
|
+
function plainTextForLayout(content) {
|
|
534
|
+
if (!content) return "";
|
|
535
|
+
return content.split("\n").map((line) => plainSegment(line)).join("\n");
|
|
536
|
+
}
|
|
537
|
+
function normalizeTextContent(content) {
|
|
538
|
+
return content.replace(/^[\t ]+/, "").replace(/[\t \r\n]+$/, "");
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// src/html/resolve/resolveDocumentPure.ts
|
|
542
|
+
function resolveDocumentPure(doc) {
|
|
543
|
+
const consts = doc.consts;
|
|
544
|
+
if (!consts || Object.keys(consts).length === 0) return doc;
|
|
545
|
+
return {
|
|
546
|
+
...doc,
|
|
547
|
+
pages: doc.pages.map((p) => ({
|
|
548
|
+
...p,
|
|
549
|
+
style: expandConstsInStyle(p.style, consts),
|
|
550
|
+
imageUrl: expandConsts(p.imageUrl.trim(), consts),
|
|
551
|
+
texts: (p.texts ?? []).map((t) => ({
|
|
552
|
+
...t,
|
|
553
|
+
style: expandConstsInStyle(t.style, consts),
|
|
554
|
+
content: normalizeTextContent(expandConsts(t.content, consts)),
|
|
555
|
+
imageRef: expandConsts((t.imageRef ?? "").trim(), consts)
|
|
556
|
+
})),
|
|
557
|
+
masks: (p.masks ?? []).map((m) => ({
|
|
558
|
+
...m,
|
|
559
|
+
style: expandConstsInStyle(m.style, consts),
|
|
560
|
+
imageRef: expandConsts((m.imageRef ?? "").trim(), consts)
|
|
561
|
+
}))
|
|
562
|
+
}))
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// src/html/document/pageBlocks.ts
|
|
567
|
+
var BlockText = "text";
|
|
568
|
+
var BlockMask = "mask";
|
|
569
|
+
function pageBlocksByIndex(page) {
|
|
570
|
+
const out = [];
|
|
571
|
+
for (const t of page.texts ?? []) {
|
|
572
|
+
out.push({ kind: BlockText, text: t });
|
|
573
|
+
}
|
|
574
|
+
for (const m of page.masks ?? []) {
|
|
575
|
+
out.push({ kind: BlockMask, mask: m });
|
|
576
|
+
}
|
|
577
|
+
out.sort((a, b) => blockIndex(a) - blockIndex(b));
|
|
578
|
+
return out;
|
|
579
|
+
}
|
|
580
|
+
function blockIndex(e) {
|
|
581
|
+
if (e.kind === BlockText && e.text) return e.text.index;
|
|
582
|
+
if (e.kind === BlockMask && e.mask) return e.mask.index;
|
|
583
|
+
return 0;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// src/html/style/keys.ts
|
|
587
|
+
var KeyPosition = "position";
|
|
588
|
+
var KeyLeft = "left";
|
|
589
|
+
var KeyTop = "top";
|
|
590
|
+
var KeyWidth = "width";
|
|
591
|
+
var KeyHeight = "height";
|
|
592
|
+
var KeyBoxSizing = "boxSizing";
|
|
593
|
+
var KeyPadding = "padding";
|
|
594
|
+
var KeyPaddingTop = "paddingTop";
|
|
595
|
+
var KeyPaddingRight = "paddingRight";
|
|
596
|
+
var KeyPaddingBottom = "paddingBottom";
|
|
597
|
+
var KeyPaddingLeft = "paddingLeft";
|
|
598
|
+
var KeyBorder = "border";
|
|
599
|
+
var KeyBorderTop = "borderTop";
|
|
600
|
+
var KeyBorderRight = "borderRight";
|
|
601
|
+
var KeyBorderBottom = "borderBottom";
|
|
602
|
+
var KeyBorderLeft = "borderLeft";
|
|
603
|
+
var KeyBorderWidth = "borderWidth";
|
|
604
|
+
var KeyBorderStyle = "borderStyle";
|
|
605
|
+
var KeyBorderColor = "borderColor";
|
|
606
|
+
var KeyBorderRadius = "borderRadius";
|
|
607
|
+
var KeyBorderTopLeftRadius = "borderTopLeftRadius";
|
|
608
|
+
var KeyBorderTopRightRadius = "borderTopRightRadius";
|
|
609
|
+
var KeyBorderBottomRightRadius = "borderBottomRightRadius";
|
|
610
|
+
var KeyBorderBottomLeftRadius = "borderBottomLeftRadius";
|
|
611
|
+
var KeyBoxShadow = "boxShadow";
|
|
612
|
+
var KeyTextShadow = "textShadow";
|
|
613
|
+
var KeyGlow = "glow";
|
|
614
|
+
var KeyBevel = "bevel";
|
|
615
|
+
var KeyBlur = "blur";
|
|
616
|
+
var KeyBlurLeft = "blurLeft";
|
|
617
|
+
var KeyBlurRight = "blurRight";
|
|
618
|
+
var KeyBlurTop = "blurTop";
|
|
619
|
+
var KeyBlurBottom = "blurBottom";
|
|
620
|
+
var KeyBackground = "background";
|
|
621
|
+
var KeyColor = "color";
|
|
622
|
+
var KeyTextAlign = "textAlign";
|
|
623
|
+
var KeyAlignItems = "alignItems";
|
|
624
|
+
var KeyTextDecoration = "textDecoration";
|
|
625
|
+
var KeyTextDecorationLine = "textDecorationLine";
|
|
626
|
+
var KeyLetterSpacing = "letterSpacing";
|
|
627
|
+
var KeyLineHeight = "lineHeight";
|
|
628
|
+
var KeyWordSpacing = "wordSpacing";
|
|
629
|
+
var KeyWhiteSpace = "whiteSpace";
|
|
630
|
+
var KeyTextTransform = "textTransform";
|
|
631
|
+
var KeyTextIndent = "textIndent";
|
|
632
|
+
var KeyTextOverflow = "textOverflow";
|
|
633
|
+
var KeyOpacity = "opacity";
|
|
634
|
+
var KeyStroke = "stroke";
|
|
635
|
+
var KeyStrokeWidth = "strokeWidth";
|
|
636
|
+
var KeyStrokeColor = "strokeColor";
|
|
637
|
+
var KeyTransform = "transform";
|
|
638
|
+
var KeyTransformOrigin = "transformOrigin";
|
|
639
|
+
var KeyTranslate = "translate";
|
|
640
|
+
var KeyRotate = "rotate";
|
|
641
|
+
var KeyScale = "scale";
|
|
642
|
+
var KeySkew = "skew";
|
|
643
|
+
var KeyMatrix = "matrix";
|
|
644
|
+
var KeyFontFamily = "fontFamily";
|
|
645
|
+
var KeyFontSize = "fontSize";
|
|
646
|
+
var KeyFontWeight = "fontWeight";
|
|
647
|
+
var KeyFontStyle = "fontStyle";
|
|
648
|
+
var KeyFontVariant = "fontVariant";
|
|
649
|
+
var KeyFontStretch = "fontStretch";
|
|
650
|
+
var boxKeys = /* @__PURE__ */ new Set([
|
|
651
|
+
KeyHeight,
|
|
652
|
+
KeyBackground,
|
|
653
|
+
KeyPadding,
|
|
654
|
+
KeyPaddingTop,
|
|
655
|
+
KeyPaddingRight,
|
|
656
|
+
KeyPaddingBottom,
|
|
657
|
+
KeyPaddingLeft,
|
|
658
|
+
KeyBorder,
|
|
659
|
+
KeyBorderTop,
|
|
660
|
+
KeyBorderRight,
|
|
661
|
+
KeyBorderBottom,
|
|
662
|
+
KeyBorderLeft,
|
|
663
|
+
KeyBorderWidth,
|
|
664
|
+
KeyBorderStyle,
|
|
665
|
+
KeyBorderColor,
|
|
666
|
+
KeyBorderRadius,
|
|
667
|
+
KeyBorderTopLeftRadius,
|
|
668
|
+
KeyBorderTopRightRadius,
|
|
669
|
+
KeyBorderBottomRightRadius,
|
|
670
|
+
KeyBorderBottomLeftRadius,
|
|
671
|
+
KeyBoxShadow,
|
|
672
|
+
KeyGlow,
|
|
673
|
+
KeyBevel,
|
|
674
|
+
KeyBlur,
|
|
675
|
+
KeyBlurLeft,
|
|
676
|
+
KeyBlurRight,
|
|
677
|
+
KeyBlurTop,
|
|
678
|
+
KeyBlurBottom
|
|
679
|
+
]);
|
|
680
|
+
var textKeys = /* @__PURE__ */ new Set([
|
|
681
|
+
KeyColor,
|
|
682
|
+
KeyTextAlign,
|
|
683
|
+
KeyAlignItems,
|
|
684
|
+
KeyTextDecoration,
|
|
685
|
+
KeyTextDecorationLine,
|
|
686
|
+
KeyLetterSpacing,
|
|
687
|
+
KeyLineHeight,
|
|
688
|
+
KeyWordSpacing,
|
|
689
|
+
KeyWhiteSpace,
|
|
690
|
+
KeyTextTransform,
|
|
691
|
+
KeyTextIndent,
|
|
692
|
+
KeyTextOverflow,
|
|
693
|
+
KeyOpacity,
|
|
694
|
+
KeyTextShadow,
|
|
695
|
+
KeyStroke,
|
|
696
|
+
KeyStrokeWidth,
|
|
697
|
+
KeyStrokeColor,
|
|
698
|
+
KeyFontFamily,
|
|
699
|
+
KeyFontSize,
|
|
700
|
+
KeyFontWeight,
|
|
701
|
+
KeyFontStyle,
|
|
702
|
+
KeyFontVariant,
|
|
703
|
+
KeyFontStretch
|
|
704
|
+
]);
|
|
705
|
+
var transformKeys = /* @__PURE__ */ new Set([
|
|
706
|
+
KeyTransform,
|
|
707
|
+
KeyTransformOrigin,
|
|
708
|
+
KeyTranslate,
|
|
709
|
+
KeyRotate,
|
|
710
|
+
KeyScale,
|
|
711
|
+
KeySkew,
|
|
712
|
+
KeyMatrix
|
|
713
|
+
]);
|
|
714
|
+
function isBoxKey(key) {
|
|
715
|
+
return boxKeys.has(key);
|
|
716
|
+
}
|
|
717
|
+
function isTextKey(key) {
|
|
718
|
+
return textKeys.has(key);
|
|
719
|
+
}
|
|
720
|
+
function isTransformKey(key) {
|
|
721
|
+
return transformKeys.has(key);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// src/html/style/values.ts
|
|
725
|
+
function stringifyCSSValue(raw) {
|
|
726
|
+
if (raw === null || raw === void 0) return "";
|
|
727
|
+
if (typeof raw === "string") return raw.trim();
|
|
728
|
+
if (typeof raw === "number") return formatJSONNumber(raw);
|
|
729
|
+
if (typeof raw === "boolean") return raw ? "1" : "0";
|
|
730
|
+
return JSON.stringify(raw).trim();
|
|
731
|
+
}
|
|
732
|
+
function formatJSONNumber(value) {
|
|
733
|
+
if (Math.abs(value - Math.trunc(value)) < 1e-9 && Math.abs(value) < 1e12) {
|
|
734
|
+
return String(Math.trunc(value));
|
|
735
|
+
}
|
|
736
|
+
return value.toString();
|
|
737
|
+
}
|
|
738
|
+
function sanitizeCSSValue(value) {
|
|
739
|
+
const trimmed = value.trim();
|
|
740
|
+
if (!trimmed) return "";
|
|
741
|
+
return trimmed.replaceAll(";", "");
|
|
742
|
+
}
|
|
743
|
+
function hasStyleValue(key, raw) {
|
|
744
|
+
if (raw === null || raw === void 0) return false;
|
|
745
|
+
if (typeof raw === "boolean") return raw;
|
|
746
|
+
const value = sanitizeCSSValue(stringifyCSSValue(raw));
|
|
747
|
+
if (!value) return false;
|
|
748
|
+
if (omitZeroForKey(key) && isZeroLikeCSSValue(value)) return false;
|
|
749
|
+
return true;
|
|
750
|
+
}
|
|
751
|
+
function omitZeroForKey(key) {
|
|
752
|
+
switch (key) {
|
|
753
|
+
case KeyHeight:
|
|
754
|
+
case KeyWidth:
|
|
755
|
+
case KeyPadding:
|
|
756
|
+
case KeyPaddingTop:
|
|
757
|
+
case KeyPaddingRight:
|
|
758
|
+
case KeyPaddingBottom:
|
|
759
|
+
case KeyPaddingLeft:
|
|
760
|
+
case KeyBorderWidth:
|
|
761
|
+
case KeyStrokeWidth:
|
|
762
|
+
case KeyBorderRadius:
|
|
763
|
+
case KeyBorderTopLeftRadius:
|
|
764
|
+
case KeyBorderTopRightRadius:
|
|
765
|
+
case KeyBorderBottomRightRadius:
|
|
766
|
+
case KeyBorderBottomLeftRadius:
|
|
767
|
+
case KeyLetterSpacing:
|
|
768
|
+
case KeyWordSpacing:
|
|
769
|
+
case KeyLineHeight:
|
|
770
|
+
case KeyTextIndent:
|
|
771
|
+
case KeyBlur:
|
|
772
|
+
case KeyBlurLeft:
|
|
773
|
+
case KeyBlurRight:
|
|
774
|
+
case KeyBlurTop:
|
|
775
|
+
case KeyBlurBottom:
|
|
776
|
+
return true;
|
|
777
|
+
default:
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
function isZeroLikeCSSValue(value) {
|
|
782
|
+
const v = value.trim().toLowerCase();
|
|
783
|
+
switch (v) {
|
|
784
|
+
case "0":
|
|
785
|
+
case "0%":
|
|
786
|
+
case "0px":
|
|
787
|
+
case "0em":
|
|
788
|
+
case "0rem":
|
|
789
|
+
case "0pt":
|
|
790
|
+
case "0cqh":
|
|
791
|
+
case "0ch":
|
|
792
|
+
case "0vw":
|
|
793
|
+
case "0vh":
|
|
794
|
+
case "0vmin":
|
|
795
|
+
case "0vmax":
|
|
796
|
+
return true;
|
|
797
|
+
}
|
|
798
|
+
const units = ["px", "%", "em", "rem", "pt", "cqh", "ch"];
|
|
799
|
+
for (const unit of units) {
|
|
800
|
+
if (!v.endsWith(unit)) continue;
|
|
801
|
+
const n2 = Number.parseFloat(v.slice(0, -unit.length));
|
|
802
|
+
if (Number.isFinite(n2) && n2 === 0) return true;
|
|
803
|
+
}
|
|
804
|
+
const n = Number.parseFloat(v);
|
|
805
|
+
return Number.isFinite(n) && n === 0;
|
|
806
|
+
}
|
|
807
|
+
function filterStyleMap(style) {
|
|
808
|
+
if (Object.keys(style).length === 0) return style;
|
|
809
|
+
const out = {};
|
|
810
|
+
for (const [key, raw] of Object.entries(style)) {
|
|
811
|
+
if (hasStyleValue(key, raw)) {
|
|
812
|
+
out[key] = raw;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return out;
|
|
816
|
+
}
|
|
817
|
+
function pctString(value) {
|
|
818
|
+
return `${value}%`;
|
|
819
|
+
}
|
|
820
|
+
function pxString(value) {
|
|
821
|
+
return `${value}px`;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// src/html/style/names.ts
|
|
825
|
+
var aliasToCanonical = {
|
|
826
|
+
"border-radius": KeyBorderRadius,
|
|
827
|
+
"border-width": KeyBorderWidth,
|
|
828
|
+
"border-style": KeyBorderStyle,
|
|
829
|
+
"border-color": KeyBorderColor,
|
|
830
|
+
"box-shadow": KeyBoxShadow,
|
|
831
|
+
"text-shadow": KeyTextShadow,
|
|
832
|
+
"text-align": KeyTextAlign,
|
|
833
|
+
"align-items": KeyAlignItems,
|
|
834
|
+
"vertical-align": KeyAlignItems,
|
|
835
|
+
"font-family": KeyFontFamily,
|
|
836
|
+
"font-weight": KeyFontWeight,
|
|
837
|
+
"font-style": KeyFontStyle,
|
|
838
|
+
"line-height": KeyLineHeight,
|
|
839
|
+
"letter-spacing": KeyLetterSpacing,
|
|
840
|
+
"text-decoration": KeyTextDecoration,
|
|
841
|
+
"background-color": KeyBackground,
|
|
842
|
+
"stroke-width": KeyStrokeWidth,
|
|
843
|
+
"stroke-color": KeyStrokeColor,
|
|
844
|
+
"text-stroke": KeyStroke,
|
|
845
|
+
backGround: KeyBackground,
|
|
846
|
+
backgroundColor: KeyBackground,
|
|
847
|
+
br: KeyBorderRadius,
|
|
848
|
+
bw: KeyBorderWidth,
|
|
849
|
+
bc: KeyBorderColor,
|
|
850
|
+
bs: KeyBorderStyle,
|
|
851
|
+
bg: KeyBackground,
|
|
852
|
+
ta: KeyTextAlign,
|
|
853
|
+
ts: KeyTextShadow,
|
|
854
|
+
bsh: KeyBoxShadow,
|
|
855
|
+
blur: KeyBlur,
|
|
856
|
+
"blur-left": KeyBlurLeft,
|
|
857
|
+
"blur-right": KeyBlurRight,
|
|
858
|
+
"blur-top": KeyBlurTop,
|
|
859
|
+
"blur-bottom": KeyBlurBottom,
|
|
860
|
+
ff: KeyFontFamily,
|
|
861
|
+
fw: KeyFontWeight,
|
|
862
|
+
fs: KeyFontStyle,
|
|
863
|
+
pd: KeyPadding,
|
|
864
|
+
sw: KeyStrokeWidth,
|
|
865
|
+
sc: KeyStrokeColor,
|
|
866
|
+
textStroke: KeyStroke,
|
|
867
|
+
textStrokeWidth: KeyStrokeWidth,
|
|
868
|
+
textStrokeColor: KeyStrokeColor
|
|
869
|
+
};
|
|
870
|
+
function resolveName(raw) {
|
|
871
|
+
const key = raw.trim();
|
|
872
|
+
if (!key) return { canonical: "", ok: false };
|
|
873
|
+
const mapped = aliasToCanonical[key];
|
|
874
|
+
if (mapped) return { canonical: mapped, ok: true };
|
|
875
|
+
if (key.includes("-")) {
|
|
876
|
+
const camel = kebabToCamel(key);
|
|
877
|
+
const mappedCamel = aliasToCanonical[camel];
|
|
878
|
+
if (mappedCamel) return { canonical: mappedCamel, ok: true };
|
|
879
|
+
const mappedRaw = aliasToCanonical[key];
|
|
880
|
+
if (mappedRaw) return { canonical: mappedRaw, ok: true };
|
|
881
|
+
return { canonical: camel, ok: isKnownCanonical(camel) };
|
|
882
|
+
}
|
|
883
|
+
const mappedAgain = aliasToCanonical[key];
|
|
884
|
+
if (mappedAgain) return { canonical: mappedAgain, ok: true };
|
|
885
|
+
return { canonical: key, ok: isKnownCanonical(key) };
|
|
886
|
+
}
|
|
887
|
+
function isKnownCanonical(name) {
|
|
888
|
+
return isBoxKey(name) || isTextKey(name) || isTransformKey(name) || name === KeyLeft || name === KeyTop || name === KeyWidth || name === KeyHeight || name === KeyGlow || name === KeyBevel || name === KeyBlur || name === KeyBlurLeft || name === KeyBlurRight || name === KeyBlurTop || name === KeyBlurBottom;
|
|
889
|
+
}
|
|
890
|
+
function kebabToCamel(value) {
|
|
891
|
+
const parts = value.split("-");
|
|
892
|
+
if (parts.length === 0) return value;
|
|
893
|
+
let out = "";
|
|
894
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
895
|
+
const part = parts[i]?.trim();
|
|
896
|
+
if (!part) continue;
|
|
897
|
+
if (i === 0) {
|
|
898
|
+
out += part.toLowerCase();
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
901
|
+
out += part[0].toUpperCase() + part.slice(1).toLowerCase();
|
|
902
|
+
}
|
|
903
|
+
return out;
|
|
904
|
+
}
|
|
905
|
+
function normalizeStyle(style) {
|
|
906
|
+
const rawObject = parseStyleObject(style);
|
|
907
|
+
if (!rawObject) return {};
|
|
908
|
+
const out = {};
|
|
909
|
+
const applyKeys = (vendorOnly) => {
|
|
910
|
+
for (const [rawKey, value] of Object.entries(rawObject)) {
|
|
911
|
+
const vendor = isVendorRawKey(rawKey);
|
|
912
|
+
if (vendorOnly !== vendor) continue;
|
|
913
|
+
const canonical = canonicalKeyForRaw(rawKey);
|
|
914
|
+
if (!canonical || isVendorKey(canonical)) continue;
|
|
915
|
+
if (vendorOnly && Object.prototype.hasOwnProperty.call(out, canonical)) continue;
|
|
916
|
+
out[canonical] = value;
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
applyKeys(false);
|
|
920
|
+
applyKeys(true);
|
|
921
|
+
mergeBackgroundKeys(out);
|
|
922
|
+
return filterStyleMap(out);
|
|
923
|
+
}
|
|
924
|
+
function parseStyleObject(style) {
|
|
925
|
+
if (typeof style === "string") {
|
|
926
|
+
const trimmed = style.trim();
|
|
927
|
+
if (!trimmed || trimmed === "{}") return null;
|
|
928
|
+
try {
|
|
929
|
+
const parsed = JSON.parse(trimmed);
|
|
930
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
931
|
+
return parsed;
|
|
932
|
+
} catch {
|
|
933
|
+
return null;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
if (!style || typeof style !== "object" || Array.isArray(style)) return null;
|
|
937
|
+
if (Object.keys(style).length === 0) return null;
|
|
938
|
+
return style;
|
|
939
|
+
}
|
|
940
|
+
function canonicalKeyForRaw(rawKey) {
|
|
941
|
+
for (const candidate of expandRawKey(rawKey)) {
|
|
942
|
+
const resolved = resolveName(candidate);
|
|
943
|
+
if (resolved.ok) return resolved.canonical;
|
|
944
|
+
if (!isVendorKey(candidate)) return candidate;
|
|
945
|
+
}
|
|
946
|
+
return "";
|
|
947
|
+
}
|
|
948
|
+
function isVendorRawKey(key) {
|
|
949
|
+
if (isVendorKey(key)) return true;
|
|
950
|
+
return stripVendorPrefix(key).ok;
|
|
951
|
+
}
|
|
952
|
+
function expandRawKey(rawKey) {
|
|
953
|
+
const key = rawKey.trim();
|
|
954
|
+
const stripped = stripVendorPrefix(key);
|
|
955
|
+
if (stripped.ok) return [stripped.value, key];
|
|
956
|
+
return [key];
|
|
957
|
+
}
|
|
958
|
+
function stripVendorPrefix(key) {
|
|
959
|
+
const trimmed = key.trim();
|
|
960
|
+
const lower = trimmed.toLowerCase();
|
|
961
|
+
for (const prefix of ["-webkit-", "webkit-", "webkit"]) {
|
|
962
|
+
if (!lower.startsWith(prefix)) continue;
|
|
963
|
+
const rest = trimmed.slice(prefix.length);
|
|
964
|
+
if (!rest) return { value: "", ok: false };
|
|
965
|
+
return { value: decapitalize(rest), ok: true };
|
|
966
|
+
}
|
|
967
|
+
if (trimmed.startsWith("Webkit")) {
|
|
968
|
+
return { value: decapitalize(trimmed.slice(6)), ok: true };
|
|
969
|
+
}
|
|
970
|
+
if (trimmed.startsWith("webkit")) {
|
|
971
|
+
return { value: decapitalize(trimmed.slice(6)), ok: true };
|
|
972
|
+
}
|
|
973
|
+
return { value: "", ok: false };
|
|
974
|
+
}
|
|
975
|
+
function decapitalize(value) {
|
|
976
|
+
if (!value) return value;
|
|
977
|
+
return value[0].toLowerCase() + value.slice(1);
|
|
978
|
+
}
|
|
979
|
+
function isVendorKey(key) {
|
|
980
|
+
return key.startsWith("Webkit") || key.startsWith("webkit") || key.toLowerCase().startsWith("-webkit-");
|
|
981
|
+
}
|
|
982
|
+
function mergeBackgroundKeys(style) {
|
|
983
|
+
if (Object.prototype.hasOwnProperty.call(style, KeyBackground)) {
|
|
984
|
+
style[KeyBackground] = style[KeyBackground];
|
|
985
|
+
delete style.backgroundColor;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// src/html/style/percent.ts
|
|
990
|
+
var defaultHandlers = [];
|
|
991
|
+
function applyPercentHandlers(style, dims) {
|
|
992
|
+
if (Object.keys(style).length === 0) return style;
|
|
993
|
+
const resolvedDims = resolveImageDims(dims);
|
|
994
|
+
const keyHandler = /* @__PURE__ */ new Map();
|
|
995
|
+
for (const handler of defaultHandlers) {
|
|
996
|
+
for (const key of handler.keys()) {
|
|
997
|
+
keyHandler.set(key, handler);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
const out = {};
|
|
1001
|
+
for (const [key, raw] of Object.entries(style)) {
|
|
1002
|
+
const value = stringifyRaw(raw);
|
|
1003
|
+
if (resolvedDims.preservePercent && value.includes("%")) {
|
|
1004
|
+
out[key] = raw;
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
const handler = keyHandler.get(key);
|
|
1008
|
+
if (handler && value) {
|
|
1009
|
+
const resolved = handler.resolve(key, value, resolvedDims);
|
|
1010
|
+
if (resolved.ok) {
|
|
1011
|
+
out[key] = resolved.resolved;
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
out[key] = raw;
|
|
1016
|
+
}
|
|
1017
|
+
return out;
|
|
1018
|
+
}
|
|
1019
|
+
function resolveImageDims(dims) {
|
|
1020
|
+
const w = dims.W ?? dims.w ?? 0;
|
|
1021
|
+
const h = dims.H ?? dims.h ?? 0;
|
|
1022
|
+
const fontSizePx = dims.FontSizePx ?? dims.fontSizePx ?? 0;
|
|
1023
|
+
const zoom = dims.Zoom ?? dims.zoom ?? 1;
|
|
1024
|
+
const preservePercent = dims.PreservePercent ?? dims.preservePercent ?? false;
|
|
1025
|
+
return { w, h, fontSizePx, zoom: zoom > 0 ? zoom : 1, preservePercent };
|
|
1026
|
+
}
|
|
1027
|
+
function stringifyRaw(raw) {
|
|
1028
|
+
if (raw === null || raw === void 0) return "";
|
|
1029
|
+
if (typeof raw === "string") return raw;
|
|
1030
|
+
return String(raw);
|
|
1031
|
+
}
|
|
1032
|
+
var DimensionHandler = class {
|
|
1033
|
+
keys() {
|
|
1034
|
+
return ["height", "width"];
|
|
1035
|
+
}
|
|
1036
|
+
resolve(key, value, dims) {
|
|
1037
|
+
if (!value.includes("%")) return { resolved: value, ok: true };
|
|
1038
|
+
const base = key === "width" ? dims.w : dims.h;
|
|
1039
|
+
return singlePercentToken(value, base, dims);
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
var PaddingHandler = class {
|
|
1043
|
+
keys() {
|
|
1044
|
+
return ["padding", "paddingTop", "paddingRight", "paddingBottom", "paddingLeft"];
|
|
1045
|
+
}
|
|
1046
|
+
resolve(key, value, dims) {
|
|
1047
|
+
if (!value.includes("%")) return { resolved: value, ok: true };
|
|
1048
|
+
const tokens = value.trim().split(/\s+/).filter(Boolean);
|
|
1049
|
+
if (tokens.length === 0) return { resolved: value, ok: true };
|
|
1050
|
+
if (tokens.length === 1 || key !== "padding") {
|
|
1051
|
+
const base = paddingAxisBase(key, tokens.length, 0, dims);
|
|
1052
|
+
if (tokens.length === 1) {
|
|
1053
|
+
return singlePercentToken(tokens[0], base, dims);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
const bases = shorthandBases(tokens.length);
|
|
1057
|
+
const out = [];
|
|
1058
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
1059
|
+
const token = tokens[i] ?? "";
|
|
1060
|
+
const axis = bases[i] ?? 0;
|
|
1061
|
+
const base = paddingAxisBase(key, tokens.length, axis, dims);
|
|
1062
|
+
const resolved = singlePercentToken(token, base, dims);
|
|
1063
|
+
out.push(resolved.resolved);
|
|
1064
|
+
}
|
|
1065
|
+
return { resolved: out.join(" "), ok: true };
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
function shorthandBases(count) {
|
|
1069
|
+
switch (count) {
|
|
1070
|
+
case 1:
|
|
1071
|
+
return [0, 0, 0, 0];
|
|
1072
|
+
case 2:
|
|
1073
|
+
return [0, 1, 0, 1];
|
|
1074
|
+
case 3:
|
|
1075
|
+
return [0, 1, 2, 1];
|
|
1076
|
+
default:
|
|
1077
|
+
return [0, 1, 2, 3];
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
function paddingAxisBase(key, count, axis, dims) {
|
|
1081
|
+
switch (key) {
|
|
1082
|
+
case "paddingTop":
|
|
1083
|
+
case "paddingBottom":
|
|
1084
|
+
return dims.h;
|
|
1085
|
+
case "paddingLeft":
|
|
1086
|
+
case "paddingRight":
|
|
1087
|
+
return dims.w;
|
|
1088
|
+
}
|
|
1089
|
+
if (count === 1) return max(dims.w, dims.h);
|
|
1090
|
+
switch (axis) {
|
|
1091
|
+
case 0:
|
|
1092
|
+
case 2:
|
|
1093
|
+
return dims.h;
|
|
1094
|
+
case 1:
|
|
1095
|
+
case 3:
|
|
1096
|
+
return dims.w;
|
|
1097
|
+
default:
|
|
1098
|
+
return dims.w;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
var BorderWidthHandler = class {
|
|
1102
|
+
keys() {
|
|
1103
|
+
return ["borderWidth", "borderTopWidth", "borderRightWidth", "borderBottomWidth", "borderLeftWidth"];
|
|
1104
|
+
}
|
|
1105
|
+
resolve(_key, value, dims) {
|
|
1106
|
+
if (!value.includes("%")) return { resolved: value, ok: true };
|
|
1107
|
+
return singlePercentToken(value, dims.w, dims);
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
var LineHeightHandler = class {
|
|
1111
|
+
keys() {
|
|
1112
|
+
return ["lineHeight"];
|
|
1113
|
+
}
|
|
1114
|
+
resolve(_key, value, dims) {
|
|
1115
|
+
if (!value.trim().endsWith("%")) return { resolved: value, ok: true };
|
|
1116
|
+
return singlePercentToken(value, dims.h, dims);
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
var StrokeWidthHandler = class {
|
|
1120
|
+
keys() {
|
|
1121
|
+
return ["strokeWidth"];
|
|
1122
|
+
}
|
|
1123
|
+
resolve(_key, value, dims) {
|
|
1124
|
+
if (!value.includes("%")) return { resolved: value, ok: true };
|
|
1125
|
+
const base = dims.fontSizePx > 0 ? dims.fontSizePx : 1;
|
|
1126
|
+
return singlePercentToken(value, base, dims);
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
var BlurHandler = class {
|
|
1130
|
+
keys() {
|
|
1131
|
+
return ["blur", "blurLeft", "blurRight", "blurTop", "blurBottom"];
|
|
1132
|
+
}
|
|
1133
|
+
resolve(_key, value, dims) {
|
|
1134
|
+
if (!value.includes("%")) return { resolved: value, ok: true };
|
|
1135
|
+
let base = dims.w;
|
|
1136
|
+
if (dims.h > 0 && dims.h < dims.w) base = dims.h;
|
|
1137
|
+
return singlePercentToken(value, base, dims);
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
var TextShadowHandler = class {
|
|
1141
|
+
keys() {
|
|
1142
|
+
return ["textShadow"];
|
|
1143
|
+
}
|
|
1144
|
+
resolve(_key, value, dims) {
|
|
1145
|
+
if (!value.includes("%")) return { resolved: value, ok: true };
|
|
1146
|
+
return { resolved: resolveShadowList(value, dims), ok: true };
|
|
1147
|
+
}
|
|
1148
|
+
};
|
|
1149
|
+
var BoxShadowHandler = class {
|
|
1150
|
+
keys() {
|
|
1151
|
+
return ["boxShadow"];
|
|
1152
|
+
}
|
|
1153
|
+
resolve(_key, value, dims) {
|
|
1154
|
+
if (!value.includes("%")) return { resolved: value, ok: true };
|
|
1155
|
+
return { resolved: resolveShadowList(value, dims), ok: true };
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
function resolveShadowList(value, dims) {
|
|
1159
|
+
const parts = value.split(",");
|
|
1160
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
1161
|
+
const shadow = parts[i]?.trim() ?? "";
|
|
1162
|
+
parts[i] = resolveShadowOne(shadow, dims);
|
|
1163
|
+
}
|
|
1164
|
+
return parts.join(", ");
|
|
1165
|
+
}
|
|
1166
|
+
function resolveShadowOne(shadow, dims) {
|
|
1167
|
+
if (!shadow) return shadow;
|
|
1168
|
+
const tokens = shadow.split(/\s+/).filter(Boolean);
|
|
1169
|
+
if (tokens.length === 0) return shadow;
|
|
1170
|
+
const colorIndex = findColorTokenIndex(tokens);
|
|
1171
|
+
const numericEnd = colorIndex < 0 ? tokens.length : colorIndex;
|
|
1172
|
+
for (let i = 0; i < numericEnd && i < 3; i += 1) {
|
|
1173
|
+
const token = tokens[i];
|
|
1174
|
+
if (token) tokens[i] = resolveShadowToken(token, i, dims);
|
|
1175
|
+
}
|
|
1176
|
+
return tokens.join(" ");
|
|
1177
|
+
}
|
|
1178
|
+
function findColorTokenIndex(tokens) {
|
|
1179
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
1180
|
+
const token = tokens[i] ?? "";
|
|
1181
|
+
if (token.startsWith("#") || token.toLowerCase().startsWith("rgb")) return i;
|
|
1182
|
+
}
|
|
1183
|
+
return -1;
|
|
1184
|
+
}
|
|
1185
|
+
function resolveShadowToken(token, index, dims) {
|
|
1186
|
+
if (!token.endsWith("%")) return token;
|
|
1187
|
+
const pct = Number.parseFloat(token.slice(0, -1));
|
|
1188
|
+
if (!Number.isFinite(pct)) return token;
|
|
1189
|
+
const z = dims.zoom > 0 ? dims.zoom : 1;
|
|
1190
|
+
let base = 0;
|
|
1191
|
+
switch (index) {
|
|
1192
|
+
case 0:
|
|
1193
|
+
base = dims.w * z;
|
|
1194
|
+
break;
|
|
1195
|
+
case 1:
|
|
1196
|
+
base = dims.h * z;
|
|
1197
|
+
break;
|
|
1198
|
+
case 2:
|
|
1199
|
+
base = max(dims.w, dims.h) * z;
|
|
1200
|
+
break;
|
|
1201
|
+
default:
|
|
1202
|
+
return token;
|
|
1203
|
+
}
|
|
1204
|
+
return `${(pct / 100 * base).toFixed(3)}px`;
|
|
1205
|
+
}
|
|
1206
|
+
function percentToPx(percentString, base, zoom) {
|
|
1207
|
+
const pct = Number.parseFloat(percentString.trim().replace(/%$/, ""));
|
|
1208
|
+
if (!Number.isFinite(pct)) return percentString;
|
|
1209
|
+
const z = zoom > 0 ? zoom : 1;
|
|
1210
|
+
return `${(pct / 100 * base * z).toFixed(3)}px`;
|
|
1211
|
+
}
|
|
1212
|
+
function singlePercentToken(value, base, dims) {
|
|
1213
|
+
const v = value.trim();
|
|
1214
|
+
if (!v.endsWith("%")) return { resolved: v, ok: true };
|
|
1215
|
+
return { resolved: percentToPx(v, base, dims.zoom), ok: true };
|
|
1216
|
+
}
|
|
1217
|
+
function max(a, b) {
|
|
1218
|
+
return a > b ? a : b;
|
|
1219
|
+
}
|
|
1220
|
+
defaultHandlers = [
|
|
1221
|
+
new TextShadowHandler(),
|
|
1222
|
+
new BoxShadowHandler(),
|
|
1223
|
+
new BlurHandler(),
|
|
1224
|
+
new BorderWidthHandler(),
|
|
1225
|
+
new StrokeWidthHandler(),
|
|
1226
|
+
new LineHeightHandler(),
|
|
1227
|
+
new PaddingHandler(),
|
|
1228
|
+
new DimensionHandler()
|
|
1229
|
+
];
|
|
1230
|
+
|
|
1231
|
+
// src/html/geometry/layoutHelpers.ts
|
|
1232
|
+
function textSizeBasisPx(canvasW, canvasH) {
|
|
1233
|
+
const w = canvasW;
|
|
1234
|
+
const h = canvasH;
|
|
1235
|
+
if (w <= 0 && h <= 0) return 1;
|
|
1236
|
+
if (w <= 0) return h;
|
|
1237
|
+
if (h <= 0) return w;
|
|
1238
|
+
return w < h ? w : h;
|
|
1239
|
+
}
|
|
1240
|
+
function textFontSizePx(textSizePct, canvasW, canvasH) {
|
|
1241
|
+
const px = textSizePct / 100 * textSizeBasisPx(canvasW, canvasH);
|
|
1242
|
+
return px < 1 ? 1 : px;
|
|
1243
|
+
}
|
|
1244
|
+
function paddingHorizontal(p) {
|
|
1245
|
+
return p.left + p.right;
|
|
1246
|
+
}
|
|
1247
|
+
function paddingVertical(p) {
|
|
1248
|
+
return p.top + p.bottom;
|
|
1249
|
+
}
|
|
1250
|
+
function cssLengthToPx(s, refFontPx) {
|
|
1251
|
+
s = s.trim().toLowerCase();
|
|
1252
|
+
if (!s) return 0;
|
|
1253
|
+
if (s.endsWith("px")) {
|
|
1254
|
+
const v2 = parseFloat(s.slice(0, -2));
|
|
1255
|
+
return Number.isFinite(v2) ? Math.max(0, v2) : 0;
|
|
1256
|
+
}
|
|
1257
|
+
if (s.endsWith("em")) {
|
|
1258
|
+
const v2 = parseFloat(s.slice(0, -2));
|
|
1259
|
+
return Number.isFinite(v2) ? Math.max(0, v2 * refFontPx) : 0;
|
|
1260
|
+
}
|
|
1261
|
+
if (s.endsWith("%")) return 0;
|
|
1262
|
+
const v = parseFloat(s);
|
|
1263
|
+
return Number.isFinite(v) ? Math.max(0, v) : 0;
|
|
1264
|
+
}
|
|
1265
|
+
function rawStringProp(m, ...keys) {
|
|
1266
|
+
for (const k of keys) {
|
|
1267
|
+
const v = m[k];
|
|
1268
|
+
if (typeof v === "string" && v.trim()) return v.trim();
|
|
1269
|
+
if (typeof v === "number") return String(v);
|
|
1270
|
+
}
|
|
1271
|
+
return "";
|
|
1272
|
+
}
|
|
1273
|
+
function cssColorFromRaw(v) {
|
|
1274
|
+
if (typeof v === "string" && v.trim()) return v.trim().replace(/;/g, "");
|
|
1275
|
+
return "";
|
|
1276
|
+
}
|
|
1277
|
+
function backgroundColorFromStyle(style) {
|
|
1278
|
+
const m = normalizeStyle(style);
|
|
1279
|
+
if (!m) return "";
|
|
1280
|
+
for (const key of ["backGround", "background", "backgroundColor"]) {
|
|
1281
|
+
const c = cssColorFromRaw(m[key]);
|
|
1282
|
+
if (c) return c;
|
|
1283
|
+
}
|
|
1284
|
+
return "";
|
|
1285
|
+
}
|
|
1286
|
+
function parsePaddingCSS(css, refFontPx) {
|
|
1287
|
+
css = css.trim();
|
|
1288
|
+
if (!css) return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
1289
|
+
const parts = css.split(/\s+/);
|
|
1290
|
+
const vals = parts.map((p) => cssLengthToPx(p, refFontPx));
|
|
1291
|
+
switch (vals.length) {
|
|
1292
|
+
case 1:
|
|
1293
|
+
return { top: vals[0], right: vals[0], bottom: vals[0], left: vals[0] };
|
|
1294
|
+
case 2:
|
|
1295
|
+
return { top: vals[0], right: vals[1], bottom: vals[0], left: vals[1] };
|
|
1296
|
+
case 3:
|
|
1297
|
+
return { top: vals[0], right: vals[1], bottom: vals[2], left: vals[1] };
|
|
1298
|
+
default:
|
|
1299
|
+
return { top: vals[0], right: vals[1], bottom: vals[2], left: vals[3] };
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
function styleResolvedForCanvas(style, canvasW, canvasH, fontPx) {
|
|
1303
|
+
const m = normalizeStyle(style);
|
|
1304
|
+
if (!m) return {};
|
|
1305
|
+
return applyPercentHandlers(m, { w: canvasW, h: canvasH, fontSizePx: fontPx, zoom: 1 });
|
|
1306
|
+
}
|
|
1307
|
+
function textBoxInsetsForCanvas(style, refFontPx, canvasW, canvasH) {
|
|
1308
|
+
const m = styleResolvedForCanvas(style, canvasW, canvasH, refFontPx);
|
|
1309
|
+
const padding = parsePaddingCSS(rawStringProp(m, "padding"), refFontPx);
|
|
1310
|
+
let borderW = rawStringProp(m, "borderWidth");
|
|
1311
|
+
if (!borderW && typeof m.border === "string") {
|
|
1312
|
+
borderW = m.border.split(/\s+/)[0] ?? "";
|
|
1313
|
+
}
|
|
1314
|
+
const px = cssLengthToPx(borderW, refFontPx);
|
|
1315
|
+
const border = px > 0 ? { top: px, right: px, bottom: px, left: px } : { top: 0, right: 0, bottom: 0, left: 0 };
|
|
1316
|
+
return {
|
|
1317
|
+
top: padding.top + border.top,
|
|
1318
|
+
right: padding.right + border.right,
|
|
1319
|
+
bottom: padding.bottom + border.bottom,
|
|
1320
|
+
left: padding.left + border.left
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
function explicitHeightPx(style, canvasW, canvasH, fontPx) {
|
|
1324
|
+
if (canvasH < 1) return { px: 0, ok: false };
|
|
1325
|
+
const m = styleResolvedForCanvas(style, canvasW, canvasH, fontPx);
|
|
1326
|
+
const val = rawStringProp(m, "height");
|
|
1327
|
+
if (!val) return { px: 0, ok: false };
|
|
1328
|
+
if (val.endsWith("%")) {
|
|
1329
|
+
const pct = parseFloat(val.slice(0, -1));
|
|
1330
|
+
if (!Number.isFinite(pct) || pct <= 0) return { px: 0, ok: false };
|
|
1331
|
+
return { px: Math.round(canvasH * pct / 100), ok: true };
|
|
1332
|
+
}
|
|
1333
|
+
if (val.endsWith("px")) {
|
|
1334
|
+
const px = parseFloat(val.slice(0, -2));
|
|
1335
|
+
if (!Number.isFinite(px) || px < 1) return { px: 0, ok: false };
|
|
1336
|
+
return { px: Math.round(px), ok: true };
|
|
1337
|
+
}
|
|
1338
|
+
if (val.endsWith("em") || val.endsWith("rem")) {
|
|
1339
|
+
const suffix = val.endsWith("rem") ? "rem" : "em";
|
|
1340
|
+
const n2 = parseFloat(val.slice(0, -suffix.length));
|
|
1341
|
+
if (!Number.isFinite(n2) || n2 <= 0 || fontPx <= 0) return { px: 0, ok: false };
|
|
1342
|
+
return { px: Math.round(n2 * fontPx), ok: true };
|
|
1343
|
+
}
|
|
1344
|
+
const n = parseFloat(val);
|
|
1345
|
+
if (Number.isFinite(n) && n > 0) return { px: Math.round(n), ok: true };
|
|
1346
|
+
return { px: 0, ok: false };
|
|
1347
|
+
}
|
|
1348
|
+
function textLayerNeedsComputedHeight(style, canvasW, canvasH, textSize) {
|
|
1349
|
+
const fontPx = textFontSizePx(textSize, canvasW, canvasH);
|
|
1350
|
+
if (explicitHeightPx(style, canvasW, canvasH, fontPx).ok) return true;
|
|
1351
|
+
return paddingVertical(textBoxInsetsForCanvas(style, fontPx, canvasW, canvasH)) > 0;
|
|
1352
|
+
}
|
|
1353
|
+
function fontWeightIsBold(style) {
|
|
1354
|
+
const m = normalizeStyle(style);
|
|
1355
|
+
if (!m) return false;
|
|
1356
|
+
const w = rawStringProp(m, "fontWeight", "font-weight").toLowerCase();
|
|
1357
|
+
if (["bold", "bolder", "600", "700", "800", "900"].includes(w)) return true;
|
|
1358
|
+
const n = parseInt(w, 10);
|
|
1359
|
+
return Number.isFinite(n) && n >= 600;
|
|
1360
|
+
}
|
|
1361
|
+
function lineHeightMultiplier(style, fontSizePx) {
|
|
1362
|
+
const def = 1.2;
|
|
1363
|
+
const m = normalizeStyle(style);
|
|
1364
|
+
if (!m) return def;
|
|
1365
|
+
const raw = m.lineHeight;
|
|
1366
|
+
if (typeof raw === "number" && raw > 0) return raw;
|
|
1367
|
+
const val = typeof raw === "string" ? raw.trim() : "";
|
|
1368
|
+
if (!val) return def;
|
|
1369
|
+
if (val.endsWith("%")) {
|
|
1370
|
+
const pct = parseFloat(val.slice(0, -1));
|
|
1371
|
+
if (Number.isFinite(pct) && pct > 0) return pct / 100;
|
|
1372
|
+
return def;
|
|
1373
|
+
}
|
|
1374
|
+
if (val.endsWith("px")) {
|
|
1375
|
+
const px = parseFloat(val.slice(0, -2));
|
|
1376
|
+
if (Number.isFinite(px) && px > 0 && fontSizePx > 0) return px / fontSizePx;
|
|
1377
|
+
return def;
|
|
1378
|
+
}
|
|
1379
|
+
const v = parseFloat(val);
|
|
1380
|
+
return Number.isFinite(v) && v > 0 ? v : def;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// src/html/geometry/textBlockGeometry.ts
|
|
1384
|
+
function textBlockWidthPx(widthPct, canvasW) {
|
|
1385
|
+
const w = Math.round(canvasW * widthPct / 100);
|
|
1386
|
+
return w < 1 ? 1 : w;
|
|
1387
|
+
}
|
|
1388
|
+
function charsPerLineForWidth(widthPx, fontSizePx, bold) {
|
|
1389
|
+
const em = bold ? 0.58 : 0.48;
|
|
1390
|
+
const cpl = Math.floor(widthPx / (fontSizePx * em));
|
|
1391
|
+
return cpl < 1 ? 1 : cpl;
|
|
1392
|
+
}
|
|
1393
|
+
function estimateTextLines(content, widthPx, fontSizePx, bold) {
|
|
1394
|
+
if (widthPx < 1) widthPx = 1;
|
|
1395
|
+
const charsPerLine = charsPerLineForWidth(widthPx, fontSizePx, bold);
|
|
1396
|
+
const parts = content.split("\n");
|
|
1397
|
+
let total = 0;
|
|
1398
|
+
for (const part of parts) {
|
|
1399
|
+
const n = [...part.trim()].length;
|
|
1400
|
+
if (n === 0) continue;
|
|
1401
|
+
total += Math.ceil(n / charsPerLine);
|
|
1402
|
+
}
|
|
1403
|
+
return total < 1 ? 1 : total;
|
|
1404
|
+
}
|
|
1405
|
+
function textBlockGeometry(t, content, canvasW, canvasH) {
|
|
1406
|
+
const x = Math.round(canvasW * t.x / 100);
|
|
1407
|
+
const y = Math.round(canvasH * t.y / 100);
|
|
1408
|
+
const outerW = textBlockWidthPx(t.width, canvasW);
|
|
1409
|
+
const fontPx = textFontSizePx(t.textSize, canvasW, canvasH);
|
|
1410
|
+
const insets = textBoxInsetsForCanvas(t.style, fontPx, canvasW, canvasH);
|
|
1411
|
+
const padW = Math.round(paddingHorizontal(insets));
|
|
1412
|
+
const padH = Math.round(paddingVertical(insets));
|
|
1413
|
+
let contentW = outerW - padW;
|
|
1414
|
+
if (contentW < 1) contentW = 1;
|
|
1415
|
+
const plain = plainTextForLayout(content);
|
|
1416
|
+
const lines = estimateTextLines(plain, contentW, fontPx, fontWeightIsBold(t.style));
|
|
1417
|
+
const lh = lineHeightMultiplier(t.style, fontPx);
|
|
1418
|
+
const linePx = fontPx * lh;
|
|
1419
|
+
let contentH = Math.round(linePx * lines);
|
|
1420
|
+
if (contentH < Math.round(linePx)) contentH = Math.round(linePx);
|
|
1421
|
+
let width = outerW;
|
|
1422
|
+
let height = contentH + padH;
|
|
1423
|
+
const explicit = explicitHeightPx(t.style, canvasW, canvasH, fontPx);
|
|
1424
|
+
if (explicit.ok) {
|
|
1425
|
+
if (content.trim() === "") height = explicit.px;
|
|
1426
|
+
else if (explicit.px > height) height = explicit.px;
|
|
1427
|
+
}
|
|
1428
|
+
if (width < 1) width = 1;
|
|
1429
|
+
if (height < 1) height = 1;
|
|
1430
|
+
return { x, y, width, height };
|
|
1431
|
+
}
|
|
1432
|
+
function appendTextLayerGeometryCSS(boxCSS, t, content, canvasW, canvasH) {
|
|
1433
|
+
if (canvasH < 1 || boxCSS.includes("height:")) return boxCSS;
|
|
1434
|
+
if (!textLayerNeedsComputedHeight(t.style, canvasW, canvasH, t.textSize)) return boxCSS;
|
|
1435
|
+
const { height: geomH } = textBlockGeometry(t, content, canvasW, canvasH);
|
|
1436
|
+
if (geomH < 1) return boxCSS;
|
|
1437
|
+
const heightPct = geomH / canvasH * 100;
|
|
1438
|
+
return `${boxCSS}height:${heightPct}%;`;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
// src/html/style/fragment.ts
|
|
1442
|
+
var TypeKey = "__type__";
|
|
1443
|
+
var TypeMotionDiv = "div";
|
|
1444
|
+
var TypeSpan = "span";
|
|
1445
|
+
var TypeFilter = "filter";
|
|
1446
|
+
var TypeMask = "mask";
|
|
1447
|
+
function newFragment(type) {
|
|
1448
|
+
return { [TypeKey]: type };
|
|
1449
|
+
}
|
|
1450
|
+
function mergeFragments(fragments) {
|
|
1451
|
+
const byType = /* @__PURE__ */ new Map();
|
|
1452
|
+
const order = [];
|
|
1453
|
+
for (const fragment of fragments) {
|
|
1454
|
+
if (!fragment) continue;
|
|
1455
|
+
const type = getFragmentString(fragment, TypeKey);
|
|
1456
|
+
if (!type) continue;
|
|
1457
|
+
const existing = byType.get(type);
|
|
1458
|
+
if (existing) {
|
|
1459
|
+
for (const [key, value] of Object.entries(fragment)) {
|
|
1460
|
+
if (key !== TypeKey) {
|
|
1461
|
+
existing[key] = value;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
const copy = { [TypeKey]: type };
|
|
1467
|
+
for (const [key, value] of Object.entries(fragment)) {
|
|
1468
|
+
copy[key] = value;
|
|
1469
|
+
}
|
|
1470
|
+
byType.set(type, copy);
|
|
1471
|
+
order.push(type);
|
|
1472
|
+
}
|
|
1473
|
+
return order.map((type) => byType.get(type)).filter((v) => Boolean(v));
|
|
1474
|
+
}
|
|
1475
|
+
function setFragmentValue(fragment, prop, value) {
|
|
1476
|
+
if (!fragment) return;
|
|
1477
|
+
fragment[prop] = value;
|
|
1478
|
+
}
|
|
1479
|
+
function getFragmentString(fragment, prop) {
|
|
1480
|
+
if (!fragment) return "";
|
|
1481
|
+
const value = fragment[prop];
|
|
1482
|
+
return typeof value === "string" ? value : "";
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
// src/html/style/effects.ts
|
|
1486
|
+
function expandEffects(style, dims, targetSVG, filterID) {
|
|
1487
|
+
if (Object.keys(style).length === 0) return { style, fragments: [] };
|
|
1488
|
+
const out = { ...style };
|
|
1489
|
+
const extra = [];
|
|
1490
|
+
if (Object.prototype.hasOwnProperty.call(out, KeyGlow)) {
|
|
1491
|
+
const raw = out[KeyGlow];
|
|
1492
|
+
const value = stringifyCSSValue(raw);
|
|
1493
|
+
delete out[KeyGlow];
|
|
1494
|
+
if (hasStyleValue(KeyGlow, raw)) {
|
|
1495
|
+
if (targetSVG) {
|
|
1496
|
+
extra.push(glowFilterFragment(filterID, value, dims));
|
|
1497
|
+
} else {
|
|
1498
|
+
mergeShadowKey(out, KeyTextShadow, value);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
if (Object.prototype.hasOwnProperty.call(out, KeyBevel)) {
|
|
1503
|
+
const raw = out[KeyBevel];
|
|
1504
|
+
const value = stringifyCSSValue(raw);
|
|
1505
|
+
delete out[KeyBevel];
|
|
1506
|
+
if (hasStyleValue(KeyBevel, raw)) {
|
|
1507
|
+
if (targetSVG) {
|
|
1508
|
+
extra.push(bevelFilterFragment(`${filterID}-bevel`, value));
|
|
1509
|
+
} else {
|
|
1510
|
+
mergeBoxShadowKey(out, bevelBoxShadows(value));
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
return { style: out, fragments: extra };
|
|
1515
|
+
}
|
|
1516
|
+
function mergeShadowKey(style, key, add) {
|
|
1517
|
+
const existing = stringifyCSSValue(style[key]);
|
|
1518
|
+
if (!existing) {
|
|
1519
|
+
style[key] = add;
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
style[key] = `${existing}, ${add}`;
|
|
1523
|
+
}
|
|
1524
|
+
function mergeBoxShadowKey(style, add) {
|
|
1525
|
+
const existing = stringifyCSSValue(style[KeyBoxShadow]);
|
|
1526
|
+
if (!existing) {
|
|
1527
|
+
style[KeyBoxShadow] = add;
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
style[KeyBoxShadow] = `${existing}, ${add}`;
|
|
1531
|
+
}
|
|
1532
|
+
function bevelBoxShadows(value) {
|
|
1533
|
+
const light = "inset 1px 1px 0 rgba(255,255,255,0.35)";
|
|
1534
|
+
let dark = "inset -1px -1px 0 rgba(0,0,0,0.35)";
|
|
1535
|
+
if (value.trim()) {
|
|
1536
|
+
dark = `inset -1px -1px 2px ${value.trim()}`;
|
|
1537
|
+
}
|
|
1538
|
+
return `${light}, ${dark}`;
|
|
1539
|
+
}
|
|
1540
|
+
function glowFilterFragment(id, value, dims) {
|
|
1541
|
+
const parsed = parseSimpleShadow(value, dims);
|
|
1542
|
+
const fragment = newFragment(TypeFilter);
|
|
1543
|
+
setFragmentValue(fragment, "id", id);
|
|
1544
|
+
setFragmentValue(fragment, "feDropShadowDx", parsed.dx.toFixed(3));
|
|
1545
|
+
setFragmentValue(fragment, "feDropShadowDy", parsed.dy.toFixed(3));
|
|
1546
|
+
setFragmentValue(fragment, "feGaussianBlurStd", (parsed.blur / 2).toFixed(3));
|
|
1547
|
+
setFragmentValue(fragment, "floodColor", parsed.color);
|
|
1548
|
+
return fragment;
|
|
1549
|
+
}
|
|
1550
|
+
function bevelFilterFragment(id, value) {
|
|
1551
|
+
const fragment = newFragment(TypeFilter);
|
|
1552
|
+
setFragmentValue(fragment, "id", id);
|
|
1553
|
+
setFragmentValue(fragment, "feDropShadowDx", "-1");
|
|
1554
|
+
setFragmentValue(fragment, "feDropShadowDy", "-1");
|
|
1555
|
+
setFragmentValue(fragment, "feGaussianBlurStd", "0.5");
|
|
1556
|
+
const color = value.trim() || "rgba(0,0,0,0.4)";
|
|
1557
|
+
setFragmentValue(fragment, "floodColor", color);
|
|
1558
|
+
setFragmentValue(fragment, "feDropShadowDx2", "1");
|
|
1559
|
+
setFragmentValue(fragment, "floodColor2", "rgba(255,255,255,0.35)");
|
|
1560
|
+
return fragment;
|
|
1561
|
+
}
|
|
1562
|
+
function parseSimpleShadow(value, dims) {
|
|
1563
|
+
const trimmed = value.trim();
|
|
1564
|
+
if (!trimmed) {
|
|
1565
|
+
return { dx: 0, dy: 0, blur: 4, color: "rgba(0,0,0,0.5)" };
|
|
1566
|
+
}
|
|
1567
|
+
const resolved = applyPercentHandlers({ textShadow: trimmed }, dims);
|
|
1568
|
+
const parts = stringifyCSSValue(resolved.textShadow).split(/\s+/).filter(Boolean);
|
|
1569
|
+
let dx = 0;
|
|
1570
|
+
let dy = 0;
|
|
1571
|
+
let blur = 0;
|
|
1572
|
+
let color = "";
|
|
1573
|
+
if (parts.length >= 3) {
|
|
1574
|
+
dx = parsePxNum(parts[0] ?? "");
|
|
1575
|
+
dy = parsePxNum(parts[1] ?? "");
|
|
1576
|
+
blur = parsePxNum(parts[2] ?? "");
|
|
1577
|
+
}
|
|
1578
|
+
if (parts.length >= 4) {
|
|
1579
|
+
color = parts[3] ?? "";
|
|
1580
|
+
}
|
|
1581
|
+
if (!color) color = "rgba(0,0,0,0.5)";
|
|
1582
|
+
return { dx, dy, blur, color };
|
|
1583
|
+
}
|
|
1584
|
+
function parsePxNum(value) {
|
|
1585
|
+
const parsed = Number.parseFloat(value.trim().replace(/px$/, ""));
|
|
1586
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
// src/html/style/blur.ts
|
|
1590
|
+
var blurFilterKind = "blur";
|
|
1591
|
+
var blurSideKeys = [
|
|
1592
|
+
{ key: KeyBlurLeft, side: "left" },
|
|
1593
|
+
{ key: KeyBlurRight, side: "right" },
|
|
1594
|
+
{ key: KeyBlurTop, side: "top" },
|
|
1595
|
+
{ key: KeyBlurBottom, side: "bottom" }
|
|
1596
|
+
];
|
|
1597
|
+
function expandBlur(style, dims, html, filterID) {
|
|
1598
|
+
if (Object.keys(style).length === 0) {
|
|
1599
|
+
return { style, meta: { filterID: "", maskID: "" }, fragments: [] };
|
|
1600
|
+
}
|
|
1601
|
+
const parsed = parseBlurFromStyle(style, dims);
|
|
1602
|
+
if (!parsed.ok) {
|
|
1603
|
+
return { style, meta: { filterID: "", maskID: "" }, fragments: [] };
|
|
1604
|
+
}
|
|
1605
|
+
const spec = parsed.spec;
|
|
1606
|
+
const out = { ...style };
|
|
1607
|
+
for (const sideKey of blurSideKeys) {
|
|
1608
|
+
delete out[sideKey.key];
|
|
1609
|
+
}
|
|
1610
|
+
delete out[KeyBlur];
|
|
1611
|
+
const meta = { filterID: `${filterID}-blur`, maskID: "" };
|
|
1612
|
+
if (html) {
|
|
1613
|
+
const patch = newFragment(TypeMotionDiv);
|
|
1614
|
+
applyBlurHTML(patch, spec);
|
|
1615
|
+
return { style: out, meta, fragments: [patch] };
|
|
1616
|
+
}
|
|
1617
|
+
const fragments = [blurFilterFragment(meta.filterID, spec)];
|
|
1618
|
+
if (spec.side) {
|
|
1619
|
+
meta.maskID = `${meta.filterID}-mask`;
|
|
1620
|
+
fragments.push(blurMaskFragment(meta.maskID, spec.side));
|
|
1621
|
+
}
|
|
1622
|
+
return { style: out, meta, fragments };
|
|
1623
|
+
}
|
|
1624
|
+
function parseBlurFromStyle(style, dims) {
|
|
1625
|
+
for (const side of blurSideKeys) {
|
|
1626
|
+
const raw2 = style[side.key];
|
|
1627
|
+
if (!hasStyleValue(side.key, raw2)) continue;
|
|
1628
|
+
const amount = parseBlurAmount(stringifyCSSValue(raw2), dims);
|
|
1629
|
+
if (!amount.ok || amount.amount <= 0) return { spec: { amountPx: 0, side: "" }, ok: false };
|
|
1630
|
+
return { spec: { amountPx: amount.amount, side: side.side }, ok: true };
|
|
1631
|
+
}
|
|
1632
|
+
const raw = style[KeyBlur];
|
|
1633
|
+
if (!hasStyleValue(KeyBlur, raw)) return { spec: { amountPx: 0, side: "" }, ok: false };
|
|
1634
|
+
return parseBlurCSSValue(stringifyCSSValue(raw), dims);
|
|
1635
|
+
}
|
|
1636
|
+
function parseBlurCSSValue(value, dims) {
|
|
1637
|
+
const trimmed = value.trim();
|
|
1638
|
+
if (!trimmed) return { spec: { amountPx: 0, side: "" }, ok: false };
|
|
1639
|
+
const parts = trimmed.split(/\s+/).filter(Boolean);
|
|
1640
|
+
if (parts.length === 0) return { spec: { amountPx: 0, side: "" }, ok: false };
|
|
1641
|
+
let side = "";
|
|
1642
|
+
const amountParts = [];
|
|
1643
|
+
for (const part of parts) {
|
|
1644
|
+
const lower = part.toLowerCase();
|
|
1645
|
+
if ((lower === "left" || lower === "right" || lower === "top" || lower === "bottom") && !side) {
|
|
1646
|
+
side = lower;
|
|
1647
|
+
continue;
|
|
1648
|
+
}
|
|
1649
|
+
amountParts.push(part);
|
|
1650
|
+
}
|
|
1651
|
+
let amountString = amountParts.join(" ");
|
|
1652
|
+
if (!amountString && side && parts.length >= 2) {
|
|
1653
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
1654
|
+
const current = parts[i]?.toLowerCase();
|
|
1655
|
+
if (current !== side) continue;
|
|
1656
|
+
amountString = parts.slice(i + 1).join(" ");
|
|
1657
|
+
break;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
if (!amountString) {
|
|
1661
|
+
amountString = trimmed;
|
|
1662
|
+
side = "";
|
|
1663
|
+
}
|
|
1664
|
+
const amount = parseBlurAmount(amountString, dims);
|
|
1665
|
+
if (!amount.ok || amount.amount <= 0) return { spec: { amountPx: 0, side: "" }, ok: false };
|
|
1666
|
+
return { spec: { amountPx: amount.amount, side }, ok: true };
|
|
1667
|
+
}
|
|
1668
|
+
function parseBlurAmount(value, dims) {
|
|
1669
|
+
let trimmed = value.trim();
|
|
1670
|
+
if (!trimmed) return { amount: 0, ok: false };
|
|
1671
|
+
if (trimmed.includes("%")) {
|
|
1672
|
+
const resolved = applyPercentHandlers({ [KeyBlur]: trimmed }, dims);
|
|
1673
|
+
trimmed = stringifyCSSValue(resolved[KeyBlur]);
|
|
1674
|
+
}
|
|
1675
|
+
const px = parsePxNum2(trimmed);
|
|
1676
|
+
return { amount: px, ok: px > 0 };
|
|
1677
|
+
}
|
|
1678
|
+
function applyBlurHTML(box, spec) {
|
|
1679
|
+
if (spec.amountPx <= 0) return;
|
|
1680
|
+
const px = `${spec.amountPx.toFixed(3)}px`;
|
|
1681
|
+
const blurValue = `blur(${px})`;
|
|
1682
|
+
setFragmentValue(box, "backdropFilter", blurValue);
|
|
1683
|
+
setFragmentValue(box, "WebkitBackdropFilter", blurValue);
|
|
1684
|
+
if (!spec.side) return;
|
|
1685
|
+
const mask = blurMaskCSS(spec.side);
|
|
1686
|
+
setFragmentValue(box, "maskImage", mask);
|
|
1687
|
+
setFragmentValue(box, "WebkitMaskImage", mask);
|
|
1688
|
+
setFragmentValue(box, "maskSize", "100% 100%");
|
|
1689
|
+
setFragmentValue(box, "WebkitMaskSize", "100% 100%");
|
|
1690
|
+
}
|
|
1691
|
+
function blurMaskCSS(side) {
|
|
1692
|
+
switch (side) {
|
|
1693
|
+
case "left":
|
|
1694
|
+
return "linear-gradient(to right, rgba(0,0,0,1) 0%, rgba(0,0,0,0) 100%)";
|
|
1695
|
+
case "right":
|
|
1696
|
+
return "linear-gradient(to left, rgba(0,0,0,1) 0%, rgba(0,0,0,0) 100%)";
|
|
1697
|
+
case "top":
|
|
1698
|
+
return "linear-gradient(to bottom, rgba(0,0,0,1) 0%, rgba(0,0,0,0) 100%)";
|
|
1699
|
+
case "bottom":
|
|
1700
|
+
return "linear-gradient(to top, rgba(0,0,0,1) 0%, rgba(0,0,0,0) 100%)";
|
|
1701
|
+
default:
|
|
1702
|
+
return "";
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
function blurFilterFragment(id, spec) {
|
|
1706
|
+
const fragment = newFragment(TypeFilter);
|
|
1707
|
+
setFragmentValue(fragment, "id", id);
|
|
1708
|
+
setFragmentValue(fragment, "filterKind", blurFilterKind);
|
|
1709
|
+
let std = spec.amountPx / 2;
|
|
1710
|
+
if (std < 0.5) std = 0.5;
|
|
1711
|
+
setFragmentValue(fragment, "feGaussianBlurStd", std.toFixed(3));
|
|
1712
|
+
setFragmentValue(fragment, "feGaussianBlurIn", "SourceGraphic");
|
|
1713
|
+
return fragment;
|
|
1714
|
+
}
|
|
1715
|
+
function blurMaskFragment(id, side) {
|
|
1716
|
+
const fragment = newFragment(TypeMask);
|
|
1717
|
+
setFragmentValue(fragment, "id", id);
|
|
1718
|
+
setFragmentValue(fragment, "maskSide", side);
|
|
1719
|
+
return fragment;
|
|
1720
|
+
}
|
|
1721
|
+
function parsePxNum2(value) {
|
|
1722
|
+
const trimmed = value.trim().replace(/px$/, "");
|
|
1723
|
+
const parsed = Number.parseFloat(trimmed);
|
|
1724
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
// src/html/style/stroke.ts
|
|
1728
|
+
function applyStrokeHTML(span, style) {
|
|
1729
|
+
if (!span) return;
|
|
1730
|
+
const stroke = stringifyCSSValue(style[KeyStroke]);
|
|
1731
|
+
if (stroke) {
|
|
1732
|
+
setFragmentValue(span, "WebkitTextStroke", stroke);
|
|
1733
|
+
}
|
|
1734
|
+
const strokeWidth = stringifyCSSValue(style[KeyStrokeWidth]);
|
|
1735
|
+
if (strokeWidth) {
|
|
1736
|
+
setFragmentValue(span, "WebkitTextStrokeWidth", strokeWidth);
|
|
1737
|
+
}
|
|
1738
|
+
const strokeColor = stringifyCSSValue(style[KeyStrokeColor]);
|
|
1739
|
+
if (strokeColor) {
|
|
1740
|
+
setFragmentValue(span, "WebkitTextStrokeColor", strokeColor);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
// src/html/style/context.ts
|
|
1745
|
+
function contextImageDims(ctx) {
|
|
1746
|
+
const zoom = ctx.zoom && ctx.zoom > 0 ? ctx.zoom : 1;
|
|
1747
|
+
return {
|
|
1748
|
+
w: ctx.canvasW,
|
|
1749
|
+
h: ctx.canvasH,
|
|
1750
|
+
fontSizePx: ctx.fontSizePx ?? 0,
|
|
1751
|
+
zoom
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
function fontSizePxOrCompute(ctx) {
|
|
1755
|
+
const fontPx = ctx.fontSizePx ?? 0;
|
|
1756
|
+
if (fontPx > 0) return fontPx;
|
|
1757
|
+
return textFontSizePx2(ctx.text.textSize, ctx.canvasW, ctx.canvasH);
|
|
1758
|
+
}
|
|
1759
|
+
function textSizeBasisPx2(canvasW, canvasH) {
|
|
1760
|
+
if (canvasW <= 0 && canvasH <= 0) return 1;
|
|
1761
|
+
if (canvasW <= 0) return canvasH;
|
|
1762
|
+
if (canvasH <= 0) return canvasW;
|
|
1763
|
+
return canvasW < canvasH ? canvasW : canvasH;
|
|
1764
|
+
}
|
|
1765
|
+
function textFontSizePx2(textSizePct, canvasW, canvasH) {
|
|
1766
|
+
const px = textSizePct / 100 * textSizeBasisPx2(canvasW, canvasH);
|
|
1767
|
+
return px < 1 ? 1 : px;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
// src/html/style/adapter.ts
|
|
1771
|
+
function adaptHTML(ctx) {
|
|
1772
|
+
return adapt(ctx, true);
|
|
1773
|
+
}
|
|
1774
|
+
function adapt(ctx, html) {
|
|
1775
|
+
let style = normalizeStyle(ctx.text.style);
|
|
1776
|
+
if (Object.keys(style).length === 0) {
|
|
1777
|
+
style = {};
|
|
1778
|
+
}
|
|
1779
|
+
const dims = contextImageDims(ctx);
|
|
1780
|
+
dims.fontSizePx = fontSizePxOrCompute(ctx);
|
|
1781
|
+
if (html && ctx.htmlCompile) {
|
|
1782
|
+
dims.preservePercent = true;
|
|
1783
|
+
}
|
|
1784
|
+
style = applyPercentHandlers(style, dims);
|
|
1785
|
+
style = filterStyleMap(style);
|
|
1786
|
+
const filterID = `psrt-filter-${ctx.pageSlug ?? ""}-${ctx.textIndex ?? 0}`;
|
|
1787
|
+
const effects = expandEffects(style, dims, !html, filterID);
|
|
1788
|
+
const blur = expandBlur(effects.style, dims, html, filterID);
|
|
1789
|
+
const fragments = html ? buildHTMLFragments(ctx, blur.style) : [];
|
|
1790
|
+
return mergeFragments([...fragments, ...effects.fragments, ...blur.fragments]);
|
|
1791
|
+
}
|
|
1792
|
+
function buildHTMLFragments(ctx, style) {
|
|
1793
|
+
const box = newFragment(TypeMotionDiv);
|
|
1794
|
+
const text = newFragment(TypeSpan);
|
|
1795
|
+
applyLayout(box, ctx);
|
|
1796
|
+
applyTransform(box, style);
|
|
1797
|
+
for (const [key, raw] of Object.entries(style)) {
|
|
1798
|
+
if (!hasStyleValue(key, raw)) continue;
|
|
1799
|
+
const value = sanitizeCSSValue(stringifyCSSValue(raw));
|
|
1800
|
+
if (isBoxKey(key)) {
|
|
1801
|
+
applyBoxCSS(box, key, value);
|
|
1802
|
+
continue;
|
|
1803
|
+
}
|
|
1804
|
+
if (isTextKey(key) && key !== KeyStroke && key !== KeyStrokeWidth && key !== KeyStrokeColor) {
|
|
1805
|
+
applyTextCSS(text, key, value);
|
|
1806
|
+
continue;
|
|
1807
|
+
}
|
|
1808
|
+
if (isTransformKey(key)) {
|
|
1809
|
+
continue;
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
applyStrokeHTML(text, style);
|
|
1813
|
+
applyFontSize(box, ctx, Boolean(ctx.htmlCompile));
|
|
1814
|
+
applyTextAlignHTML(box, text, style);
|
|
1815
|
+
if (!getFragmentString(text, KeyColor)) {
|
|
1816
|
+
const color = textColor(style);
|
|
1817
|
+
if (color) {
|
|
1818
|
+
setFragmentValue(text, KeyColor, color);
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
return [box, text];
|
|
1822
|
+
}
|
|
1823
|
+
function applyLayout(box, ctx) {
|
|
1824
|
+
setFragmentValue(box, KeyPosition, "absolute");
|
|
1825
|
+
setFragmentValue(box, KeyBoxSizing, "border-box");
|
|
1826
|
+
setFragmentValue(box, KeyLeft, pctString(ctx.text.x));
|
|
1827
|
+
setFragmentValue(box, KeyTop, pctString(ctx.text.y));
|
|
1828
|
+
setFragmentValue(box, KeyWidth, pctString(ctx.text.width));
|
|
1829
|
+
}
|
|
1830
|
+
function applyTransform(target, style) {
|
|
1831
|
+
const parts = [];
|
|
1832
|
+
const transform2 = stringifyCSSValue(style[KeyTransform]);
|
|
1833
|
+
if (transform2) parts.push(transform2);
|
|
1834
|
+
for (const key of [KeyTranslate, KeyRotate, KeyScale, KeySkew, KeyMatrix]) {
|
|
1835
|
+
const value = stringifyCSSValue(style[key]);
|
|
1836
|
+
if (!value) continue;
|
|
1837
|
+
parts.push(`${key}(${value})`);
|
|
1838
|
+
}
|
|
1839
|
+
if (parts.length === 0) return;
|
|
1840
|
+
setFragmentValue(target, "transform", parts.join(" "));
|
|
1841
|
+
const origin = stringifyCSSValue(style[KeyTransformOrigin]);
|
|
1842
|
+
if (origin) {
|
|
1843
|
+
setFragmentValue(target, KeyTransformOrigin, origin);
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
function applyBoxCSS(fragment, key, value) {
|
|
1847
|
+
switch (key) {
|
|
1848
|
+
case KeyBackground:
|
|
1849
|
+
setFragmentValue(fragment, "backgroundColor", value);
|
|
1850
|
+
return;
|
|
1851
|
+
case KeyPadding:
|
|
1852
|
+
case KeyPaddingTop:
|
|
1853
|
+
case KeyPaddingRight:
|
|
1854
|
+
case KeyPaddingBottom:
|
|
1855
|
+
case KeyPaddingLeft:
|
|
1856
|
+
case KeyBorder:
|
|
1857
|
+
case KeyBorderWidth:
|
|
1858
|
+
case KeyBorderStyle:
|
|
1859
|
+
case KeyBorderColor:
|
|
1860
|
+
case KeyBorderTop:
|
|
1861
|
+
case KeyBorderRight:
|
|
1862
|
+
case KeyBorderBottom:
|
|
1863
|
+
case KeyBorderLeft:
|
|
1864
|
+
case KeyBorderRadius:
|
|
1865
|
+
case KeyBorderTopLeftRadius:
|
|
1866
|
+
case KeyBorderTopRightRadius:
|
|
1867
|
+
case KeyBorderBottomRightRadius:
|
|
1868
|
+
case KeyBorderBottomLeftRadius:
|
|
1869
|
+
case KeyBoxShadow:
|
|
1870
|
+
case KeyHeight:
|
|
1871
|
+
setFragmentValue(fragment, key, value);
|
|
1872
|
+
return;
|
|
1873
|
+
default:
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
function applyTextCSS(fragment, key, value) {
|
|
1878
|
+
const cssKey = key === KeyTextAlign ? "textAlign" : key;
|
|
1879
|
+
setFragmentValue(fragment, cssKey, value);
|
|
1880
|
+
}
|
|
1881
|
+
function applyFontSize(fragment, ctx, htmlCompile) {
|
|
1882
|
+
if (htmlCompile) {
|
|
1883
|
+
setFragmentValue(fragment, KeyFontSize, `${ctx.text.textSize}cqmin`);
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1886
|
+
setFragmentValue(fragment, KeyFontSize, pxString(fontSizePxOrCompute(ctx)));
|
|
1887
|
+
}
|
|
1888
|
+
function applyTextAlignHTML(box, text, style) {
|
|
1889
|
+
let ta = getFragmentString(text, KeyTextAlign).toLowerCase().trim();
|
|
1890
|
+
if (!ta) {
|
|
1891
|
+
ta = readTextAlignFromStyle(style);
|
|
1892
|
+
}
|
|
1893
|
+
const va = verticalAlignFromStyle(style);
|
|
1894
|
+
if (!ta && !va) return;
|
|
1895
|
+
if (!ta) ta = "left";
|
|
1896
|
+
setFragmentValue(text, "display", "block");
|
|
1897
|
+
setFragmentValue(text, KeyWidth, "100%");
|
|
1898
|
+
setFragmentValue(text, KeyTextAlign, ta);
|
|
1899
|
+
switch (ta) {
|
|
1900
|
+
case "justify":
|
|
1901
|
+
setFragmentValue(box, "display", "block");
|
|
1902
|
+
return;
|
|
1903
|
+
case "center":
|
|
1904
|
+
case "right":
|
|
1905
|
+
case "left":
|
|
1906
|
+
case "start": {
|
|
1907
|
+
setFragmentValue(box, "display", "flex");
|
|
1908
|
+
setFragmentValue(box, "flexDirection", "column");
|
|
1909
|
+
const justify = va || "center";
|
|
1910
|
+
setFragmentValue(box, "justifyContent", justify);
|
|
1911
|
+
let align = "stretch";
|
|
1912
|
+
if (ta === "center") align = "center";
|
|
1913
|
+
if (ta === "right") align = "flex-end";
|
|
1914
|
+
if (ta === "left" || ta === "start") align = "flex-start";
|
|
1915
|
+
setFragmentValue(box, "alignItems", align);
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
default:
|
|
1919
|
+
setFragmentValue(box, "display", "block");
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
function readTextAlignFromStyle(style) {
|
|
1923
|
+
const direct = style[KeyTextAlign];
|
|
1924
|
+
if (direct !== void 0) {
|
|
1925
|
+
return sanitizeCSSValue(stringifyCSSValue(direct)).toLowerCase();
|
|
1926
|
+
}
|
|
1927
|
+
for (const key of ["text-align", "ta"]) {
|
|
1928
|
+
const value = style[key];
|
|
1929
|
+
if (value === void 0) continue;
|
|
1930
|
+
return sanitizeCSSValue(stringifyCSSValue(value)).toLowerCase();
|
|
1931
|
+
}
|
|
1932
|
+
return "";
|
|
1933
|
+
}
|
|
1934
|
+
function verticalAlignFromStyle(style) {
|
|
1935
|
+
const direct = style[KeyAlignItems];
|
|
1936
|
+
let value = direct !== void 0 ? sanitizeCSSValue(stringifyCSSValue(direct)).toLowerCase() : "";
|
|
1937
|
+
if (!value) {
|
|
1938
|
+
for (const key of ["align-items", "verticalAlign", "vertical-align"]) {
|
|
1939
|
+
const raw = style[key];
|
|
1940
|
+
if (raw === void 0) continue;
|
|
1941
|
+
value = sanitizeCSSValue(stringifyCSSValue(raw)).toLowerCase();
|
|
1942
|
+
break;
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
switch (value) {
|
|
1946
|
+
case "flex-start":
|
|
1947
|
+
case "start":
|
|
1948
|
+
case "top":
|
|
1949
|
+
return "flex-start";
|
|
1950
|
+
case "flex-end":
|
|
1951
|
+
case "end":
|
|
1952
|
+
case "bottom":
|
|
1953
|
+
return "flex-end";
|
|
1954
|
+
case "center":
|
|
1955
|
+
case "middle":
|
|
1956
|
+
return "center";
|
|
1957
|
+
default:
|
|
1958
|
+
return "";
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
function textColor(style) {
|
|
1962
|
+
const color = style[KeyColor];
|
|
1963
|
+
if (color === void 0) return "";
|
|
1964
|
+
return sanitizeCSSValue(stringifyCSSValue(color));
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
// src/html/style/maskAdapter.ts
|
|
1968
|
+
function adaptMaskHTML(ctx) {
|
|
1969
|
+
const mask = ctx.mask;
|
|
1970
|
+
if (!mask) return [];
|
|
1971
|
+
let style = normalizeStyle(mask.style);
|
|
1972
|
+
if (Object.keys(style).length === 0) {
|
|
1973
|
+
style = {};
|
|
1974
|
+
}
|
|
1975
|
+
const dims = contextImageDims(ctx);
|
|
1976
|
+
if (ctx.htmlCompile) {
|
|
1977
|
+
dims.preservePercent = true;
|
|
1978
|
+
}
|
|
1979
|
+
style = applyPercentHandlers(style, dims);
|
|
1980
|
+
style = filterStyleMap(style);
|
|
1981
|
+
const filterID = maskFilterID(ctx);
|
|
1982
|
+
const effects = expandEffects(style, dims, false, filterID);
|
|
1983
|
+
const blur = expandBlur(effects.style, dims, true, filterID);
|
|
1984
|
+
const fragments = buildMaskHTMLFragments(ctx, blur.style);
|
|
1985
|
+
return mergeFragments([...fragments, ...effects.fragments, ...blur.fragments]);
|
|
1986
|
+
}
|
|
1987
|
+
function maskFilterID(ctx) {
|
|
1988
|
+
const index = ctx.mask?.index ?? ctx.textIndex ?? 0;
|
|
1989
|
+
return `psrt-filter-${ctx.pageSlug ?? ""}-${index}`;
|
|
1990
|
+
}
|
|
1991
|
+
function buildMaskHTMLFragments(ctx, style) {
|
|
1992
|
+
const box = newFragment(TypeMotionDiv);
|
|
1993
|
+
applyMaskLayout(box, ctx);
|
|
1994
|
+
applyTransform2(box, style);
|
|
1995
|
+
for (const [key, raw] of Object.entries(style)) {
|
|
1996
|
+
if (!hasStyleValue(key, raw)) continue;
|
|
1997
|
+
const value = sanitizeCSSValue(stringifyCSSValue(raw));
|
|
1998
|
+
if (isBoxKey(key)) {
|
|
1999
|
+
applyBoxCSS(box, key, value);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
if (!box["background-size"]) {
|
|
2003
|
+
setFragmentValue(box, "background-size", "cover");
|
|
2004
|
+
}
|
|
2005
|
+
return [box];
|
|
2006
|
+
}
|
|
2007
|
+
function applyMaskLayout(box, ctx) {
|
|
2008
|
+
const mask = ctx.mask;
|
|
2009
|
+
if (!mask) return;
|
|
2010
|
+
setFragmentValue(box, KeyPosition, "absolute");
|
|
2011
|
+
setFragmentValue(box, KeyBoxSizing, "border-box");
|
|
2012
|
+
setFragmentValue(box, KeyLeft, pctString(mask.x));
|
|
2013
|
+
setFragmentValue(box, KeyTop, pctString(mask.y));
|
|
2014
|
+
setFragmentValue(box, KeyWidth, pctString(mask.width));
|
|
2015
|
+
setFragmentValue(box, KeyHeight, pctString(mask.height));
|
|
2016
|
+
}
|
|
2017
|
+
function applyTransform2(target, style) {
|
|
2018
|
+
const parts = [];
|
|
2019
|
+
const transform2 = stringifyCSSValue(style[KeyTransform]);
|
|
2020
|
+
if (transform2) parts.push(transform2);
|
|
2021
|
+
for (const key of [KeyTranslate, KeyRotate, KeyScale, KeySkew, KeyMatrix]) {
|
|
2022
|
+
const value = stringifyCSSValue(style[key]);
|
|
2023
|
+
if (!value) continue;
|
|
2024
|
+
parts.push(`${key}(${value})`);
|
|
2025
|
+
}
|
|
2026
|
+
if (parts.length > 0) {
|
|
2027
|
+
setFragmentValue(target, KeyTransform, parts.join(" "));
|
|
2028
|
+
}
|
|
2029
|
+
const origin = stringifyCSSValue(style[KeyTransformOrigin]);
|
|
2030
|
+
if (origin) {
|
|
2031
|
+
setFragmentValue(target, KeyTransformOrigin, origin);
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
// src/html/style/render.ts
|
|
2036
|
+
function htmlLayerCSS(fragments) {
|
|
2037
|
+
const merged = mergeFragments(fragments);
|
|
2038
|
+
let box = null;
|
|
2039
|
+
let text = null;
|
|
2040
|
+
for (const fragment of merged) {
|
|
2041
|
+
const type = getFragmentString(fragment, TypeKey);
|
|
2042
|
+
if (type === TypeMotionDiv) box = fragment;
|
|
2043
|
+
if (type === TypeSpan) text = fragment;
|
|
2044
|
+
}
|
|
2045
|
+
return { boxCSS: fragmentCSS(box), textCSS: fragmentCSS(text) };
|
|
2046
|
+
}
|
|
2047
|
+
function fragmentCSS(fragment) {
|
|
2048
|
+
if (!fragment) return "";
|
|
2049
|
+
const keys = Object.keys(fragment).filter((key) => key !== TypeKey).sort();
|
|
2050
|
+
let out = "";
|
|
2051
|
+
for (const key of keys) {
|
|
2052
|
+
const raw = fragment[key];
|
|
2053
|
+
const value = raw === void 0 || raw === null ? "" : String(raw);
|
|
2054
|
+
if (!value) continue;
|
|
2055
|
+
out += `${camelToKebab(key)}:${value};`;
|
|
2056
|
+
}
|
|
2057
|
+
return out;
|
|
2058
|
+
}
|
|
2059
|
+
function camelToKebab(value) {
|
|
2060
|
+
if (value.startsWith("-")) return value;
|
|
2061
|
+
let out = "";
|
|
2062
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
2063
|
+
const char = value[i] ?? "";
|
|
2064
|
+
const upper = char >= "A" && char <= "Z";
|
|
2065
|
+
if (upper) {
|
|
2066
|
+
if (i > 0) out += "-";
|
|
2067
|
+
out += char.toLowerCase();
|
|
2068
|
+
continue;
|
|
2069
|
+
}
|
|
2070
|
+
out += char;
|
|
2071
|
+
}
|
|
2072
|
+
return out;
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
// src/html/steps.ts
|
|
2076
|
+
var CompileStep = {
|
|
2077
|
+
RESOLVE: "resolve",
|
|
2078
|
+
BUILD_ASSETS: "buildAssets",
|
|
2079
|
+
ADAPT_STYLE: "adaptStyle",
|
|
2080
|
+
RENDER_FONTS: "renderFonts",
|
|
2081
|
+
RENDER_HEAD: "renderHead",
|
|
2082
|
+
RENDER_PAGE: "renderPage",
|
|
2083
|
+
RENDER_TEXT: "renderText",
|
|
2084
|
+
RENDER_MASK: "renderMask",
|
|
2085
|
+
RENDER_INLINE: "renderInline",
|
|
2086
|
+
FINALIZE: "finalize"
|
|
2087
|
+
};
|
|
2088
|
+
function notifyObservers(observers, ctx) {
|
|
2089
|
+
if (!observers) return;
|
|
2090
|
+
const fns = observers[ctx.step];
|
|
2091
|
+
if (!fns?.length) return;
|
|
2092
|
+
for (const fn of fns) fn(ctx);
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
// src/html/variants.ts
|
|
2096
|
+
function slugPageName(name) {
|
|
2097
|
+
const s = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2098
|
+
return s || "page";
|
|
2099
|
+
}
|
|
2100
|
+
function pageByName(doc, pageName) {
|
|
2101
|
+
return doc.pages.find((p) => p.name === pageName);
|
|
2102
|
+
}
|
|
2103
|
+
function variantLabels(variants) {
|
|
2104
|
+
return variants.map((v, i) => {
|
|
2105
|
+
const l = v.label.trim();
|
|
2106
|
+
return l || (i === 0 ? "PSRT" : `variant-${i}`);
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
function overlayDomId(pageName, variantIndex) {
|
|
2110
|
+
return `psrt-overlay-${slugPageName(pageName)}-v${variantIndex}`;
|
|
2111
|
+
}
|
|
2112
|
+
function textLayerDomId(pageName, textIndex, variantIndex) {
|
|
2113
|
+
return `psrt-text-${slugPageName(pageName)}-${textIndex}-v${variantIndex}`;
|
|
2114
|
+
}
|
|
2115
|
+
function maskLayerDomId(pageName, maskIndex, variantIndex) {
|
|
2116
|
+
return `psrt-mask-${slugPageName(pageName)}-${maskIndex}-v${variantIndex}`;
|
|
2117
|
+
}
|
|
2118
|
+
function buildHtmlVariants(primary, extra) {
|
|
2119
|
+
const variants = [{ label: "PSRT", doc: primary }];
|
|
2120
|
+
for (const item of extra ?? []) {
|
|
2121
|
+
const label = item.label?.trim() || `variant-${variants.length}`;
|
|
2122
|
+
variants.push({ label, doc: item.doc });
|
|
2123
|
+
}
|
|
2124
|
+
return variants;
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
// src/html/render/script.ts
|
|
2128
|
+
function variantSwitcherCSS() {
|
|
2129
|
+
return `
|
|
2130
|
+
.psrt-hidden{display:none!important}
|
|
2131
|
+
.psrt-variant-hint{position:fixed;z-index:9999;right:12px;bottom:12px;padding:6px 10px;font:12px/1.3 system-ui,sans-serif;color:#e8e8e8;background:rgba(0,0,0,.72);border-radius:6px;pointer-events:none;user-select:none}
|
|
2132
|
+
`;
|
|
2133
|
+
}
|
|
2134
|
+
function writeVariantSwitcher(labels) {
|
|
2135
|
+
if (labels.length < 2) return "";
|
|
2136
|
+
const labelsJSON = JSON.stringify(labels);
|
|
2137
|
+
return `<div id="psrt-variant-hint" class="psrt-variant-hint" aria-live="polite"></div>
|
|
2138
|
+
<script>
|
|
2139
|
+
/**
|
|
2140
|
+
* PSRT HTML \u2014 variant switcher
|
|
2141
|
+
*
|
|
2142
|
+
* Bundled PSRT variants share the same page images; only the text/mask
|
|
2143
|
+
* overlays differ. Press Ctrl+L to cycle:
|
|
2144
|
+
* variant 0 \u2192 variant 1 \u2192 \u2026 \u2192 "Sem PSRT" (hide all overlays) \u2192 variant 0 \u2026
|
|
2145
|
+
*
|
|
2146
|
+
* Each overlay has class psrt-v-N (N = variant index). Inactive overlays
|
|
2147
|
+
* use class psrt-hidden (display:none).
|
|
2148
|
+
*/
|
|
2149
|
+
(function () {
|
|
2150
|
+
/** Human-readable labels shown in the bottom-right hint. */
|
|
2151
|
+
var VARIANT_LABELS = ${labelsJSON};
|
|
2152
|
+
|
|
2153
|
+
/** Extra slot at the end: hide all PSRT overlays ("Sem PSRT"). */
|
|
2154
|
+
VARIANT_LABELS.push('');
|
|
2155
|
+
|
|
2156
|
+
/** Index into VARIANT_LABELS for the currently visible variant. */
|
|
2157
|
+
var activeVariantIndex = 0;
|
|
2158
|
+
|
|
2159
|
+
/** Bottom-right hint element (created above this script). */
|
|
2160
|
+
var hintEl = document.getElementById('psrt-variant-hint');
|
|
2161
|
+
|
|
2162
|
+
/**
|
|
2163
|
+
* Show one variant index, or hide every overlay when index is the off-state.
|
|
2164
|
+
*/
|
|
2165
|
+
function applyVariant(index) {
|
|
2166
|
+
document.querySelectorAll('.psrt-overlay').forEach(function (overlay) {
|
|
2167
|
+
overlay.classList.add('psrt-hidden');
|
|
2168
|
+
});
|
|
2169
|
+
|
|
2170
|
+
if (index < VARIANT_LABELS.length - 1) {
|
|
2171
|
+
document.querySelectorAll('.psrt-overlay.psrt-v-' + index).forEach(function (overlay) {
|
|
2172
|
+
overlay.classList.remove('psrt-hidden');
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
if (hintEl) {
|
|
2177
|
+
var label = VARIANT_LABELS[index];
|
|
2178
|
+
hintEl.textContent = label === ''
|
|
2179
|
+
? 'Sem PSRT (Ctrl+L)'
|
|
2180
|
+
: label + ' (Ctrl+L)';
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
document.addEventListener('keydown', function (event) {
|
|
2185
|
+
if (!event.ctrlKey || event.altKey) return;
|
|
2186
|
+
if (event.key.toLowerCase() !== 'l') return;
|
|
2187
|
+
event.preventDefault();
|
|
2188
|
+
activeVariantIndex = (activeVariantIndex + 1) % VARIANT_LABELS.length;
|
|
2189
|
+
applyVariant(activeVariantIndex);
|
|
2190
|
+
});
|
|
2191
|
+
|
|
2192
|
+
applyVariant(0);
|
|
2193
|
+
})();
|
|
2194
|
+
</script>
|
|
2195
|
+
`;
|
|
2196
|
+
}
|
|
2197
|
+
function variantClass(v) {
|
|
2198
|
+
return `psrt-v-${v}`;
|
|
2199
|
+
}
|
|
2200
|
+
function baseCSS(fontFaces) {
|
|
2201
|
+
return `${fontFaces.trim()}
|
|
2202
|
+
*{box-sizing:border-box;}
|
|
2203
|
+
.slides-wrap{margin:0;padding:0;display:flex;width:100%;flex-direction:column;align-items:center;}
|
|
2204
|
+
.slide{position:relative;display:block;flex:0 0 auto;line-height:0;margin:0;padding:0;}
|
|
2205
|
+
.slide-img{display:block;width:100%;height:auto;margin:0;padding:0;vertical-align:bottom;}
|
|
2206
|
+
.slide-overlays{position:absolute;left:0;top:0;right:0;bottom:0;}
|
|
2207
|
+
.slide-overlay{position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;container-type:size;container-name:slide;}
|
|
2208
|
+
.text-layer{position:absolute;box-sizing:border-box;margin:0;padding:0;line-height:1.2;overflow:hidden;overflow-wrap:anywhere;word-wrap:break-word;white-space:pre-wrap;}
|
|
2209
|
+
.text-ref-img{display:block;max-width:100%;height:auto;margin:0 0 .25em;padding:0;}
|
|
2210
|
+
`;
|
|
2211
|
+
}
|
|
2212
|
+
function escapeHtmlAttr(s) {
|
|
2213
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
// src/html/render/html.ts
|
|
2217
|
+
function buildFontCSS(fontURLs, assets, linksOnly) {
|
|
2218
|
+
if (fontURLs.length === 0) {
|
|
2219
|
+
return {
|
|
2220
|
+
fontFacesCSS: "",
|
|
2221
|
+
bodyStack: "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif"
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
2224
|
+
let fb = "";
|
|
2225
|
+
const names = [];
|
|
2226
|
+
for (let i = 0; i < fontURLs.length; i++) {
|
|
2227
|
+
const u = fontURLs[i]?.trim() ?? "";
|
|
2228
|
+
if (!u) continue;
|
|
2229
|
+
const a = assets.get(u);
|
|
2230
|
+
if (!a && !linksOnly) continue;
|
|
2231
|
+
const name = fontFamilyNameForURL(u, i);
|
|
2232
|
+
const src = fontSrcUrl(u, a, linksOnly);
|
|
2233
|
+
const format = faceFormat(a?.mime ?? "");
|
|
2234
|
+
if (linksOnly || a) {
|
|
2235
|
+
fb += `@font-face{font-family:'${name}';src:${src}${format};font-display:swap;}
|
|
2236
|
+
`;
|
|
2237
|
+
}
|
|
2238
|
+
names.push(`'${name}'`);
|
|
2239
|
+
}
|
|
2240
|
+
if (names.length === 0) {
|
|
2241
|
+
return {
|
|
2242
|
+
fontFacesCSS: fb,
|
|
2243
|
+
bodyStack: "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif"
|
|
2244
|
+
};
|
|
2245
|
+
}
|
|
2246
|
+
const stack = `${names.join(",")},-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif`;
|
|
2247
|
+
return { fontFacesCSS: fb, bodyStack: stack };
|
|
2248
|
+
}
|
|
2249
|
+
function pageBackgroundCSS(style) {
|
|
2250
|
+
const bg = backgroundColorFromStyle(style);
|
|
2251
|
+
return bg ? `background:${bg};` : "";
|
|
2252
|
+
}
|
|
2253
|
+
function writeTextLayer(parts, t, assets, canvasW, canvasH, variantIndex, linksOnly, pageName, observers) {
|
|
2254
|
+
const content = normalizeTextContent(t.content);
|
|
2255
|
+
const fontPx = textFontSizePx(t.textSize, canvasW, canvasH);
|
|
2256
|
+
const frags = adaptHTML({
|
|
2257
|
+
text: t,
|
|
2258
|
+
canvasW,
|
|
2259
|
+
canvasH,
|
|
2260
|
+
fontSizePx: fontPx,
|
|
2261
|
+
htmlCompile: true,
|
|
2262
|
+
pageSlug: pageName,
|
|
2263
|
+
textIndex: t.index
|
|
2264
|
+
});
|
|
2265
|
+
notifyObservers(observers, {
|
|
2266
|
+
step: CompileStep.ADAPT_STYLE,
|
|
2267
|
+
pageName,
|
|
2268
|
+
blockIndex: t.index,
|
|
2269
|
+
kind: "text"
|
|
2270
|
+
});
|
|
2271
|
+
let { boxCSS, textCSS } = htmlLayerCSS(frags);
|
|
2272
|
+
boxCSS = appendTextLayerGeometryCSS(boxCSS, t, content, canvasW, canvasH);
|
|
2273
|
+
const layerId = textLayerDomId(pageName, t.index, variantIndex);
|
|
2274
|
+
const classes = `text-layer psrt-text ${variantClass(variantIndex)}`;
|
|
2275
|
+
parts.push(
|
|
2276
|
+
`<div id="${escapeHtmlAttr(layerId)}" class="${classes}" style="${escapeHtmlAttr(boxCSS)}">`
|
|
2277
|
+
);
|
|
2278
|
+
const imgRef = (t.imageRef ?? "").trim();
|
|
2279
|
+
if (imgRef && isAssetReference(imgRef)) {
|
|
2280
|
+
const aa = assets.get(imgRef);
|
|
2281
|
+
if (linksOnly || aa && aa.mime.startsWith("image/")) {
|
|
2282
|
+
const refURI = assetRef(imgRef, aa, linksOnly);
|
|
2283
|
+
parts.push(
|
|
2284
|
+
`<img class="text-ref-img" src="${escapeHtmlAttr(refURI)}" alt="" style="margin:0 0 .25em 0;display:block;max-width:100%;height:auto"/>`
|
|
2285
|
+
);
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
const inlineHTML = renderInlineHTML(content);
|
|
2289
|
+
notifyObservers(observers, {
|
|
2290
|
+
step: CompileStep.RENDER_INLINE,
|
|
2291
|
+
pageName,
|
|
2292
|
+
textIndex: t.index,
|
|
2293
|
+
htmlLength: inlineHTML.length
|
|
2294
|
+
});
|
|
2295
|
+
notifyObservers(observers, {
|
|
2296
|
+
step: CompileStep.RENDER_TEXT,
|
|
2297
|
+
pageName,
|
|
2298
|
+
textIndex: t.index,
|
|
2299
|
+
contentPreview: content.slice(0, 80)
|
|
2300
|
+
});
|
|
2301
|
+
if (textCSS) {
|
|
2302
|
+
parts.push(`<span style="${escapeHtmlAttr(textCSS)}">`);
|
|
2303
|
+
} else {
|
|
2304
|
+
parts.push("<span>");
|
|
2305
|
+
}
|
|
2306
|
+
parts.push(inlineHTML);
|
|
2307
|
+
parts.push("</span></div>");
|
|
2308
|
+
}
|
|
2309
|
+
function writeMaskLayer(parts, m, assets, canvasW, canvasH, variantIndex, linksOnly, pageName, observers) {
|
|
2310
|
+
const frags = adaptMaskHTML({
|
|
2311
|
+
text: {
|
|
2312
|
+
x: m.x,
|
|
2313
|
+
y: m.y,
|
|
2314
|
+
width: m.width,
|
|
2315
|
+
textSize: 0,
|
|
2316
|
+
style: m.style,
|
|
2317
|
+
index: m.index,
|
|
2318
|
+
content: ""
|
|
2319
|
+
},
|
|
2320
|
+
mask: m,
|
|
2321
|
+
canvasW,
|
|
2322
|
+
canvasH,
|
|
2323
|
+
htmlCompile: true,
|
|
2324
|
+
pageSlug: pageName,
|
|
2325
|
+
textIndex: m.index
|
|
2326
|
+
});
|
|
2327
|
+
notifyObservers(observers, {
|
|
2328
|
+
step: CompileStep.ADAPT_STYLE,
|
|
2329
|
+
pageName,
|
|
2330
|
+
blockIndex: m.index,
|
|
2331
|
+
kind: "mask"
|
|
2332
|
+
});
|
|
2333
|
+
let { boxCSS } = htmlLayerCSS(frags);
|
|
2334
|
+
const imgRef = (m.imageRef ?? "").trim();
|
|
2335
|
+
if (imgRef && isAssetReference(imgRef)) {
|
|
2336
|
+
const aa = assets.get(imgRef);
|
|
2337
|
+
if (linksOnly || aa && aa.mime.startsWith("image/")) {
|
|
2338
|
+
const refURI = assetRef(imgRef, aa, linksOnly);
|
|
2339
|
+
if (boxCSS && !boxCSS.endsWith(";")) boxCSS += ";";
|
|
2340
|
+
boxCSS += `background-image:url(${refURI});background-size:cover;background-position:center;background-repeat:no-repeat;`;
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
const layerId = maskLayerDomId(pageName, m.index, variantIndex);
|
|
2344
|
+
const classes = `text-layer psrt-mask ${variantClass(variantIndex)}`;
|
|
2345
|
+
notifyObservers(observers, {
|
|
2346
|
+
step: CompileStep.RENDER_MASK,
|
|
2347
|
+
pageName,
|
|
2348
|
+
maskIndex: m.index
|
|
2349
|
+
});
|
|
2350
|
+
parts.push(`<div id="${escapeHtmlAttr(layerId)}" class="${classes}" style="${escapeHtmlAttr(boxCSS)}"></div>`);
|
|
2351
|
+
}
|
|
2352
|
+
function writeVariantOverlay(parts, variantPage, variantIndex, multiVariant, assets, canvasW, canvasH, linksOnly, observers) {
|
|
2353
|
+
const overlayId = overlayDomId(variantPage.name, variantIndex);
|
|
2354
|
+
let overlayClasses = `slide-overlay psrt-overlay ${variantClass(variantIndex)}`;
|
|
2355
|
+
if (multiVariant && variantIndex > 0) {
|
|
2356
|
+
overlayClasses += " psrt-hidden";
|
|
2357
|
+
}
|
|
2358
|
+
parts.push(`<div id="${escapeHtmlAttr(overlayId)}" class="${overlayClasses}">`);
|
|
2359
|
+
for (const entry of pageBlocksByIndex(variantPage)) {
|
|
2360
|
+
if (entry.kind === BlockText && entry.text) {
|
|
2361
|
+
writeTextLayer(
|
|
2362
|
+
parts,
|
|
2363
|
+
entry.text,
|
|
2364
|
+
assets,
|
|
2365
|
+
canvasW,
|
|
2366
|
+
canvasH,
|
|
2367
|
+
variantIndex,
|
|
2368
|
+
linksOnly,
|
|
2369
|
+
variantPage.name,
|
|
2370
|
+
observers
|
|
2371
|
+
);
|
|
2372
|
+
} else if (entry.kind === BlockMask && entry.mask) {
|
|
2373
|
+
writeMaskLayer(
|
|
2374
|
+
parts,
|
|
2375
|
+
entry.mask,
|
|
2376
|
+
assets,
|
|
2377
|
+
canvasW,
|
|
2378
|
+
canvasH,
|
|
2379
|
+
variantIndex,
|
|
2380
|
+
linksOnly,
|
|
2381
|
+
variantPage.name,
|
|
2382
|
+
observers
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
parts.push("</div>");
|
|
2387
|
+
}
|
|
2388
|
+
function writeSlide(parts, page, variants, assets, opts, pageIndex, multiVariant, observers) {
|
|
2389
|
+
const bg = pageBackgroundCSS(page.style);
|
|
2390
|
+
const imgURL = page.imageUrl.trim();
|
|
2391
|
+
const a = assets.get(imgURL);
|
|
2392
|
+
if (!opts.linksOnly && !a) {
|
|
2393
|
+
throw new Error(`missing fetched asset for page image "${imgURL}"`);
|
|
2394
|
+
}
|
|
2395
|
+
const src = assetRef(imgURL, a, !!opts.linksOnly);
|
|
2396
|
+
let canvasW;
|
|
2397
|
+
let canvasH;
|
|
2398
|
+
if (a) {
|
|
2399
|
+
const dims = imageDimensions(a.bytes, a.mime);
|
|
2400
|
+
canvasW = dims.w;
|
|
2401
|
+
canvasH = dims.h;
|
|
2402
|
+
} else {
|
|
2403
|
+
const dims = imageDimensions(null, "");
|
|
2404
|
+
canvasW = dims.w;
|
|
2405
|
+
canvasH = dims.h;
|
|
2406
|
+
}
|
|
2407
|
+
notifyObservers(observers, {
|
|
2408
|
+
step: CompileStep.RENDER_PAGE,
|
|
2409
|
+
pageIndex,
|
|
2410
|
+
pageName: page.name,
|
|
2411
|
+
canvasW,
|
|
2412
|
+
canvasH
|
|
2413
|
+
});
|
|
2414
|
+
let slideStyle = `width:${canvasW}px`;
|
|
2415
|
+
if (bg) slideStyle += `;${bg}`;
|
|
2416
|
+
parts.push(`<div class="slide" style="${escapeHtmlAttr(slideStyle)}">`);
|
|
2417
|
+
parts.push(`<img class="slide-img" src="${escapeHtmlAttr(src)}" alt=""/>`);
|
|
2418
|
+
parts.push('<div class="slide-overlays">');
|
|
2419
|
+
for (let vi = 0; vi < variants.length; vi++) {
|
|
2420
|
+
const variantPage = pageByName(variants[vi].doc, page.name);
|
|
2421
|
+
if (!variantPage) continue;
|
|
2422
|
+
writeVariantOverlay(
|
|
2423
|
+
parts,
|
|
2424
|
+
variantPage,
|
|
2425
|
+
vi,
|
|
2426
|
+
multiVariant,
|
|
2427
|
+
assets,
|
|
2428
|
+
canvasW,
|
|
2429
|
+
canvasH,
|
|
2430
|
+
!!opts.linksOnly,
|
|
2431
|
+
observers
|
|
2432
|
+
);
|
|
2433
|
+
}
|
|
2434
|
+
parts.push("</div></div>");
|
|
2435
|
+
}
|
|
2436
|
+
function renderHtmlBundle(variants, assets, opts, observers) {
|
|
2437
|
+
if (variants.length === 0) {
|
|
2438
|
+
throw new Error("no variants");
|
|
2439
|
+
}
|
|
2440
|
+
const primary = variants[0].doc;
|
|
2441
|
+
const labels = variantLabels(variants);
|
|
2442
|
+
const multiVariant = variants.length > 1;
|
|
2443
|
+
const includeVariantUI = multiVariant && !opts.noScript;
|
|
2444
|
+
const { fontFacesCSS, bodyStack } = buildFontCSS(primary.fonts ?? [], assets, !!opts.linksOnly);
|
|
2445
|
+
notifyObservers(observers, {
|
|
2446
|
+
step: CompileStep.RENDER_FONTS,
|
|
2447
|
+
fontCount: primary.fonts?.length ?? 0
|
|
2448
|
+
});
|
|
2449
|
+
let title = "PSRT";
|
|
2450
|
+
if (primary.pages.length > 0 && primary.pages[0]?.name.trim()) {
|
|
2451
|
+
title = primary.pages[0].name.trim();
|
|
2452
|
+
}
|
|
2453
|
+
notifyObservers(observers, {
|
|
2454
|
+
step: CompileStep.RENDER_HEAD,
|
|
2455
|
+
title
|
|
2456
|
+
});
|
|
2457
|
+
const parts = [];
|
|
2458
|
+
parts.push(`<!DOCTYPE html>
|
|
2459
|
+
<html lang="pt-BR">
|
|
2460
|
+
<head>
|
|
2461
|
+
<meta charset="utf-8"/>
|
|
2462
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
2463
|
+
<title>${escapeHtmlAttr(title)}</title>
|
|
2464
|
+
<style>`);
|
|
2465
|
+
parts.push(baseCSS(fontFacesCSS));
|
|
2466
|
+
if (multiVariant) parts.push(variantSwitcherCSS());
|
|
2467
|
+
parts.push(`
|
|
2468
|
+
body{font-family:${bodyStack};margin:0;padding:0;background:#111;overflow-x:auto;}
|
|
2469
|
+
</style>
|
|
2470
|
+
</head>
|
|
2471
|
+
<body>
|
|
2472
|
+
<main class="slides-wrap">
|
|
2473
|
+
`);
|
|
2474
|
+
for (let i = 0; i < primary.pages.length; i++) {
|
|
2475
|
+
writeSlide(parts, primary.pages[i], variants, assets, opts, i, multiVariant, observers);
|
|
2476
|
+
}
|
|
2477
|
+
parts.push(`
|
|
2478
|
+
</main>
|
|
2479
|
+
`);
|
|
2480
|
+
if (includeVariantUI) parts.push(writeVariantSwitcher(labels));
|
|
2481
|
+
parts.push(`
|
|
2482
|
+
</body>
|
|
2483
|
+
</html>`);
|
|
2484
|
+
const html = parts.join("");
|
|
2485
|
+
notifyObservers(observers, {
|
|
2486
|
+
step: CompileStep.FINALIZE,
|
|
2487
|
+
htmlLength: html.length,
|
|
2488
|
+
pageCount: primary.pages.length
|
|
2489
|
+
});
|
|
2490
|
+
return html;
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
// src/html/compilePure.ts
|
|
2494
|
+
function buildAssetMapWithSizes(variants, linksOnly) {
|
|
2495
|
+
const docs = variants.map((v) => v.doc);
|
|
2496
|
+
const assets = buildAssetMap(docs, linksOnly);
|
|
2497
|
+
const canvasSizes = {};
|
|
2498
|
+
for (const doc of docs) {
|
|
2499
|
+
for (const page of doc.pages) {
|
|
2500
|
+
const url = page.imageUrl.trim();
|
|
2501
|
+
if (canvasSizes[url]) continue;
|
|
2502
|
+
const asset = assets.get(url);
|
|
2503
|
+
if (asset) {
|
|
2504
|
+
canvasSizes[url] = imageDimensions(asset.bytes, asset.mime);
|
|
2505
|
+
} else if (linksOnly) {
|
|
2506
|
+
canvasSizes[url] = imageDimensions(null, "");
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
return { assets, canvasSizes };
|
|
2511
|
+
}
|
|
2512
|
+
function compileToHtmlPure(doc, options) {
|
|
2513
|
+
const opts = options ?? {};
|
|
2514
|
+
const primary = resolveDocumentPure(doc);
|
|
2515
|
+
notifyObservers(opts.observers, { step: CompileStep.RESOLVE, doc: primary });
|
|
2516
|
+
const extraResolved = (opts.variants ?? []).map((v) => ({
|
|
2517
|
+
label: v.label,
|
|
2518
|
+
doc: resolveDocumentPure(v.doc)
|
|
2519
|
+
}));
|
|
2520
|
+
const htmlVariants = buildHtmlVariants(primary, extraResolved);
|
|
2521
|
+
const { assets, canvasSizes } = buildAssetMapWithSizes(htmlVariants, !!opts.linksOnly);
|
|
2522
|
+
notifyObservers(opts.observers, {
|
|
2523
|
+
step: CompileStep.BUILD_ASSETS,
|
|
2524
|
+
assetCount: assets.size,
|
|
2525
|
+
canvasSizes
|
|
2526
|
+
});
|
|
2527
|
+
return renderHtmlBundle(htmlVariants, assets, opts, opts.observers);
|
|
2528
|
+
}
|
|
2529
|
+
|
|
194
2530
|
// src/compile.ts
|
|
195
2531
|
function compileToHtml(doc, options) {
|
|
196
2532
|
return invokeCompileToHtml(doc, options);
|
|
197
2533
|
}
|
|
2534
|
+
function compileToHtmlPure2(doc, options) {
|
|
2535
|
+
return compileToHtmlPure(doc, options);
|
|
2536
|
+
}
|
|
198
2537
|
function compileToSvg(doc, pageName, options) {
|
|
199
2538
|
return invokeCompileToSvg(doc, pageName, options);
|
|
200
2539
|
}
|
|
@@ -478,6 +2817,7 @@ function resolveDocumentStrict(doc) {
|
|
|
478
2817
|
return invokeDocMutation("resolveDocumentStrict", doc);
|
|
479
2818
|
}
|
|
480
2819
|
export {
|
|
2820
|
+
CompileStep,
|
|
481
2821
|
Transformer,
|
|
482
2822
|
adaptEntriesForWeb,
|
|
483
2823
|
addConst,
|
|
@@ -486,6 +2826,7 @@ export {
|
|
|
486
2826
|
addPage,
|
|
487
2827
|
addText,
|
|
488
2828
|
compileToHtml,
|
|
2829
|
+
compileToHtmlPure2 as compileToHtmlPure,
|
|
489
2830
|
compileToSvg,
|
|
490
2831
|
findMaskByIndex,
|
|
491
2832
|
findPage,
|
|
@@ -497,6 +2838,7 @@ export {
|
|
|
497
2838
|
mergePageDocumentPSRT,
|
|
498
2839
|
mergeStyle,
|
|
499
2840
|
movePage,
|
|
2841
|
+
notifyObservers,
|
|
500
2842
|
nudgeTextPosition,
|
|
501
2843
|
parse,
|
|
502
2844
|
parseTextIndex,
|
|
@@ -514,6 +2856,7 @@ export {
|
|
|
514
2856
|
reorderTextRelative,
|
|
515
2857
|
reorderTextTo,
|
|
516
2858
|
resolveDocument,
|
|
2859
|
+
resolveDocumentPure,
|
|
517
2860
|
resolveDocumentStrict,
|
|
518
2861
|
revertConstReferences,
|
|
519
2862
|
setMaskPosition,
|