@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.js
CHANGED
|
@@ -16,7 +16,7 @@ function dominantTextColor(lines) {
|
|
|
16
16
|
}
|
|
17
17
|
return [...counts].sort((left, right) => right[1] - left[1])[0]?.[0] ?? "#000000";
|
|
18
18
|
}
|
|
19
|
-
function semanticTextHtml(text, lines, defaultColor) {
|
|
19
|
+
function semanticTextHtml(text, lines, defaultColor, preserveWeight = true) {
|
|
20
20
|
const ranges = [];
|
|
21
21
|
let cursor = 0;
|
|
22
22
|
for (const span of lines.flatMap((line) => line.spans)) {
|
|
@@ -25,14 +25,25 @@ function semanticTextHtml(text, lines, defaultColor) {
|
|
|
25
25
|
if (start < 0) continue;
|
|
26
26
|
cursor = start + span.text.length;
|
|
27
27
|
const color = normalizedColor(span.color);
|
|
28
|
-
|
|
28
|
+
const bold = preserveWeight && /(?:bold|semibold|demi|medium|medi)/i.test(span.fontFamily ?? "");
|
|
29
|
+
const italic = /(?:italic|oblique|slanted|slant|ital)/i.test(span.fontFamily ?? "");
|
|
30
|
+
const nondefaultColor = color && color !== defaultColor ? color : void 0;
|
|
31
|
+
if (nondefaultColor || bold || italic) {
|
|
32
|
+
ranges.push({
|
|
33
|
+
start,
|
|
34
|
+
end: cursor,
|
|
35
|
+
...nondefaultColor ? { color: nondefaultColor } : {},
|
|
36
|
+
bold,
|
|
37
|
+
italic
|
|
38
|
+
});
|
|
39
|
+
}
|
|
29
40
|
}
|
|
30
41
|
const merged = mergeRanges(ranges, text);
|
|
31
42
|
let html = "";
|
|
32
43
|
let offset = 0;
|
|
33
44
|
for (const range of merged) {
|
|
34
45
|
html += escapeHtml(text.slice(offset, range.start));
|
|
35
|
-
html +=
|
|
46
|
+
html += styledHtml(text.slice(range.start, range.end), range);
|
|
36
47
|
offset = range.end;
|
|
37
48
|
}
|
|
38
49
|
return html + escapeHtml(text.slice(offset));
|
|
@@ -41,7 +52,7 @@ function mergeRanges(ranges, text) {
|
|
|
41
52
|
const merged = [];
|
|
42
53
|
for (const range of ranges) {
|
|
43
54
|
const previous = merged.at(-1);
|
|
44
|
-
if (previous && previous.color === range.color && /^\s*$/.test(text.slice(previous.end, range.start))) {
|
|
55
|
+
if (previous && previous.color === range.color && previous.bold === range.bold && previous.italic === range.italic && /^\s*$/.test(text.slice(previous.end, range.start))) {
|
|
45
56
|
previous.end = range.end;
|
|
46
57
|
} else {
|
|
47
58
|
merged.push({ ...range });
|
|
@@ -49,6 +60,13 @@ function mergeRanges(ranges, text) {
|
|
|
49
60
|
}
|
|
50
61
|
return merged;
|
|
51
62
|
}
|
|
63
|
+
function styledHtml(value, range) {
|
|
64
|
+
let html = escapeHtml(value);
|
|
65
|
+
if (range.color) html = `<span style="color:${range.color}">${html}</span>`;
|
|
66
|
+
if (range.italic) html = `<em>${html}</em>`;
|
|
67
|
+
if (range.bold) html = `<strong>${html}</strong>`;
|
|
68
|
+
return html;
|
|
69
|
+
}
|
|
52
70
|
function normalizedColor(value) {
|
|
53
71
|
if (!value || !/^#[\da-f]{6}$/i.test(value)) return void 0;
|
|
54
72
|
const color = value.toLowerCase();
|
|
@@ -342,7 +360,9 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
342
360
|
await flushPendingParagraph();
|
|
343
361
|
if (employmentOpen && block.type !== "list") await closeEmployment();
|
|
344
362
|
if (!contentStarted && !headerOpen && block.type === "heading" && block.level === 1) {
|
|
345
|
-
await write(
|
|
363
|
+
await write(
|
|
364
|
+
`<header><h1>${semanticTextHtml(block.text, block.lines, defaultColor, false)}</h1>`
|
|
365
|
+
);
|
|
346
366
|
headerOpen = true;
|
|
347
367
|
continue;
|
|
348
368
|
}
|
|
@@ -357,7 +377,7 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
357
377
|
}
|
|
358
378
|
if (block.type === "heading" && !headerHasParagraph && (block.level === 1 || block.text.trimStart().startsWith("#") || block.level === 4 && nextBlock?.type === "paragraph" && isContactBlock(nextBlock))) {
|
|
359
379
|
await write(
|
|
360
|
-
`<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor)}</h${block.level}>`
|
|
380
|
+
`<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor, false)}</h${block.level}>`
|
|
361
381
|
);
|
|
362
382
|
continue;
|
|
363
383
|
}
|
|
@@ -395,7 +415,7 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
395
415
|
const level = contentStarted && block.level === 1 ? 2 : block.level;
|
|
396
416
|
await closeSections(level);
|
|
397
417
|
await write(
|
|
398
|
-
`<section data-level="${level}"><h${level}>${semanticTextHtml(block.text, block.lines, defaultColor)}</h${level}>`
|
|
418
|
+
`<section data-level="${level}"><h${level}>${semanticTextHtml(block.text, block.lines, defaultColor, false)}</h${level}>`
|
|
399
419
|
);
|
|
400
420
|
sectionLevels.push(level);
|
|
401
421
|
continue;
|
|
@@ -564,7 +584,7 @@ function financialSummaryRow(entry, columns) {
|
|
|
564
584
|
}
|
|
565
585
|
function semanticBlockHtml(block, defaultColor = "#000000") {
|
|
566
586
|
if (block.type === "heading")
|
|
567
|
-
return `<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor)}</h${block.level}>`;
|
|
587
|
+
return `<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor, false)}</h${block.level}>`;
|
|
568
588
|
if (block.type === "paragraph")
|
|
569
589
|
return `<p>${semanticTextHtml(block.text, block.lines, defaultColor)}</p>`;
|
|
570
590
|
if (block.type === "preformatted") return `<pre>${escapeHtml3(block.text)}</pre>`;
|
|
@@ -589,7 +609,7 @@ function semanticBlockHtml(block, defaultColor = "#000000") {
|
|
|
589
609
|
return `<section><h3>${escapeHtml3(block.role)}</h3><p>${escapeHtml3(block.organization)}</p><p>${escapeHtml3(block.date)}</p></section>`;
|
|
590
610
|
}
|
|
591
611
|
const tag = block.ordered ? "ol" : "ul";
|
|
592
|
-
return `<${tag}>${block.items.map((item) => `<li>${
|
|
612
|
+
return `<${tag}>${block.items.map((item) => `<li>${semanticTextHtml(item.text, item.lines, defaultColor)}</li>`).join("")}</${tag}>`;
|
|
593
613
|
}
|
|
594
614
|
function semanticBlockY(block) {
|
|
595
615
|
const lines = block.type === "list" ? block.items.flatMap((item) => item.lines) : block.lines;
|
|
@@ -862,7 +882,7 @@ async function writeFlowPage(page, write) {
|
|
|
862
882
|
if (block.type === "table") await write(tableToHtml(block.table));
|
|
863
883
|
else if (block.type === "heading") {
|
|
864
884
|
await write(
|
|
865
|
-
`<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor)}</h${block.level}>`
|
|
885
|
+
`<h${block.level}>${semanticTextHtml(block.text, block.lines, defaultColor, false)}</h${block.level}>`
|
|
866
886
|
);
|
|
867
887
|
} else if (block.type === "paragraph") {
|
|
868
888
|
await write(
|
|
@@ -901,7 +921,9 @@ async function writeFlowPage(page, write) {
|
|
|
901
921
|
} else {
|
|
902
922
|
const tag = block.ordered ? "ol" : "ul";
|
|
903
923
|
await write(`<${tag}>`);
|
|
904
|
-
for (const item of block.items)
|
|
924
|
+
for (const item of block.items) {
|
|
925
|
+
await write(`<li>${semanticTextHtml(item.text, item.lines, defaultColor)}</li>`);
|
|
926
|
+
}
|
|
905
927
|
await write(`</${tag}>`);
|
|
906
928
|
}
|
|
907
929
|
}
|
|
@@ -971,7 +993,7 @@ function visualType3Text(span, font, pageHeight) {
|
|
|
971
993
|
let content = "";
|
|
972
994
|
for (const glyph of sequence) {
|
|
973
995
|
if (!glyph) continue;
|
|
974
|
-
content += `<g transform="translate(${number2(offset)} 0)">${type3Glyph(glyph)}</g>`;
|
|
996
|
+
content += `<g transform="translate(${number2(offset)} 0)">${type3Glyph(glyph, span.color)}</g>`;
|
|
975
997
|
offset += glyph.advance;
|
|
976
998
|
}
|
|
977
999
|
return `<g transform="${outer}"><g transform="scale(${number2(xScale)} ${number2(-span.fontSize)})">${content}</g></g>`;
|
|
@@ -982,18 +1004,19 @@ function isHebrewPaintOrder(span) {
|
|
|
982
1004
|
function usesSpacingAdjustment(span) {
|
|
983
1005
|
return !span.fontAssetId && /arial/i.test(span.fontFamily ?? "");
|
|
984
1006
|
}
|
|
985
|
-
function type3Glyph(glyph) {
|
|
1007
|
+
function type3Glyph(glyph, textColor) {
|
|
986
1008
|
let output = "";
|
|
987
1009
|
for (const fill of glyph.fills ?? []) {
|
|
988
|
-
|
|
1010
|
+
const color = glyph.usesTextColor && isCssHexColor(textColor) ? textColor : fill.color;
|
|
1011
|
+
if (!isCssHexColor(color)) continue;
|
|
989
1012
|
const points = fill.points.map(([x, y]) => `${number2(x)},${number2(y)}`).join(" ");
|
|
990
1013
|
const opacity = isUnitInterval(fill.opacity) ? ` fill-opacity="${number2(fill.opacity)}"` : "";
|
|
991
|
-
output += `<polygon points="${points}" fill="${
|
|
1014
|
+
output += `<polygon points="${points}" fill="${color}"${opacity}/>`;
|
|
992
1015
|
}
|
|
993
1016
|
for (const path of glyph.paths ?? []) {
|
|
994
1017
|
if (!isSvgPath(path.d)) continue;
|
|
995
|
-
const fill = isCssHexColor(path.fill) ? path.fill : "none";
|
|
996
|
-
const stroke = isCssHexColor(path.stroke) ? path.stroke : "none";
|
|
1018
|
+
const fill = glyph.usesTextColor && isCssHexColor(textColor) ? textColor : isCssHexColor(path.fill) ? path.fill : "none";
|
|
1019
|
+
const stroke = glyph.usesTextColor && isCssHexColor(textColor) && path.stroke ? textColor : isCssHexColor(path.stroke) ? path.stroke : "none";
|
|
997
1020
|
const width = path.strokeWidth !== void 0 && Number.isFinite(path.strokeWidth) && path.strokeWidth >= 0 ? ` stroke-width="${number2(path.strokeWidth)}"` : "";
|
|
998
1021
|
output += `<path d="${path.d}" fill="${fill}" stroke="${stroke}"${width}/>`;
|
|
999
1022
|
}
|