@boxpdf/html-writer 0.1.11 → 0.1.12
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/index.cjs +113 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +113 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -29,6 +29,59 @@ var import_structure2 = require("@boxpdf/reader/structure");
|
|
|
29
29
|
|
|
30
30
|
// src/semantic-document.ts
|
|
31
31
|
var import_structure = require("@boxpdf/reader/structure");
|
|
32
|
+
|
|
33
|
+
// src/semantic-inline.ts
|
|
34
|
+
function dominantTextColor(lines) {
|
|
35
|
+
const counts = /* @__PURE__ */ new Map();
|
|
36
|
+
for (const span of lines.flatMap((line) => line.spans)) {
|
|
37
|
+
const color = normalizedColor(span.color) ?? "#000000";
|
|
38
|
+
counts.set(color, (counts.get(color) ?? 0) + Math.max(1, [...span.text].length));
|
|
39
|
+
}
|
|
40
|
+
return [...counts].sort((left, right) => right[1] - left[1])[0]?.[0] ?? "#000000";
|
|
41
|
+
}
|
|
42
|
+
function semanticTextHtml(text, lines, defaultColor) {
|
|
43
|
+
const ranges = [];
|
|
44
|
+
let cursor = 0;
|
|
45
|
+
for (const span of lines.flatMap((line) => line.spans)) {
|
|
46
|
+
if (!span.text) continue;
|
|
47
|
+
const start = text.indexOf(span.text, cursor);
|
|
48
|
+
if (start < 0) continue;
|
|
49
|
+
cursor = start + span.text.length;
|
|
50
|
+
const color = normalizedColor(span.color);
|
|
51
|
+
if (color && color !== defaultColor) ranges.push({ start, end: cursor, color });
|
|
52
|
+
}
|
|
53
|
+
const merged = mergeRanges(ranges, text);
|
|
54
|
+
let html = "";
|
|
55
|
+
let offset = 0;
|
|
56
|
+
for (const range of merged) {
|
|
57
|
+
html += escapeHtml(text.slice(offset, range.start));
|
|
58
|
+
html += `<span style="color:${range.color}">${escapeHtml(text.slice(range.start, range.end))}</span>`;
|
|
59
|
+
offset = range.end;
|
|
60
|
+
}
|
|
61
|
+
return html + escapeHtml(text.slice(offset));
|
|
62
|
+
}
|
|
63
|
+
function mergeRanges(ranges, text) {
|
|
64
|
+
const merged = [];
|
|
65
|
+
for (const range of ranges) {
|
|
66
|
+
const previous = merged.at(-1);
|
|
67
|
+
if (previous && previous.color === range.color && /^\s*$/.test(text.slice(previous.end, range.start))) {
|
|
68
|
+
previous.end = range.end;
|
|
69
|
+
} else {
|
|
70
|
+
merged.push({ ...range });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return merged;
|
|
74
|
+
}
|
|
75
|
+
function normalizedColor(value) {
|
|
76
|
+
if (!value || !/^#[\da-f]{6}$/i.test(value)) return void 0;
|
|
77
|
+
const color = value.toLowerCase();
|
|
78
|
+
return color === "#000000" || color === "#000" ? "#000000" : color;
|
|
79
|
+
}
|
|
80
|
+
function escapeHtml(value) {
|
|
81
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/semantic-document.ts
|
|
32
85
|
async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
33
86
|
const stats = {
|
|
34
87
|
pagesProcessed: 0,
|
|
@@ -60,7 +113,7 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
60
113
|
};
|
|
61
114
|
const flushPendingParagraph = async () => {
|
|
62
115
|
if (!pendingParagraph) return;
|
|
63
|
-
await write(semanticBlockHtml(pendingParagraph.block));
|
|
116
|
+
await write(semanticBlockHtml(pendingParagraph.block, pendingParagraph.defaultColor));
|
|
64
117
|
pendingParagraph = void 0;
|
|
65
118
|
};
|
|
66
119
|
const closeEmployment = async () => {
|
|
@@ -69,9 +122,11 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
69
122
|
employmentOpen = false;
|
|
70
123
|
};
|
|
71
124
|
const emitPage = async (page, future) => {
|
|
125
|
+
const defaultColor = dominantTextColor(page.structured.lines);
|
|
72
126
|
const futureFurniture = new Set(future.flatMap((candidate) => marginSignatures(candidate)));
|
|
73
127
|
const repeatedFurniture = /* @__PURE__ */ new Set([...seenFurniture, ...futureFurniture]);
|
|
74
|
-
for (const block of page.structured.blocks) {
|
|
128
|
+
for (const [blockIndex, block] of page.structured.blocks.entries()) {
|
|
129
|
+
const nextBlock = page.structured.blocks[blockIndex + 1];
|
|
75
130
|
if (isRepeatedFurniture(block, page, repeatedFurniture)) {
|
|
76
131
|
stats.suppressedFurniture += 1;
|
|
77
132
|
continue;
|
|
@@ -79,19 +134,23 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
79
134
|
await flushPendingParagraph();
|
|
80
135
|
if (employmentOpen && block.type !== "list") await closeEmployment();
|
|
81
136
|
if (!contentStarted && !headerOpen && block.type === "heading" && block.level === 1) {
|
|
82
|
-
await write(`<header><h1>${
|
|
137
|
+
await write(`<header><h1>${semanticTextHtml(block.text, block.lines, defaultColor)}</h1>`);
|
|
83
138
|
headerOpen = true;
|
|
84
139
|
continue;
|
|
85
140
|
}
|
|
86
141
|
if (headerOpen) {
|
|
87
142
|
if (block.type === "paragraph") {
|
|
88
143
|
const tag = isContactBlock(block) ? "address" : "p";
|
|
89
|
-
await write(
|
|
144
|
+
await write(
|
|
145
|
+
`<${tag}>${semanticTextHtml(block.text, block.lines, defaultColor)}</${tag}>`
|
|
146
|
+
);
|
|
90
147
|
headerHasParagraph = true;
|
|
91
148
|
continue;
|
|
92
149
|
}
|
|
93
|
-
if (block.type === "heading" && !headerHasParagraph && (block.level === 1 || block.text.trimStart().startsWith("#"))) {
|
|
94
|
-
await write(
|
|
150
|
+
if (block.type === "heading" && !headerHasParagraph && (block.level === 1 || block.text.trimStart().startsWith("#") || block.level === 4 && nextBlock?.type === "paragraph" && isContactBlock(nextBlock))) {
|
|
151
|
+
await write(
|
|
152
|
+
`<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor)}</h${block.level}>`
|
|
153
|
+
);
|
|
95
154
|
continue;
|
|
96
155
|
}
|
|
97
156
|
await write("</header>");
|
|
@@ -128,7 +187,7 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
128
187
|
const level = contentStarted && block.level === 1 ? 2 : block.level;
|
|
129
188
|
await closeSections(level);
|
|
130
189
|
await write(
|
|
131
|
-
`<section data-level="${level}"><h${level}>${
|
|
190
|
+
`<section data-level="${level}"><h${level}>${semanticTextHtml(block.text, block.lines, defaultColor)}</h${level}>`
|
|
132
191
|
);
|
|
133
192
|
sectionLevels.push(level);
|
|
134
193
|
continue;
|
|
@@ -136,27 +195,27 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
136
195
|
if (block.type === "paragraph") {
|
|
137
196
|
if (isTitledRecord(block)) {
|
|
138
197
|
const [institution, ...details] = block.lines;
|
|
139
|
-
if (institution) await write(`<h3>${
|
|
140
|
-
for (const detail of details) await write(`<p>${
|
|
198
|
+
if (institution) await write(`<h3>${escapeHtml2(institution.text)}</h3>`);
|
|
199
|
+
for (const detail of details) await write(`<p>${escapeHtml2(detail.text)}</p>`);
|
|
141
200
|
continue;
|
|
142
201
|
}
|
|
143
202
|
if (isUnmarkedList(block)) {
|
|
144
203
|
await write(
|
|
145
|
-
`<ul>${block.lines.map((line) => `<li>${
|
|
204
|
+
`<ul>${block.lines.map((line) => `<li>${escapeHtml2(line.text)}</li>`).join("")}</ul>`
|
|
146
205
|
);
|
|
147
206
|
continue;
|
|
148
207
|
}
|
|
149
|
-
pendingParagraph = { block, height: page.height };
|
|
208
|
+
pendingParagraph = { block, height: page.height, defaultColor };
|
|
150
209
|
continue;
|
|
151
210
|
}
|
|
152
211
|
if (block.type === "employment") {
|
|
153
212
|
await write(
|
|
154
|
-
`<section><h3>${
|
|
213
|
+
`<section><h3>${escapeHtml2(block.role)}</h3><p>${escapeHtml2(block.organization)}</p><p>${escapeHtml2(block.date)}</p>`
|
|
155
214
|
);
|
|
156
215
|
employmentOpen = true;
|
|
157
216
|
continue;
|
|
158
217
|
}
|
|
159
|
-
await write(semanticBlockHtml(block));
|
|
218
|
+
await write(semanticBlockHtml(block, defaultColor));
|
|
160
219
|
}
|
|
161
220
|
for (const signature of marginSignatures(page)) seenFurniture.add(signature);
|
|
162
221
|
};
|
|
@@ -183,7 +242,9 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
183
242
|
await closeEmployment();
|
|
184
243
|
if (pendingParagraph && isFooterParagraph(pendingParagraph.block, pendingParagraph.height)) {
|
|
185
244
|
await closeSections();
|
|
186
|
-
await write(
|
|
245
|
+
await write(
|
|
246
|
+
`<footer>${semanticBlockHtml(pendingParagraph.block, pendingParagraph.defaultColor)}</footer>`
|
|
247
|
+
);
|
|
187
248
|
pendingParagraph = void 0;
|
|
188
249
|
} else {
|
|
189
250
|
await flushPendingParagraph();
|
|
@@ -273,7 +334,7 @@ function sameRow(left, right) {
|
|
|
273
334
|
}
|
|
274
335
|
function tableRow(row, header) {
|
|
275
336
|
const cell = header ? "th" : "td";
|
|
276
|
-
return `<tr>${row.map((value) => `<${cell}>${
|
|
337
|
+
return `<tr>${row.map((value) => `<${cell}>${escapeHtml2(value)}</${cell}>`).join("")}</tr>`;
|
|
277
338
|
}
|
|
278
339
|
function isFinancialSummary(block) {
|
|
279
340
|
return block.entries.length > 0 && block.entries.every((entry) => isNumericValue(entry.description)) && block.entries.some((entry) => /\p{Sc}|%/u.test(entry.description));
|
|
@@ -283,20 +344,22 @@ function isNumericValue(value) {
|
|
|
283
344
|
}
|
|
284
345
|
function financialSummaryRow(entry, columns) {
|
|
285
346
|
const colspan = columns > 2 ? ` colspan="${columns - 1}"` : "";
|
|
286
|
-
return `<tr><th scope="row"${colspan}>${
|
|
347
|
+
return `<tr><th scope="row"${colspan}>${escapeHtml2(entry.term)}</th><td>${escapeHtml2(entry.description)}</td></tr>`;
|
|
287
348
|
}
|
|
288
|
-
function semanticBlockHtml(block) {
|
|
349
|
+
function semanticBlockHtml(block, defaultColor = "#000000") {
|
|
289
350
|
if (block.type === "heading")
|
|
290
|
-
return `<h${block.level}>${
|
|
291
|
-
if (block.type === "paragraph")
|
|
351
|
+
return `<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor)}</h${block.level}>`;
|
|
352
|
+
if (block.type === "paragraph")
|
|
353
|
+
return `<p>${semanticTextHtml(block.text, block.lines, defaultColor)}</p>`;
|
|
354
|
+
if (block.type === "preformatted") return `<pre>${escapeHtml2(block.text)}</pre>`;
|
|
292
355
|
if (block.type === "definitionList") {
|
|
293
356
|
if (block.entries.length <= 3 && block.entries.some((entry) => entry.description.trim().split(/\s+/).length >= 5) && block.entries.every((entry) => /^[A-Z][A-Z\s/-]*$/.test(entry.term.trim()))) {
|
|
294
357
|
return block.entries.map(
|
|
295
|
-
(entry) => `<section><h2>${
|
|
358
|
+
(entry) => `<section><h2>${escapeHtml2(titleCase(entry.term))}</h2><p>${escapeHtml2(entry.description)}</p></section>`
|
|
296
359
|
).join("");
|
|
297
360
|
}
|
|
298
361
|
const list = `<dl>${block.entries.map(
|
|
299
|
-
(entry) => `<div><dt>${
|
|
362
|
+
(entry) => `<div><dt>${escapeHtml2(entry.term)}</dt><dd>${escapeHtml2(entry.description)}</dd></div>`
|
|
300
363
|
).join("")}</dl>`;
|
|
301
364
|
return isFinancialSummary(block) ? `<section>${list}</section>` : list;
|
|
302
365
|
}
|
|
@@ -307,42 +370,42 @@ function semanticBlockHtml(block) {
|
|
|
307
370
|
return block.items.map(labeledSectionHtml).join("");
|
|
308
371
|
}
|
|
309
372
|
if (block.type === "employment") {
|
|
310
|
-
return `<section><h3>${
|
|
373
|
+
return `<section><h3>${escapeHtml2(block.role)}</h3><p>${escapeHtml2(block.organization)}</p><p>${escapeHtml2(block.date)}</p></section>`;
|
|
311
374
|
}
|
|
312
375
|
const tag = block.ordered ? "ol" : "ul";
|
|
313
|
-
return `<${tag}>${block.items.map((item) => `<li>${
|
|
376
|
+
return `<${tag}>${block.items.map((item) => `<li>${escapeHtml2(item.text)}</li>`).join("")}</${tag}>`;
|
|
314
377
|
}
|
|
315
378
|
function cardTableRow(item) {
|
|
316
379
|
const trailing = item.details.at(-1) ?? "";
|
|
317
380
|
const match = /^\s*[×x]\s*(\d+)\s+(.+)$/u.exec(trailing);
|
|
318
381
|
const description = item.details.slice(0, -1).join(" ");
|
|
319
|
-
const detail = description ? `<br><span>${
|
|
382
|
+
const detail = description ? `<br><span>${escapeHtml2(description)}</span>` : "";
|
|
320
383
|
const quantity = match?.[1] ?? "";
|
|
321
384
|
const amount = match?.[2] ?? trailing;
|
|
322
|
-
return `<tr><th scope="row">${
|
|
385
|
+
return `<tr><th scope="row">${escapeHtml2(item.title)}${detail}</th><td>${escapeHtml2(quantity)}</td><td>${escapeHtml2(amount)}</td></tr>`;
|
|
323
386
|
}
|
|
324
387
|
function labeledSectionHtml(item) {
|
|
325
388
|
const heading = titleCase(item.label);
|
|
326
389
|
const postal = /\b(?:ship|deliver|mail)(?:ed)?\b/i.test(item.label);
|
|
327
390
|
if (postal) {
|
|
328
391
|
const [name, ...address] = item.content;
|
|
329
|
-
const content = [name ? `<strong>${
|
|
330
|
-
return `<section><h2>${
|
|
392
|
+
const content = [name ? `<strong>${escapeHtml2(name)}</strong>` : "", ...address.map(escapeHtml2)].filter(Boolean).join("<br>");
|
|
393
|
+
return `<section><h2>${escapeHtml2(heading)}</h2><address>${content}</address></section>`;
|
|
331
394
|
}
|
|
332
|
-
return `<section><h2>${
|
|
333
|
-
(content, index) => `<p>${index === 0 ? `<strong>${
|
|
395
|
+
return `<section><h2>${escapeHtml2(heading)}</h2>${item.content.map(
|
|
396
|
+
(content, index) => `<p>${index === 0 ? `<strong>${escapeHtml2(content)}</strong>` : escapeHtml2(content)}</p>`
|
|
334
397
|
).join("")}</section>`;
|
|
335
398
|
}
|
|
336
399
|
function titleCase(value) {
|
|
337
400
|
const normalized = value.trim().toLocaleLowerCase("en");
|
|
338
401
|
return normalized.replace(/^\p{L}/u, (letter) => letter.toLocaleUpperCase("en"));
|
|
339
402
|
}
|
|
340
|
-
function
|
|
403
|
+
function escapeHtml2(value) {
|
|
341
404
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
342
405
|
}
|
|
343
406
|
|
|
344
407
|
// src/index.ts
|
|
345
|
-
var styles = `.pdf-document{margin:0 auto}.pdf-page{box-sizing:border-box;margin:1rem auto;background:#fff;color:#000}.pdf-page--visual,.pdf-page--positioned{position:relative;overflow:hidden}.pdf-page-content{position:absolute;transform-origin:0 0}.pdf-span{position:absolute;white-space:pre;transform-origin:left bottom;unicode-bidi:isolate}.pdf-span[data-direction=ttb]{writing-mode:vertical-rl}.pdf-page--semantic,.pdf-page--flow{max-width:60rem;padding:1rem}.pdf-page--semantic p,.pdf-page--flow p{white-space:pre-wrap;unicode-bidi:plaintext}.pdf-page table{border-collapse:collapse}.pdf-page td{padding:.15rem .4rem;vertical-align:top}`;
|
|
408
|
+
var styles = `.pdf-document{margin:0 auto}.pdf-page{box-sizing:border-box;margin:1rem auto;background:#fff;color:#000}.pdf-page--visual,.pdf-page--positioned{position:relative;overflow:hidden}.pdf-page-content{position:absolute;transform-origin:0 0}.pdf-span{position:absolute;white-space:pre;transform-origin:left bottom;unicode-bidi:isolate}.pdf-span[data-direction=ttb]{writing-mode:vertical-rl}.pdf-page--semantic,.pdf-page--flow{max-width:60rem;padding:1rem}.pdf-page--semantic p,.pdf-page--flow p{white-space:pre-wrap;unicode-bidi:plaintext}.pdf-semantic-document h1,.pdf-page--semantic h1{font-size:1.7em}.pdf-semantic-document h2,.pdf-page--semantic h2{font-size:1.5em}.pdf-semantic-document h3,.pdf-page--semantic h3{font-size:1.35em}.pdf-semantic-document h4,.pdf-page--semantic h4{font-size:1.1em}.pdf-page table{border-collapse:collapse}.pdf-page td{padding:.15rem .4rem;vertical-align:top}`;
|
|
346
409
|
async function writeHtmlDocument(pages, write, options = {}) {
|
|
347
410
|
const includeDocument = options.includeDocument ?? true;
|
|
348
411
|
if (includeDocument) {
|
|
@@ -351,7 +414,7 @@ async function writeHtmlDocument(pages, write, options = {}) {
|
|
|
351
414
|
` lang="${escapeAttribute(options.language ?? "en")}"><head><meta charset="utf-8">`
|
|
352
415
|
);
|
|
353
416
|
await write('<meta name="viewport" content="width=device-width,initial-scale=1">');
|
|
354
|
-
await write(`<title>${
|
|
417
|
+
await write(`<title>${escapeHtml3(options.title ?? "PDF document")}</title>`);
|
|
355
418
|
if (options.includeStyles ?? true) await write(`<style>${styles}</style>`);
|
|
356
419
|
await write("</head><body>");
|
|
357
420
|
}
|
|
@@ -560,53 +623,58 @@ function positionedSpan(span, fontAliases) {
|
|
|
560
623
|
span.fontAssetId ? fontAliases.get(span.fontAssetId) : void 0
|
|
561
624
|
)
|
|
562
625
|
].join(";");
|
|
563
|
-
return `<span class="pdf-span"${direction} style="${style}">${
|
|
626
|
+
return `<span class="pdf-span"${direction} style="${style}">${escapeHtml3(span.text)}</span>`;
|
|
564
627
|
}
|
|
565
628
|
async function writeFlowPage(page, write) {
|
|
566
629
|
const structured = (0, import_structure2.structurePage)(page);
|
|
630
|
+
const defaultColor = dominantTextColor(structured.lines);
|
|
567
631
|
await write(
|
|
568
632
|
`<section class="pdf-page pdf-page--semantic pdf-page--flow" data-page="${page.number}">`
|
|
569
633
|
);
|
|
570
634
|
for (const block of structured.blocks) {
|
|
571
635
|
if (block.type === "table") await write((0, import_structure2.tableToHtml)(block.table));
|
|
572
636
|
else if (block.type === "heading") {
|
|
573
|
-
await write(
|
|
637
|
+
await write(
|
|
638
|
+
`<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor)}</h${block.level}>`
|
|
639
|
+
);
|
|
574
640
|
} else if (block.type === "paragraph") {
|
|
575
641
|
await write(
|
|
576
|
-
`<p${directionAttribute(block.lines.flatMap((line) => line.spans))}>${
|
|
642
|
+
`<p${directionAttribute(block.lines.flatMap((line) => line.spans))}>${semanticTextHtml(block.text, block.lines, defaultColor)}</p>`
|
|
577
643
|
);
|
|
644
|
+
} else if (block.type === "preformatted") {
|
|
645
|
+
await write(`<pre>${escapeHtml3(block.text)}</pre>`);
|
|
578
646
|
} else if (block.type === "definitionList") {
|
|
579
647
|
await write("<dl>");
|
|
580
648
|
for (const entry of block.entries) {
|
|
581
649
|
await write(
|
|
582
|
-
`<div><dt>${
|
|
650
|
+
`<div><dt>${escapeHtml3(entry.term)}</dt><dd>${escapeHtml3(entry.description)}</dd></div>`
|
|
583
651
|
);
|
|
584
652
|
}
|
|
585
653
|
await write("</dl>");
|
|
586
654
|
} else if (block.type === "cardList") {
|
|
587
655
|
await write('<div class="pdf-semantic-cards">');
|
|
588
656
|
for (const item of block.items) {
|
|
589
|
-
await write(`<article><h3>${
|
|
590
|
-
for (const detail of item.details) await write(`<p>${
|
|
657
|
+
await write(`<article><h3>${escapeHtml3(item.title)}</h3>`);
|
|
658
|
+
for (const detail of item.details) await write(`<p>${escapeHtml3(detail)}</p>`);
|
|
591
659
|
await write("</article>");
|
|
592
660
|
}
|
|
593
661
|
await write("</div>");
|
|
594
662
|
} else if (block.type === "sectionGroup") {
|
|
595
663
|
await write('<div class="pdf-semantic-sections">');
|
|
596
664
|
for (const item of block.items) {
|
|
597
|
-
await write(`<section><h3>${
|
|
598
|
-
for (const content of item.content) await write(`<p>${
|
|
665
|
+
await write(`<section><h3>${escapeHtml3(item.label)}</h3>`);
|
|
666
|
+
for (const content of item.content) await write(`<p>${escapeHtml3(content)}</p>`);
|
|
599
667
|
await write("</section>");
|
|
600
668
|
}
|
|
601
669
|
await write("</div>");
|
|
602
670
|
} else if (block.type === "employment") {
|
|
603
671
|
await write(
|
|
604
|
-
`<section><h3>${
|
|
672
|
+
`<section><h3>${escapeHtml3(block.role)}</h3><p>${escapeHtml3(block.organization)}</p><p>${escapeHtml3(block.date)}</p></section>`
|
|
605
673
|
);
|
|
606
674
|
} else {
|
|
607
675
|
const tag = block.ordered ? "ol" : "ul";
|
|
608
676
|
await write(`<${tag}>`);
|
|
609
|
-
for (const item of block.items) await write(`<li>${
|
|
677
|
+
for (const item of block.items) await write(`<li>${escapeHtml3(item.text)}</li>`);
|
|
610
678
|
await write(`</${tag}>`);
|
|
611
679
|
}
|
|
612
680
|
}
|
|
@@ -650,7 +718,7 @@ function visualText(span, pageHeight, fontAliases, counterRotateReflectedText =
|
|
|
650
718
|
const anchorX = span.bounds.x + basisX * rtlOffset;
|
|
651
719
|
const anchorY = pageHeight - span.bounds.y + basisY * rtlOffset;
|
|
652
720
|
const position = transformed ? ` x="0" y="0" transform="matrix(${transform?.map(number).join(" ")} ${number(anchorX)} ${number(anchorY)})"` : ` x="${number(anchorX)}" y="${number(anchorY)}"`;
|
|
653
|
-
return `<text${direction}${position} font-size="${number(span.fontSize)}"${textLength}${style ? ` style="${style}"` : ""}>${
|
|
721
|
+
return `<text${direction}${position} font-size="${number(span.fontSize)}"${textLength}${style ? ` style="${style}"` : ""}>${escapeHtml3(span.text)}</text>`;
|
|
654
722
|
}
|
|
655
723
|
function isAdobeCjkFont(fontFamily) {
|
|
656
724
|
return /^Adobe(?:Heiti|Song|Kaiti|Ming|Gothic|Mincho)Std-/i.test(fontFamily ?? "");
|
|
@@ -767,9 +835,9 @@ function number(value) {
|
|
|
767
835
|
return Number.isFinite(value) ? String(Math.round(value * 1e3) / 1e3) : "0";
|
|
768
836
|
}
|
|
769
837
|
function escapeAttribute(value) {
|
|
770
|
-
return
|
|
838
|
+
return escapeHtml3(value).replaceAll("`", "`");
|
|
771
839
|
}
|
|
772
|
-
function
|
|
840
|
+
function escapeHtml3(value) {
|
|
773
841
|
return [...value].map((character) => {
|
|
774
842
|
const codePoint = character.codePointAt(0) ?? 0;
|
|
775
843
|
if (codePoint === 13) return "\n";
|