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