@boxpdf/html-writer 0.1.19 → 0.1.22
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/README.md +45 -8
- package/dist/index.cjs +601 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +600 -95
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -22,6 +22,7 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
pageToHtml: () => pageToHtml,
|
|
24
24
|
writeHtmlDocument: () => writeHtmlDocument,
|
|
25
|
+
writeMarkdownDocument: () => writeMarkdownDocument,
|
|
25
26
|
writePage: () => writePage
|
|
26
27
|
});
|
|
27
28
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -218,6 +219,12 @@ function dominantTextColor(lines) {
|
|
|
218
219
|
return [...counts].sort((left, right) => right[1] - left[1])[0]?.[0] ?? "#000000";
|
|
219
220
|
}
|
|
220
221
|
function semanticTextHtml(text, lines, defaultColor, preserveWeight = true) {
|
|
222
|
+
return semanticText(text, lines, defaultColor, preserveWeight, "html");
|
|
223
|
+
}
|
|
224
|
+
function semanticTextMarkdown(text, lines, defaultColor, preserveWeight = true) {
|
|
225
|
+
return semanticText(text, lines, defaultColor, preserveWeight, "markdown");
|
|
226
|
+
}
|
|
227
|
+
function semanticText(text, lines, defaultColor, preserveWeight, format) {
|
|
221
228
|
const ranges = [];
|
|
222
229
|
let cursor = 0;
|
|
223
230
|
for (const span of lines.flatMap((line) => line.spans)) {
|
|
@@ -243,11 +250,11 @@ function semanticTextHtml(text, lines, defaultColor, preserveWeight = true) {
|
|
|
243
250
|
let html = "";
|
|
244
251
|
let offset = 0;
|
|
245
252
|
for (const range of merged) {
|
|
246
|
-
html +=
|
|
247
|
-
html +=
|
|
253
|
+
html += escapeText(text.slice(offset, range.start), format);
|
|
254
|
+
html += styledText(text.slice(range.start, range.end), range, format);
|
|
248
255
|
offset = range.end;
|
|
249
256
|
}
|
|
250
|
-
return html +
|
|
257
|
+
return html + escapeText(text.slice(offset), format);
|
|
251
258
|
}
|
|
252
259
|
function mergeRanges(ranges, text) {
|
|
253
260
|
const merged = [];
|
|
@@ -261,13 +268,19 @@ function mergeRanges(ranges, text) {
|
|
|
261
268
|
}
|
|
262
269
|
return merged;
|
|
263
270
|
}
|
|
264
|
-
function
|
|
265
|
-
let html =
|
|
271
|
+
function styledText(value, range, format) {
|
|
272
|
+
let html = escapeText(value, format);
|
|
266
273
|
if (range.color) html = `<span style="color:${range.color}">${html}</span>`;
|
|
267
|
-
if (range.italic) html = `<em>${html}</em
|
|
268
|
-
if (range.bold) html = `<strong>${html}</strong
|
|
274
|
+
if (range.italic) html = format === "html" ? `<em>${html}</em>` : `_${html}_`;
|
|
275
|
+
if (range.bold) html = format === "html" ? `<strong>${html}</strong>` : `**${html}**`;
|
|
269
276
|
return html;
|
|
270
277
|
}
|
|
278
|
+
function escapeText(value, format) {
|
|
279
|
+
return format === "html" ? escapeHtml(value) : escapeMarkdown(value);
|
|
280
|
+
}
|
|
281
|
+
function escapeMarkdown(value) {
|
|
282
|
+
return value.replace(/([\\`*_[\]<>])/g, "\\$1");
|
|
283
|
+
}
|
|
271
284
|
function normalizedColor(value) {
|
|
272
285
|
if (!value || !/^#[\da-f]{6}$/i.test(value)) return void 0;
|
|
273
286
|
const color = value.toLowerCase();
|
|
@@ -359,17 +372,20 @@ function number(value) {
|
|
|
359
372
|
// src/visual-font.ts
|
|
360
373
|
function visualFontAliases(pageNumber, fonts) {
|
|
361
374
|
return new Map(
|
|
362
|
-
fonts.filter(
|
|
375
|
+
fonts.filter(
|
|
376
|
+
(font) => (font.format === "truetype" || font.format === "opentype") && !/(?:courier|^TTE)/i.test(font.family ?? "")
|
|
377
|
+
).map((font) => [font.id, `boxpdf-${pageNumber}-${font.id}`])
|
|
363
378
|
);
|
|
364
379
|
}
|
|
365
380
|
function visualFontFace(font, aliases) {
|
|
366
|
-
if (font.format !== "truetype") return "";
|
|
381
|
+
if (font.format !== "truetype" && font.format !== "opentype") return "";
|
|
367
382
|
const alias = aliases.get(font.id);
|
|
368
383
|
if (!alias) return "";
|
|
369
384
|
const styles2 = visualFontStyles(font.family, alias).filter(
|
|
370
385
|
(style) => !style.startsWith("font-family:")
|
|
371
386
|
);
|
|
372
|
-
|
|
387
|
+
const mime = font.format === "opentype" ? "font/otf" : "font/ttf";
|
|
388
|
+
return `@font-face{font-family:${alias};src:url(data:${mime};base64,${base64(font.data)}) format("${font.format}");${styles2.join(";")}}`;
|
|
373
389
|
}
|
|
374
390
|
function visualFontStyles(fontFamily, alias) {
|
|
375
391
|
const normalized = fontFamily?.toLowerCase() ?? "";
|
|
@@ -403,23 +419,39 @@ function base64(bytes) {
|
|
|
403
419
|
}
|
|
404
420
|
|
|
405
421
|
// src/semantic-media.ts
|
|
406
|
-
function semanticMedia(page) {
|
|
407
|
-
|
|
408
|
-
output
|
|
422
|
+
function semanticMedia(page, imageOptions = "embedded") {
|
|
423
|
+
if (imageOptions === "excluded") return [];
|
|
424
|
+
const output = (page.images ?? []).map(
|
|
425
|
+
(image, index) => rasterMedia(image, page.number, index, imageOptions)
|
|
426
|
+
);
|
|
427
|
+
output.push(...vectorMedia(page, imageOptions));
|
|
409
428
|
return mediaComponents(output, page).sort((left, right) => right.bounds.y - left.bounds.y);
|
|
410
429
|
}
|
|
411
|
-
function
|
|
430
|
+
async function prepareSemanticMedia(page, imageOptions, onImage) {
|
|
431
|
+
const media = semanticMedia(page, imageOptions);
|
|
432
|
+
for (const item of media) {
|
|
433
|
+
for (const asset of item.assets ?? []) await onImage?.(asset);
|
|
434
|
+
delete item.assets;
|
|
435
|
+
}
|
|
436
|
+
return media;
|
|
437
|
+
}
|
|
438
|
+
function rasterMedia(image, pageNumber, index, imageOptions) {
|
|
412
439
|
const bounds2 = transformedUnitBounds(image.transform);
|
|
413
440
|
const mime = image.format === "jpeg" ? "image/jpeg" : "image/bmp";
|
|
414
441
|
const data = image.format === "jpeg" ? image.data : rgbBmp(image);
|
|
442
|
+
const extension = image.format === "jpeg" ? "jpg" : "bmp";
|
|
443
|
+
const name = `page-${pageNumber}-image-${index + 1}.${extension}`;
|
|
444
|
+
const source = imageOptions === "references" ? name : `data:${mime};base64,${base64(data)}`;
|
|
415
445
|
const opacity = unitInterval(image.opacity) ? `;opacity:${number2(image.opacity)}` : "";
|
|
416
446
|
return {
|
|
417
447
|
bounds: bounds2,
|
|
418
448
|
kind: "raster",
|
|
419
|
-
html: `<img class="pdf-semantic-media" src="
|
|
449
|
+
html: `<img class="pdf-semantic-media" src="${source}" width="${number2(bounds2.width)}" height="${number2(bounds2.height)}" alt="" style="max-width:100%;height:auto${opacity}">`,
|
|
450
|
+
markdown: ``,
|
|
451
|
+
...imageOptions === "references" ? { assets: [{ name, mimeType: mime, data }] } : {}
|
|
420
452
|
};
|
|
421
453
|
}
|
|
422
|
-
function vectorMedia(page) {
|
|
454
|
+
function vectorMedia(page, imageOptions) {
|
|
423
455
|
const primitives = [
|
|
424
456
|
...(page.paths ?? []).flatMap((path, index) => {
|
|
425
457
|
const bounds2 = vectorPathBounds(path);
|
|
@@ -435,9 +467,11 @@ function vectorMedia(page) {
|
|
|
435
467
|
);
|
|
436
468
|
const aliases = visualFontAliases(page.number, page.fonts ?? []);
|
|
437
469
|
const visualCodeFonts = new Set(
|
|
438
|
-
(page.fonts ?? []).filter(
|
|
470
|
+
(page.fonts ?? []).filter(
|
|
471
|
+
(font) => (font.format === "truetype" || font.format === "opentype") && font.visualCodeMapping
|
|
472
|
+
).map((font) => font.id)
|
|
439
473
|
);
|
|
440
|
-
return components.map((component) => {
|
|
474
|
+
return components.map((component, componentIndex) => {
|
|
441
475
|
const bounds2 = component.bounds;
|
|
442
476
|
const paths = component.primitives.flatMap(
|
|
443
477
|
(primitive) => primitive.type === "path" ? [{ path: primitive.value, index: primitive.index }] : []
|
|
@@ -454,10 +488,18 @@ function vectorMedia(page) {
|
|
|
454
488
|
);
|
|
455
489
|
const fontIds = new Set(overlay.map((span) => span.fontAssetId));
|
|
456
490
|
const fontFaces = (page.fonts ?? []).filter((font) => fontIds.has(font.id)).map((font) => visualFontFace(font, aliases)).join("");
|
|
491
|
+
const svg = `<svg class="pdf-semantic-media" xmlns="http://www.w3.org/2000/svg" viewBox="${number2(bounds2.x)} ${number2(page.height - bounds2.y - bounds2.height)} ${number2(bounds2.width)} ${number2(bounds2.height)}" style="display:block;max-width:100%;height:auto" aria-hidden="true">${fontFaces ? `<style>${fontFaces}</style>` : ""}${paths.length ? `<defs>${vectorPathClipDefinitions(paths, page.number)}</defs>` : ""}<g transform="translate(0 ${number2(page.height)}) scale(1 -1)">${fills.map(vectorFillSvg).join("") + paths.map(({ path, index }) => vectorPathSvg(path, page.number, index)).join("")}</g>${overlay.map((span) => vectorText(span, page.height, aliases)).join("")}</svg>`;
|
|
492
|
+
const name = `page-${page.number}-vector-${componentIndex + 1}.svg`;
|
|
457
493
|
return {
|
|
458
494
|
bounds: bounds2,
|
|
459
495
|
kind: "vector",
|
|
460
|
-
html: `<
|
|
496
|
+
html: imageOptions === "references" ? `<img class="pdf-semantic-media" src="${name}" width="${number2(bounds2.width)}" height="${number2(bounds2.height)}" alt="">` : svg,
|
|
497
|
+
markdown: imageOptions === "references" ? `` : svg,
|
|
498
|
+
...imageOptions === "references" ? {
|
|
499
|
+
assets: [
|
|
500
|
+
{ name, mimeType: "image/svg+xml", data: new TextEncoder().encode(svg) }
|
|
501
|
+
]
|
|
502
|
+
} : {},
|
|
461
503
|
...consumedSpans.length > 0 ? { consumedSpans } : {}
|
|
462
504
|
};
|
|
463
505
|
});
|
|
@@ -499,7 +541,9 @@ function compositeMedia(items) {
|
|
|
499
541
|
bounds: bounds2,
|
|
500
542
|
kind: "composite",
|
|
501
543
|
html: `<div class="pdf-semantic-media pdf-semantic-media-composite" style="position:relative;max-width:100%;width:${number2(bounds2.width)}px;aspect-ratio:${number2(bounds2.width)}/${number2(bounds2.height)}">${layers}</div>`,
|
|
502
|
-
|
|
544
|
+
markdown: items.map((item) => item.markdown).join("\n\n"),
|
|
545
|
+
consumedSpans: items.flatMap((item) => item.consumedSpans ?? []),
|
|
546
|
+
assets: items.flatMap((item) => item.assets ?? [])
|
|
503
547
|
};
|
|
504
548
|
}
|
|
505
549
|
function mediaPiecesTouch(left, right) {
|
|
@@ -638,7 +682,7 @@ function escapeHtml2(value) {
|
|
|
638
682
|
}
|
|
639
683
|
|
|
640
684
|
// src/semantic-document.ts
|
|
641
|
-
async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
685
|
+
async function writeSemanticDocument(pages, write, lookaheadPages, imageOptions, onImage, format = "html") {
|
|
642
686
|
const stats = {
|
|
643
687
|
pagesProcessed: 0,
|
|
644
688
|
peakBufferedPages: 0,
|
|
@@ -656,27 +700,30 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
656
700
|
let contentStarted = false;
|
|
657
701
|
let employmentOpen = false;
|
|
658
702
|
let pendingParagraph;
|
|
659
|
-
|
|
703
|
+
const markdown = format === "markdown";
|
|
704
|
+
const output = (html, markdownValue = "") => write(markdown ? markdownValue : html);
|
|
705
|
+
const inlineText = (text, lines, defaultColor, preserveWeight = true) => markdown ? semanticTextMarkdown(text, lines, defaultColor, preserveWeight) : semanticTextHtml(text, lines, defaultColor, preserveWeight);
|
|
706
|
+
await output('<article class="pdf-semantic-document">');
|
|
660
707
|
const closeTable = async () => {
|
|
661
708
|
if (!activeTable) return;
|
|
662
|
-
await
|
|
709
|
+
await output("</table>", "\n");
|
|
663
710
|
activeTable = void 0;
|
|
664
711
|
while (pendingMedia.length > 0) await write(pendingMedia.shift() ?? "");
|
|
665
712
|
};
|
|
666
713
|
const closeSections = async (minimumLevel = 0) => {
|
|
667
714
|
while ((sectionLevels.at(-1) ?? -1) >= minimumLevel && sectionLevels.length > 0) {
|
|
668
|
-
await
|
|
715
|
+
await output("</section>");
|
|
669
716
|
sectionLevels.pop();
|
|
670
717
|
}
|
|
671
718
|
};
|
|
672
719
|
const flushPendingParagraph = async () => {
|
|
673
720
|
if (!pendingParagraph) return;
|
|
674
|
-
await write(
|
|
721
|
+
await write(semanticBlockOutput(pendingParagraph.block, pendingParagraph.defaultColor, format));
|
|
675
722
|
pendingParagraph = void 0;
|
|
676
723
|
};
|
|
677
724
|
const closeEmployment = async () => {
|
|
678
725
|
if (!employmentOpen) return;
|
|
679
|
-
await
|
|
726
|
+
await output("</section>");
|
|
680
727
|
employmentOpen = false;
|
|
681
728
|
};
|
|
682
729
|
const emitPage = async (page, future) => {
|
|
@@ -704,7 +751,11 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
704
751
|
await flushPendingParagraph();
|
|
705
752
|
const item = page.media[mediaIndex];
|
|
706
753
|
if (item && captions.get(block) === item && block.type === "paragraph") {
|
|
707
|
-
const html2 =
|
|
754
|
+
const html2 = markdown ? `${item.markdown}
|
|
755
|
+
|
|
756
|
+
*${inlineText(block.text, block.lines, defaultColor)}*
|
|
757
|
+
|
|
758
|
+
` : `<figure class="pdf-semantic-figure">${item.html}<figcaption>${inlineText(block.text, block.lines, defaultColor)}</figcaption></figure>`;
|
|
708
759
|
if (activeTable) pendingMedia.push(html2);
|
|
709
760
|
else await write(html2);
|
|
710
761
|
emittedMedia.add(item);
|
|
@@ -713,7 +764,9 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
713
764
|
break;
|
|
714
765
|
}
|
|
715
766
|
if (item && captionedMedia.has(item)) break;
|
|
716
|
-
const html =
|
|
767
|
+
const html = markdown ? `${item?.markdown ?? ""}
|
|
768
|
+
|
|
769
|
+
` : `<div class="pdf-semantic-visual">${item?.html}</div>`;
|
|
717
770
|
if (activeTable) pendingMedia.push(html);
|
|
718
771
|
else await write(html);
|
|
719
772
|
mediaIndex += 1;
|
|
@@ -721,7 +774,11 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
721
774
|
const associatedMedia = captions.get(block);
|
|
722
775
|
if (!emittedAsCaption && associatedMedia && block.type === "paragraph") {
|
|
723
776
|
await flushPendingParagraph();
|
|
724
|
-
const html =
|
|
777
|
+
const html = markdown ? `${associatedMedia.markdown}
|
|
778
|
+
|
|
779
|
+
*${inlineText(block.text, block.lines, defaultColor)}*
|
|
780
|
+
|
|
781
|
+
` : `<figure class="pdf-semantic-figure">${associatedMedia.html}<figcaption>${inlineText(block.text, block.lines, defaultColor)}</figcaption></figure>`;
|
|
725
782
|
if (activeTable) pendingMedia.push(html);
|
|
726
783
|
else await write(html);
|
|
727
784
|
emittedMedia.add(associatedMedia);
|
|
@@ -735,8 +792,11 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
735
792
|
await flushPendingParagraph();
|
|
736
793
|
if (employmentOpen && block.type !== "list") await closeEmployment();
|
|
737
794
|
if (!contentStarted && !headerOpen && block.type === "heading" && block.level === 1) {
|
|
738
|
-
await
|
|
739
|
-
`<header><h1>${
|
|
795
|
+
await output(
|
|
796
|
+
`<header><h1>${inlineText(block.text, block.lines, defaultColor, false)}</h1>`,
|
|
797
|
+
`# ${inlineText(block.text, block.lines, defaultColor, false)}
|
|
798
|
+
|
|
799
|
+
`
|
|
740
800
|
);
|
|
741
801
|
headerOpen = true;
|
|
742
802
|
continue;
|
|
@@ -744,19 +804,25 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
744
804
|
if (headerOpen) {
|
|
745
805
|
if (block.type === "paragraph") {
|
|
746
806
|
const tag = isContactBlock(block) ? "address" : "p";
|
|
747
|
-
await
|
|
748
|
-
`<${tag}>${
|
|
807
|
+
await output(
|
|
808
|
+
`<${tag}>${inlineText(block.text, block.lines, defaultColor)}</${tag}>`,
|
|
809
|
+
`${inlineText(block.text, block.lines, defaultColor)}
|
|
810
|
+
|
|
811
|
+
`
|
|
749
812
|
);
|
|
750
813
|
headerHasParagraph = true;
|
|
751
814
|
continue;
|
|
752
815
|
}
|
|
753
816
|
if (block.type === "heading" && !headerHasParagraph && (block.level === 1 || block.text.trimStart().startsWith("#") || block.level === 4 && nextBlock?.type === "paragraph" && isContactBlock(nextBlock))) {
|
|
754
|
-
await
|
|
755
|
-
`<h${block.level}>${
|
|
817
|
+
await output(
|
|
818
|
+
`<h${block.level}>${inlineText(block.text, block.lines, defaultColor, false)}</h${block.level}>`,
|
|
819
|
+
`${"#".repeat(block.level)} ${inlineText(block.text, block.lines, defaultColor, false)}
|
|
820
|
+
|
|
821
|
+
`
|
|
756
822
|
);
|
|
757
823
|
continue;
|
|
758
824
|
}
|
|
759
|
-
await
|
|
825
|
+
await output("</header>");
|
|
760
826
|
headerOpen = false;
|
|
761
827
|
contentStarted = true;
|
|
762
828
|
}
|
|
@@ -764,23 +830,34 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
764
830
|
const rows = (0, import_structure.tableToRows)(block.table);
|
|
765
831
|
if (activeTable && tablesContinue(activeTable.table, block.table, page.width)) {
|
|
766
832
|
const continuationRows = sameRow(activeTable.header, rows[0]) ? rows.slice(1) : rows;
|
|
767
|
-
for (const row of continuationRows)
|
|
833
|
+
for (const row of continuationRows)
|
|
834
|
+
await write(markdown ? markdownTableRow(row) : tableRow(row, false));
|
|
768
835
|
activeTable.table = block.table;
|
|
769
836
|
stats.mergedTables += 1;
|
|
770
837
|
continue;
|
|
771
838
|
}
|
|
772
839
|
await closeTable();
|
|
773
840
|
const header = tableHeader(rows);
|
|
774
|
-
await
|
|
775
|
-
|
|
776
|
-
|
|
841
|
+
await output("<table>", markdownTableStart(rows, header));
|
|
842
|
+
const markdownRows = markdown ? header ? rows.slice(1) : rows : rows;
|
|
843
|
+
for (const [index, row] of markdownRows.entries())
|
|
844
|
+
await write(
|
|
845
|
+
markdown ? markdownTableRow(row) : tableRow(row, Boolean(header && index === 0))
|
|
846
|
+
);
|
|
777
847
|
activeTable = { table: block.table, header };
|
|
778
848
|
continue;
|
|
779
849
|
}
|
|
780
850
|
if (activeTable && block.type === "definitionList" && isFinancialSummary(block)) {
|
|
781
851
|
const columns = activeTable.table.columns.length;
|
|
782
|
-
await
|
|
783
|
-
`<tfoot>${block.entries.map((entry) => financialSummaryRow(entry, columns)).join("")}</tfoot
|
|
852
|
+
await output(
|
|
853
|
+
`<tfoot>${block.entries.map((entry) => financialSummaryRow(entry, columns)).join("")}</tfoot>`,
|
|
854
|
+
block.entries.map(
|
|
855
|
+
(entry) => markdownTableRow([
|
|
856
|
+
entry.term,
|
|
857
|
+
...Array(Math.max(0, columns - 2)).fill(""),
|
|
858
|
+
entry.description
|
|
859
|
+
])
|
|
860
|
+
).join("")
|
|
784
861
|
);
|
|
785
862
|
await closeTable();
|
|
786
863
|
continue;
|
|
@@ -789,8 +866,11 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
789
866
|
if (block.type === "heading") {
|
|
790
867
|
const level = contentStarted && block.level === 1 ? 2 : block.level;
|
|
791
868
|
await closeSections(level);
|
|
792
|
-
await
|
|
793
|
-
`<section data-level="${level}"><h${level}>${
|
|
869
|
+
await output(
|
|
870
|
+
`<section data-level="${level}"><h${level}>${inlineText(block.text, block.lines, defaultColor, false)}</h${level}>`,
|
|
871
|
+
`${"#".repeat(level)} ${inlineText(block.text, block.lines, defaultColor, false)}
|
|
872
|
+
|
|
873
|
+
`
|
|
794
874
|
);
|
|
795
875
|
sectionLevels.push(level);
|
|
796
876
|
continue;
|
|
@@ -798,13 +878,25 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
798
878
|
if (block.type === "paragraph") {
|
|
799
879
|
if (isTitledRecord(block)) {
|
|
800
880
|
const [institution, ...details] = block.lines;
|
|
801
|
-
if (institution)
|
|
802
|
-
|
|
881
|
+
if (institution)
|
|
882
|
+
await output(
|
|
883
|
+
`<h3>${escapeHtml3(institution.text)}</h3>`,
|
|
884
|
+
`### ${escapeMarkdown2(institution.text)}
|
|
885
|
+
|
|
886
|
+
`
|
|
887
|
+
);
|
|
888
|
+
for (const detail of details)
|
|
889
|
+
await output(`<p>${escapeHtml3(detail.text)}</p>`, `${escapeMarkdown2(detail.text)}
|
|
890
|
+
|
|
891
|
+
`);
|
|
803
892
|
continue;
|
|
804
893
|
}
|
|
805
894
|
if (isUnmarkedList(block)) {
|
|
806
|
-
await
|
|
807
|
-
`<ul>${block.lines.map((line) => `<li>${escapeHtml3(line.text)}</li>`).join("")}</ul
|
|
895
|
+
await output(
|
|
896
|
+
`<ul>${block.lines.map((line) => `<li>${escapeHtml3(line.text)}</li>`).join("")}</ul>`,
|
|
897
|
+
`${block.lines.map((line) => `- ${escapeMarkdown2(line.text)}`).join("\n")}
|
|
898
|
+
|
|
899
|
+
`
|
|
808
900
|
);
|
|
809
901
|
continue;
|
|
810
902
|
}
|
|
@@ -812,19 +904,28 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
812
904
|
continue;
|
|
813
905
|
}
|
|
814
906
|
if (block.type === "employment") {
|
|
815
|
-
await
|
|
816
|
-
`<section><h3>${escapeHtml3(block.role)}</h3><p>${escapeHtml3(block.organization)}</p><p>${escapeHtml3(block.date)}</p
|
|
907
|
+
await output(
|
|
908
|
+
`<section><h3>${escapeHtml3(block.role)}</h3><p>${escapeHtml3(block.organization)}</p><p>${escapeHtml3(block.date)}</p>`,
|
|
909
|
+
`### ${escapeMarkdown2(block.role)}
|
|
910
|
+
|
|
911
|
+
${escapeMarkdown2(block.organization)}
|
|
912
|
+
|
|
913
|
+
${escapeMarkdown2(block.date)}
|
|
914
|
+
|
|
915
|
+
`
|
|
817
916
|
);
|
|
818
917
|
employmentOpen = true;
|
|
819
918
|
continue;
|
|
820
919
|
}
|
|
821
|
-
await write(
|
|
920
|
+
await write(semanticBlockOutput(block, defaultColor, format));
|
|
822
921
|
}
|
|
823
922
|
while (mediaIndex < page.media.length) {
|
|
824
923
|
const item = page.media[mediaIndex];
|
|
825
924
|
if (item && !emittedMedia.has(item)) {
|
|
826
925
|
await flushPendingParagraph();
|
|
827
|
-
const html =
|
|
926
|
+
const html = markdown ? `${item.markdown}
|
|
927
|
+
|
|
928
|
+
` : `<div class="pdf-semantic-visual">${item.html}</div>`;
|
|
828
929
|
if (activeTable) pendingMedia.push(html);
|
|
829
930
|
else await write(html);
|
|
830
931
|
}
|
|
@@ -833,9 +934,10 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
833
934
|
for (const signature of marginSignatures(page)) seenFurniture.add(signature);
|
|
834
935
|
};
|
|
835
936
|
for await (const page of pages) {
|
|
836
|
-
const media =
|
|
937
|
+
const media = await prepareSemanticMedia(page, imageOptions, onImage);
|
|
837
938
|
const structured = (0, import_structure.structurePage)(withoutSemanticMediaSpans(page, media));
|
|
838
939
|
buffer.push({ width: page.width, height: page.height, structured, media });
|
|
940
|
+
restoreObservedHyphens(buffer);
|
|
839
941
|
stats.pagesProcessed += 1;
|
|
840
942
|
stats.peakBufferedPages = Math.max(stats.peakBufferedPages, buffer.length);
|
|
841
943
|
stats.peakBufferedLines = Math.max(
|
|
@@ -851,22 +953,82 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
851
953
|
const ready = buffer.shift();
|
|
852
954
|
if (ready) await emitPage(ready, buffer);
|
|
853
955
|
}
|
|
854
|
-
if (headerOpen) await
|
|
956
|
+
if (headerOpen) await output("</header>");
|
|
855
957
|
await closeTable();
|
|
856
958
|
await closeEmployment();
|
|
857
959
|
if (pendingParagraph && isFooterParagraph(pendingParagraph.block, pendingParagraph.height)) {
|
|
858
960
|
await closeSections();
|
|
859
|
-
await
|
|
860
|
-
`<footer>${semanticBlockHtml(pendingParagraph.block, pendingParagraph.defaultColor)}</footer
|
|
961
|
+
await output(
|
|
962
|
+
`<footer>${semanticBlockHtml(pendingParagraph.block, pendingParagraph.defaultColor)}</footer>`,
|
|
963
|
+
`---
|
|
964
|
+
|
|
965
|
+
${semanticBlockMarkdown(pendingParagraph.block, pendingParagraph.defaultColor)}`
|
|
861
966
|
);
|
|
862
967
|
pendingParagraph = void 0;
|
|
863
968
|
} else {
|
|
864
969
|
await flushPendingParagraph();
|
|
865
970
|
await closeSections();
|
|
866
971
|
}
|
|
867
|
-
await
|
|
972
|
+
await output("</article>");
|
|
868
973
|
return stats;
|
|
869
974
|
}
|
|
975
|
+
function restoreObservedHyphens(buffer) {
|
|
976
|
+
const terms = new Set(
|
|
977
|
+
buffer.flatMap(
|
|
978
|
+
(page) => page.structured.lines.flatMap(
|
|
979
|
+
(line) => line.text.match(/[\p{L}\p{N}]+(?:[-‐‑][\p{L}\p{N}]+)+/gu) ?? []
|
|
980
|
+
)
|
|
981
|
+
)
|
|
982
|
+
);
|
|
983
|
+
for (const page of buffer) {
|
|
984
|
+
for (const block of page.structured.blocks) restoreBlockHyphens(block, terms);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
function restoreBlockHyphens(block, terms) {
|
|
988
|
+
const restore = (value) => restoreTextHyphens(value, terms);
|
|
989
|
+
if (block.type === "insetGroup") {
|
|
990
|
+
for (const nested of block.blocks) restoreBlockHyphens(nested, terms);
|
|
991
|
+
} else if (block.type === "heading" || block.type === "paragraph" || block.type === "preformatted") {
|
|
992
|
+
block.text = restore(block.text);
|
|
993
|
+
} else if (block.type === "list") {
|
|
994
|
+
for (const item of block.items) item.text = restore(item.text);
|
|
995
|
+
} else if (block.type === "definitionList") {
|
|
996
|
+
for (const entry of block.entries) {
|
|
997
|
+
entry.term = restore(entry.term);
|
|
998
|
+
entry.description = restore(entry.description);
|
|
999
|
+
}
|
|
1000
|
+
} else if (block.type === "cardList") {
|
|
1001
|
+
for (const item of block.items) {
|
|
1002
|
+
item.title = restore(item.title);
|
|
1003
|
+
item.details = item.details.map(restore);
|
|
1004
|
+
}
|
|
1005
|
+
} else if (block.type === "sectionGroup") {
|
|
1006
|
+
for (const item of block.items) {
|
|
1007
|
+
item.label = restore(item.label);
|
|
1008
|
+
item.content = item.content.map(restore);
|
|
1009
|
+
}
|
|
1010
|
+
} else if (block.type === "employment") {
|
|
1011
|
+
block.role = restore(block.role);
|
|
1012
|
+
block.organization = restore(block.organization);
|
|
1013
|
+
block.date = restore(block.date);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
function restoreTextHyphens(value, terms) {
|
|
1017
|
+
let output = value;
|
|
1018
|
+
for (const term of terms) {
|
|
1019
|
+
const collapsed = term.replace(/[-‐‑]/gu, "");
|
|
1020
|
+
if (collapsed === term || !output.includes(collapsed)) continue;
|
|
1021
|
+
const pattern = new RegExp(
|
|
1022
|
+
`(?<![\\p{L}\\p{N}])${escapeRegularExpression(collapsed)}(?![\\p{L}\\p{N}])`,
|
|
1023
|
+
"gu"
|
|
1024
|
+
);
|
|
1025
|
+
output = output.replace(pattern, term);
|
|
1026
|
+
}
|
|
1027
|
+
return output;
|
|
1028
|
+
}
|
|
1029
|
+
function escapeRegularExpression(value) {
|
|
1030
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1031
|
+
}
|
|
870
1032
|
function isContactBlock(block) {
|
|
871
1033
|
const text = block.text;
|
|
872
1034
|
const signals = [
|
|
@@ -950,6 +1112,20 @@ function tableRow(row, header) {
|
|
|
950
1112
|
const cell = header ? "th" : "td";
|
|
951
1113
|
return `<tr>${row.map((value) => `<${cell}>${escapeHtml3(value)}</${cell}>`).join("")}</tr>`;
|
|
952
1114
|
}
|
|
1115
|
+
function markdownTableStart(rows, header) {
|
|
1116
|
+
const columns = rows[0]?.length ?? 0;
|
|
1117
|
+
if (columns === 0) return "";
|
|
1118
|
+
const heading = header ?? Array(columns).fill("");
|
|
1119
|
+
return `${markdownTableRow(heading)}${markdownTableRow(Array(columns).fill("---"), false)}`;
|
|
1120
|
+
}
|
|
1121
|
+
function markdownTableRow(row, shouldEscape = true) {
|
|
1122
|
+
const cells = row.map((value) => shouldEscape ? escapeMarkdownTableCell(value) : value);
|
|
1123
|
+
return `| ${cells.join(" | ")} |
|
|
1124
|
+
`;
|
|
1125
|
+
}
|
|
1126
|
+
function escapeMarkdownTableCell(value) {
|
|
1127
|
+
return escapeMarkdown2(value).replaceAll("|", "\\|").replace(/\s*\n\s*/g, "<br>");
|
|
1128
|
+
}
|
|
953
1129
|
function isFinancialSummary(block) {
|
|
954
1130
|
return block.entries.length > 0 && block.entries.every((entry) => isNumericValue(entry.description)) && block.entries.some((entry) => /\p{Sc}|%/u.test(entry.description));
|
|
955
1131
|
}
|
|
@@ -997,6 +1173,90 @@ function semanticBlockHtml(block, defaultColor = "#000000") {
|
|
|
997
1173
|
const tag = block.ordered ? "ol" : "ul";
|
|
998
1174
|
return `<${tag}>${block.items.map((item) => `<li>${semanticTextHtml(item.text, item.lines, defaultColor)}</li>`).join("")}</${tag}>`;
|
|
999
1175
|
}
|
|
1176
|
+
function semanticBlockOutput(block, defaultColor, format) {
|
|
1177
|
+
return format === "markdown" ? semanticBlockMarkdown(block, defaultColor) : semanticBlockHtml(block, defaultColor);
|
|
1178
|
+
}
|
|
1179
|
+
function semanticBlockMarkdown(block, defaultColor = "#000000") {
|
|
1180
|
+
if (block.type === "insetGroup") {
|
|
1181
|
+
const content = block.blocks.map((item) => semanticBlockMarkdown(item, defaultColor)).join("");
|
|
1182
|
+
return `${content.trimEnd().split("\n").map((line) => line ? `> ${line}` : ">").join("\n")}
|
|
1183
|
+
|
|
1184
|
+
`;
|
|
1185
|
+
}
|
|
1186
|
+
if (block.type === "table") {
|
|
1187
|
+
const rows = (0, import_structure.tableToRows)(block.table);
|
|
1188
|
+
const header = tableHeader(rows);
|
|
1189
|
+
return `${markdownTableStart(rows, header)}${(header ? rows.slice(1) : rows).map((row) => markdownTableRow(row)).join("")}
|
|
1190
|
+
`;
|
|
1191
|
+
}
|
|
1192
|
+
if (block.type === "heading") {
|
|
1193
|
+
return `${"#".repeat(block.level)} ${semanticTextMarkdown(block.text, block.lines, defaultColor, false)}
|
|
1194
|
+
|
|
1195
|
+
`;
|
|
1196
|
+
}
|
|
1197
|
+
if (block.type === "paragraph") {
|
|
1198
|
+
return `${semanticTextMarkdown(block.text, block.lines, defaultColor)}
|
|
1199
|
+
|
|
1200
|
+
`;
|
|
1201
|
+
}
|
|
1202
|
+
if (block.type === "preformatted") {
|
|
1203
|
+
const fence = block.text.includes("```") ? "````" : "```";
|
|
1204
|
+
return `${fence}
|
|
1205
|
+
${block.text}
|
|
1206
|
+
${fence}
|
|
1207
|
+
|
|
1208
|
+
`;
|
|
1209
|
+
}
|
|
1210
|
+
if (block.type === "definitionList") {
|
|
1211
|
+
return `${block.entries.map((entry) => `**${escapeMarkdown2(entry.term)}:** ${escapeMarkdown2(entry.description)}`).join("\n\n")}
|
|
1212
|
+
|
|
1213
|
+
`;
|
|
1214
|
+
}
|
|
1215
|
+
if (block.type === "cardList") {
|
|
1216
|
+
const rows = [
|
|
1217
|
+
["Item", "Quantity", "Amount"],
|
|
1218
|
+
...block.items.map((item) => {
|
|
1219
|
+
const trailing = item.details.at(-1) ?? "";
|
|
1220
|
+
const match = /^\s*[×x]\s*(\d+)\s+(.+)$/u.exec(trailing);
|
|
1221
|
+
const detail = item.details.slice(0, -1).join(" ");
|
|
1222
|
+
return [
|
|
1223
|
+
`${item.title}${detail ? ` \u2014 ${detail}` : ""}`,
|
|
1224
|
+
match?.[1] ?? "",
|
|
1225
|
+
match?.[2] ?? trailing
|
|
1226
|
+
];
|
|
1227
|
+
})
|
|
1228
|
+
];
|
|
1229
|
+
return `## Items ordered
|
|
1230
|
+
|
|
1231
|
+
${markdownTableStart(rows, rows[0])}${rows.slice(1).map((row) => markdownTableRow(row)).join("")}
|
|
1232
|
+
`;
|
|
1233
|
+
}
|
|
1234
|
+
if (block.type === "sectionGroup") {
|
|
1235
|
+
return block.items.map(
|
|
1236
|
+
(item) => `## ${escapeMarkdown2(titleCase(item.label))}
|
|
1237
|
+
|
|
1238
|
+
${item.content.map(
|
|
1239
|
+
(content, index) => index === 0 ? `**${escapeMarkdown2(content)}**` : escapeMarkdown2(content)
|
|
1240
|
+
).join("\n\n")}
|
|
1241
|
+
|
|
1242
|
+
`
|
|
1243
|
+
).join("");
|
|
1244
|
+
}
|
|
1245
|
+
if (block.type === "employment") {
|
|
1246
|
+
return `### ${escapeMarkdown2(block.role)}
|
|
1247
|
+
|
|
1248
|
+
${escapeMarkdown2(block.organization)}
|
|
1249
|
+
|
|
1250
|
+
${escapeMarkdown2(block.date)}
|
|
1251
|
+
|
|
1252
|
+
`;
|
|
1253
|
+
}
|
|
1254
|
+
return `${block.items.map(
|
|
1255
|
+
(item, index) => `${block.ordered ? `${index + 1}.` : "-"} ${semanticTextMarkdown(item.text, item.lines, defaultColor)}`
|
|
1256
|
+
).join("\n")}
|
|
1257
|
+
|
|
1258
|
+
`;
|
|
1259
|
+
}
|
|
1000
1260
|
function semanticBlockY(block) {
|
|
1001
1261
|
const lines = block.type === "list" ? block.items.flatMap((item) => item.lines) : block.lines;
|
|
1002
1262
|
return Math.max(...lines.map((line) => line.bounds.y + line.bounds.height));
|
|
@@ -1029,6 +1289,9 @@ function titleCase(value) {
|
|
|
1029
1289
|
function escapeHtml3(value) {
|
|
1030
1290
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1031
1291
|
}
|
|
1292
|
+
function escapeMarkdown2(value) {
|
|
1293
|
+
return value.replace(/([\\`*_[\]<>])/g, "\\$1");
|
|
1294
|
+
}
|
|
1032
1295
|
|
|
1033
1296
|
// src/index.ts
|
|
1034
1297
|
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}`;
|
|
@@ -1045,16 +1308,40 @@ async function writeHtmlDocument(pages, write, options = {}) {
|
|
|
1045
1308
|
await write("</head><body>");
|
|
1046
1309
|
}
|
|
1047
1310
|
await write('<main class="pdf-document">');
|
|
1048
|
-
|
|
1311
|
+
const profile = resolveProfile(options);
|
|
1312
|
+
const imageOptions = resolveImageOptions(profile, options);
|
|
1313
|
+
validateImageOptions(imageOptions, options);
|
|
1314
|
+
if (profile === "semantic") {
|
|
1049
1315
|
const lookahead = semanticLookahead(options.semanticLookaheadPages);
|
|
1050
|
-
const stats = await writeSemanticDocument(
|
|
1316
|
+
const stats = await writeSemanticDocument(
|
|
1317
|
+
pages,
|
|
1318
|
+
write,
|
|
1319
|
+
lookahead,
|
|
1320
|
+
imageOptions,
|
|
1321
|
+
options.onImage
|
|
1322
|
+
);
|
|
1051
1323
|
options.onSemanticStats?.(stats);
|
|
1052
1324
|
} else {
|
|
1053
|
-
|
|
1325
|
+
const documentFonts = { entries: [] };
|
|
1326
|
+
for await (const page of pages) await writePositionedPage(page, write, options, documentFonts);
|
|
1054
1327
|
}
|
|
1055
1328
|
await write("</main>");
|
|
1056
1329
|
if (includeDocument) await write("</body></html>");
|
|
1057
1330
|
}
|
|
1331
|
+
async function writeMarkdownDocument(pages, write, options = {}) {
|
|
1332
|
+
const imageOptions = options.imageOptions ?? "excluded";
|
|
1333
|
+
validateImageOptions(imageOptions, options);
|
|
1334
|
+
const lookahead = semanticLookahead(options.semanticLookaheadPages);
|
|
1335
|
+
const stats = await writeSemanticDocument(
|
|
1336
|
+
pages,
|
|
1337
|
+
write,
|
|
1338
|
+
lookahead,
|
|
1339
|
+
imageOptions,
|
|
1340
|
+
options.onImage,
|
|
1341
|
+
"markdown"
|
|
1342
|
+
);
|
|
1343
|
+
options.onSemanticStats?.(stats);
|
|
1344
|
+
}
|
|
1058
1345
|
function semanticLookahead(value) {
|
|
1059
1346
|
const lookahead = value ?? 4;
|
|
1060
1347
|
if (!Number.isSafeInteger(lookahead) || lookahead < 1 || lookahead > 16) {
|
|
@@ -1063,7 +1350,9 @@ function semanticLookahead(value) {
|
|
|
1063
1350
|
return lookahead;
|
|
1064
1351
|
}
|
|
1065
1352
|
async function writePage(page, write, options = {}) {
|
|
1066
|
-
|
|
1353
|
+
const profile = resolveProfile(options);
|
|
1354
|
+
validateImageOptions(resolveImageOptions(profile, options), options);
|
|
1355
|
+
if (profile === "semantic") await writeFlowPage(page, write, options);
|
|
1067
1356
|
else await writePositionedPage(page, write, options);
|
|
1068
1357
|
}
|
|
1069
1358
|
async function pageToHtml(page, options = {}) {
|
|
@@ -1077,8 +1366,10 @@ async function pageToHtml(page, options = {}) {
|
|
|
1077
1366
|
);
|
|
1078
1367
|
return output;
|
|
1079
1368
|
}
|
|
1080
|
-
async function writePositionedPage(page, write, options) {
|
|
1081
|
-
const
|
|
1369
|
+
async function writePositionedPage(page, write, options, documentFonts) {
|
|
1370
|
+
const imageOptions = resolveImageOptions("visual", options);
|
|
1371
|
+
const visualImages = await prepareVisualImages(page, imageOptions, options.onImage);
|
|
1372
|
+
const visualSpans = coalesceVisualSpans(page.visualSpans ?? page.spans);
|
|
1082
1373
|
const reflectedOverlay = usesReflectedVisualOverlay(page, visualSpans);
|
|
1083
1374
|
const quarterTurn = page.rotate === 90 || page.rotate === 270;
|
|
1084
1375
|
const displayWidth = quarterTurn ? page.height : page.width;
|
|
@@ -1086,29 +1377,40 @@ async function writePositionedPage(page, write, options) {
|
|
|
1086
1377
|
await write(
|
|
1087
1378
|
`<section class="pdf-page pdf-page--visual pdf-page--positioned" data-page="${page.number}" data-rotate="${page.rotate}" style="width:${number3(displayWidth)}pt;height:${number3(displayHeight)}pt">`
|
|
1088
1379
|
);
|
|
1089
|
-
const
|
|
1380
|
+
const { aliases: fontAliases, fontsToEmit } = visualPageFonts(
|
|
1381
|
+
page.number,
|
|
1382
|
+
page.fonts ?? [],
|
|
1383
|
+
documentFonts
|
|
1384
|
+
);
|
|
1090
1385
|
const type3Fonts = new Map(
|
|
1091
1386
|
(page.fonts ?? []).filter((font) => font.format === "type3").map((font) => [font.id, font])
|
|
1092
1387
|
);
|
|
1093
|
-
|
|
1388
|
+
const textClasses = options.includeStyles ?? true ? visualTextClasses(page.number, visualSpans, fontAliases) : void 0;
|
|
1389
|
+
if ((options.includeStyles ?? true) && fontsToEmit.length) {
|
|
1094
1390
|
await write(
|
|
1095
|
-
`<style>${
|
|
1391
|
+
`<style>${fontsToEmit.map((font) => visualFontFace(font, fontAliases)).join("")}</style>`
|
|
1096
1392
|
);
|
|
1097
1393
|
}
|
|
1394
|
+
if (textClasses?.css) await write(`<style>${textClasses.css}</style>`);
|
|
1098
1395
|
await write(
|
|
1099
1396
|
`<div class="pdf-page-content pdf-page-content--${page.rotate}" style="width:${number3(page.width)}pt;height:${number3(page.height)}pt${rotationTransform(page)}">`
|
|
1100
1397
|
);
|
|
1101
1398
|
await write(
|
|
1102
1399
|
`<svg class="pdf-visual-text" xmlns="http://www.w3.org/2000/svg" width="${number3(page.width)}pt" height="${number3(page.height)}pt" viewBox="0 0 ${number3(page.width)} ${number3(page.height)}">`
|
|
1103
1400
|
);
|
|
1104
|
-
const clipDefinitions = imageClipDefinitions(
|
|
1401
|
+
const clipDefinitions = imageClipDefinitions(
|
|
1402
|
+
imageOptions === "excluded" ? [] : page.images ?? [],
|
|
1403
|
+
page.number,
|
|
1404
|
+
page.height
|
|
1405
|
+
) + vectorPathClipDefinitions(
|
|
1105
1406
|
(page.paths ?? []).map((path, index) => ({ path, index })),
|
|
1106
1407
|
page.number
|
|
1107
1408
|
);
|
|
1108
1409
|
if (clipDefinitions) await write(`<defs>${clipDefinitions}</defs>`);
|
|
1109
1410
|
if (reflectedOverlay) {
|
|
1110
1411
|
for (const [index, image] of (page.images ?? []).entries()) {
|
|
1111
|
-
|
|
1412
|
+
const source = visualImages[index];
|
|
1413
|
+
if (source) await write(visualImage(image, page.height, page.number, index, source));
|
|
1112
1414
|
}
|
|
1113
1415
|
}
|
|
1114
1416
|
if (page.fills?.length || page.paths?.length) {
|
|
@@ -1121,14 +1423,29 @@ async function writePositionedPage(page, write, options) {
|
|
|
1121
1423
|
}
|
|
1122
1424
|
if (!reflectedOverlay) {
|
|
1123
1425
|
for (const [index, image] of (page.images ?? []).entries()) {
|
|
1124
|
-
|
|
1426
|
+
const source = visualImages[index];
|
|
1427
|
+
if (source) await write(visualImage(image, page.height, page.number, index, source));
|
|
1125
1428
|
}
|
|
1126
1429
|
}
|
|
1127
|
-
for (
|
|
1430
|
+
for (let spanIndex = 0; spanIndex < visualSpans.length; spanIndex += 1) {
|
|
1431
|
+
const span = visualSpans[spanIndex];
|
|
1432
|
+
if (!span) continue;
|
|
1128
1433
|
if (!usesPositionedSpan(span)) {
|
|
1129
1434
|
const type3 = span.fontAssetId ? type3Fonts.get(span.fontAssetId) : void 0;
|
|
1435
|
+
const line = !type3 && textClasses ? visualTextLine(visualSpans, spanIndex, textClasses.names, page.height, fontAliases) : void 0;
|
|
1436
|
+
if (line) {
|
|
1437
|
+
await write(line.html);
|
|
1438
|
+
spanIndex = line.endIndex;
|
|
1439
|
+
continue;
|
|
1440
|
+
}
|
|
1130
1441
|
await write(
|
|
1131
|
-
type3 ? visualType3Text(span, type3, page.height) : visualText(
|
|
1442
|
+
type3 ? visualType3Text(span, type3, page.height) : visualText(
|
|
1443
|
+
span,
|
|
1444
|
+
page.height,
|
|
1445
|
+
fontAliases,
|
|
1446
|
+
reflectedOverlay && page.rotate === 180,
|
|
1447
|
+
textClasses?.names
|
|
1448
|
+
)
|
|
1132
1449
|
);
|
|
1133
1450
|
}
|
|
1134
1451
|
}
|
|
@@ -1138,23 +1455,191 @@ async function writePositionedPage(page, write, options) {
|
|
|
1138
1455
|
}
|
|
1139
1456
|
await write("</div></section>");
|
|
1140
1457
|
}
|
|
1458
|
+
function visualPageFonts(pageNumber, fonts, documentFonts) {
|
|
1459
|
+
const aliases = visualFontAliases(pageNumber, fonts);
|
|
1460
|
+
if (!documentFonts) return { aliases, fontsToEmit: fonts };
|
|
1461
|
+
const fontsToEmit = [];
|
|
1462
|
+
for (const font of fonts) {
|
|
1463
|
+
if (!aliases.has(font.id) || font.format === "type3") continue;
|
|
1464
|
+
const existing = documentFonts.entries.find(
|
|
1465
|
+
(entry) => entry.format === font.format && equalBytes(entry.data, font.data)
|
|
1466
|
+
);
|
|
1467
|
+
if (existing) {
|
|
1468
|
+
aliases.set(font.id, existing.alias);
|
|
1469
|
+
continue;
|
|
1470
|
+
}
|
|
1471
|
+
const alias = `boxpdf-document-font-${documentFonts.entries.length + 1}`;
|
|
1472
|
+
aliases.set(font.id, alias);
|
|
1473
|
+
documentFonts.entries.push({ alias, data: font.data, format: font.format });
|
|
1474
|
+
fontsToEmit.push(font);
|
|
1475
|
+
}
|
|
1476
|
+
return { aliases, fontsToEmit };
|
|
1477
|
+
}
|
|
1478
|
+
function equalBytes(left, right) {
|
|
1479
|
+
if (left.length !== right.length) return false;
|
|
1480
|
+
return left.every((value, index) => value === right[index]);
|
|
1481
|
+
}
|
|
1482
|
+
function coalesceVisualSpans(spans) {
|
|
1483
|
+
const output = [];
|
|
1484
|
+
for (const span of spans) {
|
|
1485
|
+
const previous = output.at(-1);
|
|
1486
|
+
if (!previous || !canCoalesceVisualSpans(previous, span)) {
|
|
1487
|
+
output.push(span);
|
|
1488
|
+
continue;
|
|
1489
|
+
}
|
|
1490
|
+
output[output.length - 1] = {
|
|
1491
|
+
...previous,
|
|
1492
|
+
text: previous.text + span.text,
|
|
1493
|
+
bounds: {
|
|
1494
|
+
...previous.bounds,
|
|
1495
|
+
width: span.bounds.x + span.bounds.width - previous.bounds.x,
|
|
1496
|
+
height: Math.max(previous.bounds.height, span.bounds.height)
|
|
1497
|
+
}
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
return output;
|
|
1501
|
+
}
|
|
1502
|
+
function canCoalesceVisualSpans(left, right) {
|
|
1503
|
+
if (usesPositionedSpan(left) || usesPositionedSpan(right)) return false;
|
|
1504
|
+
if (left.direction !== "ltr" || right.direction !== "ltr") return false;
|
|
1505
|
+
if (/guardian/i.test(left.fontFamily ?? "")) return false;
|
|
1506
|
+
if (left.glyphCodes || right.glyphCodes) return false;
|
|
1507
|
+
if (left.fontAssetId || right.fontAssetId) return false;
|
|
1508
|
+
if (!sameVisualTextState(left, right)) return false;
|
|
1509
|
+
const tolerance = Math.max(0.02, left.fontSize * 0.015);
|
|
1510
|
+
if (Math.abs(left.bounds.y - right.bounds.y) > tolerance) return false;
|
|
1511
|
+
const gap = right.bounds.x - (left.bounds.x + left.bounds.width);
|
|
1512
|
+
if (right.hasLeadingSpace) return false;
|
|
1513
|
+
if (gap >= -tolerance && gap <= tolerance) return true;
|
|
1514
|
+
return right.textAdjustmentBefore !== void 0 && right.textAdjustmentBefore < 0 && Math.abs(right.textAdjustmentBefore - gap) <= 1e-3;
|
|
1515
|
+
}
|
|
1516
|
+
function sameVisualTextState(left, right) {
|
|
1517
|
+
return Math.abs(left.fontSize - right.fontSize) <= 1e-3 && left.fontName === right.fontName && left.fontFamily === right.fontFamily && left.fontAssetId === right.fontAssetId && left.color === right.color && left.fillOpacity === right.fillOpacity && left.strokeColor === right.strokeColor && left.strokeWidth === right.strokeWidth && left.strokeOpacity === right.strokeOpacity && left.renderingMode === right.renderingMode && sameTransform(left.transform, right.transform);
|
|
1518
|
+
}
|
|
1519
|
+
function sameTransform(left, right) {
|
|
1520
|
+
if (!left || !right) return left === right;
|
|
1521
|
+
return left.every((value, index) => Math.abs(value - (right[index] ?? 0)) <= 1e-6);
|
|
1522
|
+
}
|
|
1523
|
+
function visualTextClasses(pageNumber, spans, fontAliases) {
|
|
1524
|
+
const names = /* @__PURE__ */ new Map();
|
|
1525
|
+
let css = "";
|
|
1526
|
+
for (const span of spans) {
|
|
1527
|
+
if (usesPositionedSpan(span) || span.glyphCodes) {
|
|
1528
|
+
continue;
|
|
1529
|
+
}
|
|
1530
|
+
const style = visualTextClassStyle(span, fontAliases);
|
|
1531
|
+
if (!style || names.has(style)) continue;
|
|
1532
|
+
const name = `boxpdf-p${number3(pageNumber)}-t${names.size + 1}`;
|
|
1533
|
+
names.set(style, name);
|
|
1534
|
+
css += `.${name}{${style}}`;
|
|
1535
|
+
}
|
|
1536
|
+
return { css, names };
|
|
1537
|
+
}
|
|
1538
|
+
function visualTextLine(spans, startIndex, styleClasses, pageHeight, fontAliases) {
|
|
1539
|
+
const first = spans[startIndex];
|
|
1540
|
+
if (!first || !canGroupVisualTextLine(first)) return void 0;
|
|
1541
|
+
const style = visualTextClassStyle(first, fontAliases);
|
|
1542
|
+
const className = styleClasses.get(style);
|
|
1543
|
+
if (!className) return void 0;
|
|
1544
|
+
let endIndex = startIndex;
|
|
1545
|
+
while (endIndex + 1 < spans.length) {
|
|
1546
|
+
const next = spans[endIndex + 1];
|
|
1547
|
+
if (!next || !canGroupVisualTextLine(next) || Math.abs(next.bounds.y - first.bounds.y) > 1e-3 || visualTextClassStyle(next, fontAliases) !== style) {
|
|
1548
|
+
break;
|
|
1549
|
+
}
|
|
1550
|
+
endIndex += 1;
|
|
1551
|
+
}
|
|
1552
|
+
if (endIndex === startIndex) return void 0;
|
|
1553
|
+
const baseline = pageHeight - first.bounds.y;
|
|
1554
|
+
const lineSpans = spans.slice(startIndex, endIndex + 1);
|
|
1555
|
+
const dxRun = visualDxTextRun(lineSpans, fontAliases);
|
|
1556
|
+
if (dxRun) {
|
|
1557
|
+
return {
|
|
1558
|
+
html: `<text class="${className}" x="${number3(first.bounds.x)}" y="${number3(baseline)}" dx="${dxRun.offsets.map(number3).join(" ")}">${escapeHtml4(dxRun.text)}</text>`,
|
|
1559
|
+
endIndex
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
const content = lineSpans.map(
|
|
1563
|
+
(span, index) => visualTextTspan(span, index > 0 ? textSpanGap(lineSpans[index - 1], span) : void 0)
|
|
1564
|
+
).join("");
|
|
1565
|
+
return {
|
|
1566
|
+
html: `<text class="${className}" x="${number3(first.bounds.x)}" y="${number3(baseline)}">${content}</text>`,
|
|
1567
|
+
endIndex
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
function visualDxTextRun(spans, fontAliases) {
|
|
1571
|
+
const first = spans[0];
|
|
1572
|
+
if (!first?.fontAssetId || !fontAliases.has(first.fontAssetId) || spans.length < 2)
|
|
1573
|
+
return void 0;
|
|
1574
|
+
let text = first.text;
|
|
1575
|
+
const offsets = characterOffsets(first, 0);
|
|
1576
|
+
for (let index = 1; index < spans.length; index += 1) {
|
|
1577
|
+
const previous = spans[index - 1];
|
|
1578
|
+
const current = spans[index];
|
|
1579
|
+
if (!previous || !current || current.fontAssetId !== first.fontAssetId) return void 0;
|
|
1580
|
+
const characters = [...current.text];
|
|
1581
|
+
if (characters.length === 0 || previous.naturalWidth === void 0) return void 0;
|
|
1582
|
+
const gap = current.bounds.x - (previous.bounds.x + previous.bounds.width);
|
|
1583
|
+
const trailingSpacing = (previous.characterSpacing ?? 0) + (previous.text.endsWith(" ") ? previous.wordSpacing ?? 0 : 0);
|
|
1584
|
+
offsets.push(...characterOffsets(current, gap + trailingSpacing));
|
|
1585
|
+
text += current.text;
|
|
1586
|
+
}
|
|
1587
|
+
while (offsets.at(-1) === 0) offsets.pop();
|
|
1588
|
+
return offsets.length > 0 ? { text, offsets } : void 0;
|
|
1589
|
+
}
|
|
1590
|
+
function characterOffsets(span, first) {
|
|
1591
|
+
const characters = [...span.text];
|
|
1592
|
+
return characters.map(
|
|
1593
|
+
(_, index) => index === 0 ? first : (span.characterSpacing ?? 0) + (characters[index - 1] === " " ? span.wordSpacing ?? 0 : 0)
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
function visualTextTspan(span, dx) {
|
|
1597
|
+
const extent = span.bounds.width;
|
|
1598
|
+
const offset = dx === void 0 || number3(dx) === "0" ? "" : ` dx="${number3(dx)}"`;
|
|
1599
|
+
const length = extent > 0 ? ` textLength="${number3(extent)}" lengthAdjust="${usesSpacingAdjustment(span) ? "spacing" : "spacingAndGlyphs"}"` : "";
|
|
1600
|
+
return `<tspan${offset}${length}>${escapeHtml4(span.text)}</tspan>`;
|
|
1601
|
+
}
|
|
1602
|
+
function textSpanGap(previous, current) {
|
|
1603
|
+
if (!previous) return 0;
|
|
1604
|
+
const gap = current.bounds.x - (previous.bounds.x + previous.bounds.width);
|
|
1605
|
+
const adjustment = current.textAdjustmentBefore;
|
|
1606
|
+
return adjustment !== void 0 && Math.abs(adjustment - gap) <= 1e-3 ? adjustment : gap;
|
|
1607
|
+
}
|
|
1608
|
+
function canGroupVisualTextLine(span) {
|
|
1609
|
+
return !usesPositionedSpan(span) && !span.glyphCodes && span.direction === "ltr" && !isHebrewPaintOrder(span) && !hasNonIdentityTransform(span.transform) && span.renderingMode !== 3 && span.renderingMode !== 7 && (span.fontAssetId !== void 0 || !isAdobeCjkFont(span.fontFamily));
|
|
1610
|
+
}
|
|
1141
1611
|
function usesReflectedVisualOverlay(page, spans) {
|
|
1142
1612
|
return Boolean(page.images?.length) && Boolean(page.paths?.length || page.fills?.length) && spans.length > 0 && spans.every(
|
|
1143
1613
|
(span) => span.transform !== void 0 && Math.abs(span.transform[0] + 1) < 1e-6 && Math.abs(span.transform[1]) < 1e-6 && Math.abs(span.transform[2]) < 1e-6 && Math.abs(span.transform[3] - 1) < 1e-6
|
|
1144
1614
|
);
|
|
1145
1615
|
}
|
|
1146
|
-
function visualImage(image, pageHeight, pageNumber, imageIndex) {
|
|
1616
|
+
function visualImage(image, pageHeight, pageNumber, imageIndex, source) {
|
|
1147
1617
|
const [a, b, c, d, e, f] = image.transform;
|
|
1148
1618
|
const transform = [a, -b, -c, d, c + e, pageHeight - d - f].map(number3).join(" ");
|
|
1149
1619
|
const opacity = isUnitInterval2(image.opacity) ? ` opacity="${number3(image.opacity)}"` : "";
|
|
1150
|
-
|
|
1151
|
-
const data = image.format === "jpeg" ? image.data : rgbBmp2(image);
|
|
1152
|
-
let output = `<image width="1" height="1" preserveAspectRatio="none" transform="matrix(${transform})" href="data:${mime};base64,${base64(data)}"${opacity}/>`;
|
|
1620
|
+
let output = `<image width="1" height="1" preserveAspectRatio="none" transform="matrix(${transform})" href="${source}"${opacity}/>`;
|
|
1153
1621
|
for (let index = (image.clips?.length ?? 0) - 1; index >= 0; index -= 1) {
|
|
1154
1622
|
output = `<g clip-path="url(#${imageClipId(pageNumber, imageIndex, index)})">${output}</g>`;
|
|
1155
1623
|
}
|
|
1156
1624
|
return output;
|
|
1157
1625
|
}
|
|
1626
|
+
async function prepareVisualImages(page, imageOptions, onImage) {
|
|
1627
|
+
if (imageOptions === "excluded") return [];
|
|
1628
|
+
const sources = [];
|
|
1629
|
+
for (const [index, image] of (page.images ?? []).entries()) {
|
|
1630
|
+
const mimeType = image.format === "jpeg" ? "image/jpeg" : "image/bmp";
|
|
1631
|
+
const data = image.format === "jpeg" ? image.data : rgbBmp2(image);
|
|
1632
|
+
if (imageOptions === "embedded") {
|
|
1633
|
+
sources.push(`data:${mimeType};base64,${base64(data)}`);
|
|
1634
|
+
continue;
|
|
1635
|
+
}
|
|
1636
|
+
const extension = image.format === "jpeg" ? "jpg" : "bmp";
|
|
1637
|
+
const name = `page-${page.number}-image-${index + 1}.${extension}`;
|
|
1638
|
+
await onImage?.({ name, mimeType, data });
|
|
1639
|
+
sources.push(name);
|
|
1640
|
+
}
|
|
1641
|
+
return sources;
|
|
1642
|
+
}
|
|
1158
1643
|
function imageClipDefinitions(images, pageNumber, pageHeight) {
|
|
1159
1644
|
return images.flatMap(
|
|
1160
1645
|
(image, imageIndex) => (image.clips ?? []).map((clip, clipIndex) => {
|
|
@@ -1221,8 +1706,9 @@ function positionedSpan(span, fontAliases) {
|
|
|
1221
1706
|
].join(";");
|
|
1222
1707
|
return `<span class="pdf-span"${direction} style="${style}">${escapeHtml4(span.text)}</span>`;
|
|
1223
1708
|
}
|
|
1224
|
-
async function writeFlowPage(page, write) {
|
|
1225
|
-
const
|
|
1709
|
+
async function writeFlowPage(page, write, options) {
|
|
1710
|
+
const imageOptions = resolveImageOptions("semantic", options);
|
|
1711
|
+
const media = await prepareSemanticMedia(page, imageOptions, options.onImage);
|
|
1226
1712
|
const structured = (0, import_structure2.structurePage)(withoutSemanticMediaSpans(page, media));
|
|
1227
1713
|
const defaultColor = dominantTextColor(structured.lines);
|
|
1228
1714
|
let mediaIndex = 0;
|
|
@@ -1359,10 +1845,37 @@ function semanticBlockY2(block) {
|
|
|
1359
1845
|
const lines = block.type === "list" ? block.items.flatMap((item) => item.lines) : block.lines;
|
|
1360
1846
|
return Math.max(...lines.map((line) => line.bounds.y + line.bounds.height));
|
|
1361
1847
|
}
|
|
1362
|
-
function visualText(span, pageHeight, fontAliases, counterRotateReflectedText = false) {
|
|
1848
|
+
function visualText(span, pageHeight, fontAliases, counterRotateReflectedText = false, styleClasses) {
|
|
1363
1849
|
if (span.renderingMode === 3 || span.renderingMode === 7) return "";
|
|
1364
1850
|
if (!span.fontAssetId && isAdobeCjkFont(span.fontFamily)) return "";
|
|
1365
1851
|
const direction = directionAttribute([span]);
|
|
1852
|
+
const style = visualTextStyle(span, fontAliases);
|
|
1853
|
+
const styleClass = styleClasses?.get(visualTextClassStyle(span, fontAliases));
|
|
1854
|
+
const presentation = styleClass ? ` class="${styleClass}"` : style ? ` style="${style}"` : "";
|
|
1855
|
+
const fontSize = styleClass ? "" : ` font-size="${number3(span.fontSize)}"`;
|
|
1856
|
+
const textExtent = span.direction === "ttb" ? span.bounds.height : span.bounds.width;
|
|
1857
|
+
const textLength = textExtent > 0 && !isHebrewPaintOrder(span) ? ` textLength="${number3(textExtent)}" lengthAdjust="${span.direction === "ttb" || usesSpacingAdjustment(span) ? "spacing" : "spacingAndGlyphs"}"` : "";
|
|
1858
|
+
const transform = counterRotateReflectedText && span.transform ? [
|
|
1859
|
+
span.transform[0],
|
|
1860
|
+
span.transform[1],
|
|
1861
|
+
span.transform[2],
|
|
1862
|
+
-span.transform[3]
|
|
1863
|
+
] : span.transform;
|
|
1864
|
+
const transformed = hasNonIdentityTransform(transform);
|
|
1865
|
+
const rtlOffset = span.direction === "rtl" ? span.bounds.width : 0;
|
|
1866
|
+
const basisX = transform?.[0] ?? 1;
|
|
1867
|
+
const basisY = transform?.[1] ?? 0;
|
|
1868
|
+
const anchorX = span.bounds.x + basisX * rtlOffset;
|
|
1869
|
+
const anchorY = pageHeight - span.bounds.y + basisY * rtlOffset;
|
|
1870
|
+
const position = transformed ? ` x="0" y="0" transform="matrix(${transform?.map(number3).join(" ")} ${number3(anchorX)} ${number3(anchorY)})"` : ` x="${number3(anchorX)}" y="${number3(anchorY)}"`;
|
|
1871
|
+
return `<text${direction}${position}${fontSize}${textLength}${presentation}>${escapeHtml4(span.text)}</text>`;
|
|
1872
|
+
}
|
|
1873
|
+
function visualTextClassStyle(span, fontAliases) {
|
|
1874
|
+
const style = visualTextStyle(span, fontAliases);
|
|
1875
|
+
const fontSize = `font-size:${number3(span.fontSize)}px`;
|
|
1876
|
+
return style ? `${style};${fontSize}` : fontSize;
|
|
1877
|
+
}
|
|
1878
|
+
function visualTextStyle(span, fontAliases) {
|
|
1366
1879
|
const font = visualFontStyles(
|
|
1367
1880
|
span.fontFamily,
|
|
1368
1881
|
span.fontAssetId ? fontAliases.get(span.fontAssetId) : void 0
|
|
@@ -1372,7 +1885,7 @@ function visualText(span, pageHeight, fontAliases, counterRotateReflectedText =
|
|
|
1372
1885
|
const strokeOnly = span.renderingMode === 1 || span.renderingMode === 5;
|
|
1373
1886
|
const fillOpacity = isUnitInterval2(span.fillOpacity) ? `fill-opacity:${number3(span.fillOpacity)}` : "";
|
|
1374
1887
|
const strokeOpacity = isUnitInterval2(span.strokeOpacity) ? `stroke-opacity:${number3(span.strokeOpacity)}` : "";
|
|
1375
|
-
|
|
1888
|
+
return [
|
|
1376
1889
|
isHebrewPaintOrder(span) ? "unicode-bidi:bidi-override;direction:ltr" : "",
|
|
1377
1890
|
span.direction === "ttb" ? "writing-mode:vertical-rl" : "",
|
|
1378
1891
|
strokeOnly ? "fill:none" : isCssHexColor2(span.color) ? `fill:${span.color}` : "",
|
|
@@ -1382,22 +1895,6 @@ function visualText(span, pageHeight, fontAliases, counterRotateReflectedText =
|
|
|
1382
1895
|
strokeOpacity,
|
|
1383
1896
|
font
|
|
1384
1897
|
].filter(Boolean).join(";");
|
|
1385
|
-
const textExtent = span.direction === "ttb" ? span.bounds.height : span.bounds.width;
|
|
1386
|
-
const textLength = textExtent > 0 && !isHebrewPaintOrder(span) ? ` textLength="${number3(textExtent)}" lengthAdjust="${span.direction === "ttb" || usesSpacingAdjustment(span) ? "spacing" : "spacingAndGlyphs"}"` : "";
|
|
1387
|
-
const transform = counterRotateReflectedText && span.transform ? [
|
|
1388
|
-
span.transform[0],
|
|
1389
|
-
span.transform[1],
|
|
1390
|
-
span.transform[2],
|
|
1391
|
-
-span.transform[3]
|
|
1392
|
-
] : span.transform;
|
|
1393
|
-
const transformed = hasNonIdentityTransform(transform);
|
|
1394
|
-
const rtlOffset = span.direction === "rtl" ? span.bounds.width : 0;
|
|
1395
|
-
const basisX = transform?.[0] ?? 1;
|
|
1396
|
-
const basisY = transform?.[1] ?? 0;
|
|
1397
|
-
const anchorX = span.bounds.x + basisX * rtlOffset;
|
|
1398
|
-
const anchorY = pageHeight - span.bounds.y + basisY * rtlOffset;
|
|
1399
|
-
const position = transformed ? ` x="0" y="0" transform="matrix(${transform?.map(number3).join(" ")} ${number3(anchorX)} ${number3(anchorY)})"` : ` x="${number3(anchorX)}" y="${number3(anchorY)}"`;
|
|
1400
|
-
return `<text${direction}${position} font-size="${number3(span.fontSize)}"${textLength}${style ? ` style="${style}"` : ""}>${escapeHtml4(span.text)}</text>`;
|
|
1401
1898
|
}
|
|
1402
1899
|
function isAdobeCjkFont(fontFamily) {
|
|
1403
1900
|
return /^Adobe(?:Heiti|Song|Kaiti|Ming|Gothic|Mincho)Std-/i.test(fontFamily ?? "");
|
|
@@ -1492,10 +1989,19 @@ function resolveProfile(options) {
|
|
|
1492
1989
|
}
|
|
1493
1990
|
return options.profile ?? legacyProfile;
|
|
1494
1991
|
}
|
|
1992
|
+
function resolveImageOptions(profile, options) {
|
|
1993
|
+
return options.imageOptions ?? (profile === "semantic" ? "excluded" : "embedded");
|
|
1994
|
+
}
|
|
1995
|
+
function validateImageOptions(imageOptions, options) {
|
|
1996
|
+
if (imageOptions === "references" && !options.onImage) {
|
|
1997
|
+
throw new Error('imageOptions "references" requires an onImage callback');
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
1495
2000
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1496
2001
|
0 && (module.exports = {
|
|
1497
2002
|
pageToHtml,
|
|
1498
2003
|
writeHtmlDocument,
|
|
2004
|
+
writeMarkdownDocument,
|
|
1499
2005
|
writePage
|
|
1500
2006
|
});
|
|
1501
2007
|
//# sourceMappingURL=index.cjs.map
|