@boxpdf/html-writer 0.1.15 → 0.1.17
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 +40 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +40 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -39,7 +39,7 @@ function dominantTextColor(lines) {
|
|
|
39
39
|
}
|
|
40
40
|
return [...counts].sort((left, right) => right[1] - left[1])[0]?.[0] ?? "#000000";
|
|
41
41
|
}
|
|
42
|
-
function semanticTextHtml(text, lines, defaultColor) {
|
|
42
|
+
function semanticTextHtml(text, lines, defaultColor, preserveWeight = true) {
|
|
43
43
|
const ranges = [];
|
|
44
44
|
let cursor = 0;
|
|
45
45
|
for (const span of lines.flatMap((line) => line.spans)) {
|
|
@@ -48,14 +48,25 @@ function semanticTextHtml(text, lines, defaultColor) {
|
|
|
48
48
|
if (start < 0) continue;
|
|
49
49
|
cursor = start + span.text.length;
|
|
50
50
|
const color = normalizedColor(span.color);
|
|
51
|
-
|
|
51
|
+
const bold = preserveWeight && /(?:bold|semibold|demi|medium|medi)/i.test(span.fontFamily ?? "");
|
|
52
|
+
const italic = /(?:italic|oblique|slanted|slant|ital)/i.test(span.fontFamily ?? "");
|
|
53
|
+
const nondefaultColor = color && color !== defaultColor ? color : void 0;
|
|
54
|
+
if (nondefaultColor || bold || italic) {
|
|
55
|
+
ranges.push({
|
|
56
|
+
start,
|
|
57
|
+
end: cursor,
|
|
58
|
+
...nondefaultColor ? { color: nondefaultColor } : {},
|
|
59
|
+
bold,
|
|
60
|
+
italic
|
|
61
|
+
});
|
|
62
|
+
}
|
|
52
63
|
}
|
|
53
64
|
const merged = mergeRanges(ranges, text);
|
|
54
65
|
let html = "";
|
|
55
66
|
let offset = 0;
|
|
56
67
|
for (const range of merged) {
|
|
57
68
|
html += escapeHtml(text.slice(offset, range.start));
|
|
58
|
-
html +=
|
|
69
|
+
html += styledHtml(text.slice(range.start, range.end), range);
|
|
59
70
|
offset = range.end;
|
|
60
71
|
}
|
|
61
72
|
return html + escapeHtml(text.slice(offset));
|
|
@@ -64,7 +75,7 @@ function mergeRanges(ranges, text) {
|
|
|
64
75
|
const merged = [];
|
|
65
76
|
for (const range of ranges) {
|
|
66
77
|
const previous = merged.at(-1);
|
|
67
|
-
if (previous && previous.color === range.color && /^\s*$/.test(text.slice(previous.end, range.start))) {
|
|
78
|
+
if (previous && previous.color === range.color && previous.bold === range.bold && previous.italic === range.italic && /^\s*$/.test(text.slice(previous.end, range.start))) {
|
|
68
79
|
previous.end = range.end;
|
|
69
80
|
} else {
|
|
70
81
|
merged.push({ ...range });
|
|
@@ -72,6 +83,13 @@ function mergeRanges(ranges, text) {
|
|
|
72
83
|
}
|
|
73
84
|
return merged;
|
|
74
85
|
}
|
|
86
|
+
function styledHtml(value, range) {
|
|
87
|
+
let html = escapeHtml(value);
|
|
88
|
+
if (range.color) html = `<span style="color:${range.color}">${html}</span>`;
|
|
89
|
+
if (range.italic) html = `<em>${html}</em>`;
|
|
90
|
+
if (range.bold) html = `<strong>${html}</strong>`;
|
|
91
|
+
return html;
|
|
92
|
+
}
|
|
75
93
|
function normalizedColor(value) {
|
|
76
94
|
if (!value || !/^#[\da-f]{6}$/i.test(value)) return void 0;
|
|
77
95
|
const color = value.toLowerCase();
|
|
@@ -365,7 +383,9 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
365
383
|
await flushPendingParagraph();
|
|
366
384
|
if (employmentOpen && block.type !== "list") await closeEmployment();
|
|
367
385
|
if (!contentStarted && !headerOpen && block.type === "heading" && block.level === 1) {
|
|
368
|
-
await write(
|
|
386
|
+
await write(
|
|
387
|
+
`<header><h1>${semanticTextHtml(block.text, block.lines, defaultColor, false)}</h1>`
|
|
388
|
+
);
|
|
369
389
|
headerOpen = true;
|
|
370
390
|
continue;
|
|
371
391
|
}
|
|
@@ -380,7 +400,7 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
380
400
|
}
|
|
381
401
|
if (block.type === "heading" && !headerHasParagraph && (block.level === 1 || block.text.trimStart().startsWith("#") || block.level === 4 && nextBlock?.type === "paragraph" && isContactBlock(nextBlock))) {
|
|
382
402
|
await write(
|
|
383
|
-
`<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor)}</h${block.level}>`
|
|
403
|
+
`<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor, false)}</h${block.level}>`
|
|
384
404
|
);
|
|
385
405
|
continue;
|
|
386
406
|
}
|
|
@@ -418,7 +438,7 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
418
438
|
const level = contentStarted && block.level === 1 ? 2 : block.level;
|
|
419
439
|
await closeSections(level);
|
|
420
440
|
await write(
|
|
421
|
-
`<section data-level="${level}"><h${level}>${semanticTextHtml(block.text, block.lines, defaultColor)}</h${level}>`
|
|
441
|
+
`<section data-level="${level}"><h${level}>${semanticTextHtml(block.text, block.lines, defaultColor, false)}</h${level}>`
|
|
422
442
|
);
|
|
423
443
|
sectionLevels.push(level);
|
|
424
444
|
continue;
|
|
@@ -587,7 +607,7 @@ function financialSummaryRow(entry, columns) {
|
|
|
587
607
|
}
|
|
588
608
|
function semanticBlockHtml(block, defaultColor = "#000000") {
|
|
589
609
|
if (block.type === "heading")
|
|
590
|
-
return `<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor)}</h${block.level}>`;
|
|
610
|
+
return `<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor, false)}</h${block.level}>`;
|
|
591
611
|
if (block.type === "paragraph")
|
|
592
612
|
return `<p>${semanticTextHtml(block.text, block.lines, defaultColor)}</p>`;
|
|
593
613
|
if (block.type === "preformatted") return `<pre>${escapeHtml3(block.text)}</pre>`;
|
|
@@ -612,7 +632,7 @@ function semanticBlockHtml(block, defaultColor = "#000000") {
|
|
|
612
632
|
return `<section><h3>${escapeHtml3(block.role)}</h3><p>${escapeHtml3(block.organization)}</p><p>${escapeHtml3(block.date)}</p></section>`;
|
|
613
633
|
}
|
|
614
634
|
const tag = block.ordered ? "ol" : "ul";
|
|
615
|
-
return `<${tag}>${block.items.map((item) => `<li>${
|
|
635
|
+
return `<${tag}>${block.items.map((item) => `<li>${semanticTextHtml(item.text, item.lines, defaultColor)}</li>`).join("")}</${tag}>`;
|
|
616
636
|
}
|
|
617
637
|
function semanticBlockY(block) {
|
|
618
638
|
const lines = block.type === "list" ? block.items.flatMap((item) => item.lines) : block.lines;
|
|
@@ -885,7 +905,7 @@ async function writeFlowPage(page, write) {
|
|
|
885
905
|
if (block.type === "table") await write((0, import_structure2.tableToHtml)(block.table));
|
|
886
906
|
else if (block.type === "heading") {
|
|
887
907
|
await write(
|
|
888
|
-
`<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor)}</h${block.level}>`
|
|
908
|
+
`<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor, false)}</h${block.level}>`
|
|
889
909
|
);
|
|
890
910
|
} else if (block.type === "paragraph") {
|
|
891
911
|
await write(
|
|
@@ -924,7 +944,9 @@ async function writeFlowPage(page, write) {
|
|
|
924
944
|
} else {
|
|
925
945
|
const tag = block.ordered ? "ol" : "ul";
|
|
926
946
|
await write(`<${tag}>`);
|
|
927
|
-
for (const item of block.items)
|
|
947
|
+
for (const item of block.items) {
|
|
948
|
+
await write(`<li>${semanticTextHtml(item.text, item.lines, defaultColor)}</li>`);
|
|
949
|
+
}
|
|
928
950
|
await write(`</${tag}>`);
|
|
929
951
|
}
|
|
930
952
|
}
|
|
@@ -994,7 +1016,7 @@ function visualType3Text(span, font, pageHeight) {
|
|
|
994
1016
|
let content = "";
|
|
995
1017
|
for (const glyph of sequence) {
|
|
996
1018
|
if (!glyph) continue;
|
|
997
|
-
content += `<g transform="translate(${number2(offset)} 0)">${type3Glyph(glyph)}</g>`;
|
|
1019
|
+
content += `<g transform="translate(${number2(offset)} 0)">${type3Glyph(glyph, span.color)}</g>`;
|
|
998
1020
|
offset += glyph.advance;
|
|
999
1021
|
}
|
|
1000
1022
|
return `<g transform="${outer}"><g transform="scale(${number2(xScale)} ${number2(-span.fontSize)})">${content}</g></g>`;
|
|
@@ -1005,18 +1027,19 @@ function isHebrewPaintOrder(span) {
|
|
|
1005
1027
|
function usesSpacingAdjustment(span) {
|
|
1006
1028
|
return !span.fontAssetId && /arial/i.test(span.fontFamily ?? "");
|
|
1007
1029
|
}
|
|
1008
|
-
function type3Glyph(glyph) {
|
|
1030
|
+
function type3Glyph(glyph, textColor) {
|
|
1009
1031
|
let output = "";
|
|
1010
1032
|
for (const fill of glyph.fills ?? []) {
|
|
1011
|
-
|
|
1033
|
+
const color = glyph.usesTextColor && isCssHexColor(textColor) ? textColor : fill.color;
|
|
1034
|
+
if (!isCssHexColor(color)) continue;
|
|
1012
1035
|
const points = fill.points.map(([x, y]) => `${number2(x)},${number2(y)}`).join(" ");
|
|
1013
1036
|
const opacity = isUnitInterval(fill.opacity) ? ` fill-opacity="${number2(fill.opacity)}"` : "";
|
|
1014
|
-
output += `<polygon points="${points}" fill="${
|
|
1037
|
+
output += `<polygon points="${points}" fill="${color}"${opacity}/>`;
|
|
1015
1038
|
}
|
|
1016
1039
|
for (const path of glyph.paths ?? []) {
|
|
1017
1040
|
if (!isSvgPath(path.d)) continue;
|
|
1018
|
-
const fill = isCssHexColor(path.fill) ? path.fill : "none";
|
|
1019
|
-
const stroke = isCssHexColor(path.stroke) ? path.stroke : "none";
|
|
1041
|
+
const fill = glyph.usesTextColor && isCssHexColor(textColor) ? textColor : isCssHexColor(path.fill) ? path.fill : "none";
|
|
1042
|
+
const stroke = glyph.usesTextColor && isCssHexColor(textColor) && path.stroke ? textColor : isCssHexColor(path.stroke) ? path.stroke : "none";
|
|
1020
1043
|
const width = path.strokeWidth !== void 0 && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0 ? ` stroke-width="${number2(path.strokeWidth)}"` : "";
|
|
1021
1044
|
output += `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${width}/>`;
|
|
1022
1045
|
}
|