@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.js
CHANGED
|
@@ -195,6 +195,12 @@ function dominantTextColor(lines) {
|
|
|
195
195
|
return [...counts].sort((left, right) => right[1] - left[1])[0]?.[0] ?? "#000000";
|
|
196
196
|
}
|
|
197
197
|
function semanticTextHtml(text, lines, defaultColor, preserveWeight = true) {
|
|
198
|
+
return semanticText(text, lines, defaultColor, preserveWeight, "html");
|
|
199
|
+
}
|
|
200
|
+
function semanticTextMarkdown(text, lines, defaultColor, preserveWeight = true) {
|
|
201
|
+
return semanticText(text, lines, defaultColor, preserveWeight, "markdown");
|
|
202
|
+
}
|
|
203
|
+
function semanticText(text, lines, defaultColor, preserveWeight, format) {
|
|
198
204
|
const ranges = [];
|
|
199
205
|
let cursor = 0;
|
|
200
206
|
for (const span of lines.flatMap((line) => line.spans)) {
|
|
@@ -220,11 +226,11 @@ function semanticTextHtml(text, lines, defaultColor, preserveWeight = true) {
|
|
|
220
226
|
let html = "";
|
|
221
227
|
let offset = 0;
|
|
222
228
|
for (const range of merged) {
|
|
223
|
-
html +=
|
|
224
|
-
html +=
|
|
229
|
+
html += escapeText(text.slice(offset, range.start), format);
|
|
230
|
+
html += styledText(text.slice(range.start, range.end), range, format);
|
|
225
231
|
offset = range.end;
|
|
226
232
|
}
|
|
227
|
-
return html +
|
|
233
|
+
return html + escapeText(text.slice(offset), format);
|
|
228
234
|
}
|
|
229
235
|
function mergeRanges(ranges, text) {
|
|
230
236
|
const merged = [];
|
|
@@ -238,13 +244,19 @@ function mergeRanges(ranges, text) {
|
|
|
238
244
|
}
|
|
239
245
|
return merged;
|
|
240
246
|
}
|
|
241
|
-
function
|
|
242
|
-
let html =
|
|
247
|
+
function styledText(value, range, format) {
|
|
248
|
+
let html = escapeText(value, format);
|
|
243
249
|
if (range.color) html = `<span style="color:${range.color}">${html}</span>`;
|
|
244
|
-
if (range.italic) html = `<em>${html}</em
|
|
245
|
-
if (range.bold) html = `<strong>${html}</strong
|
|
250
|
+
if (range.italic) html = format === "html" ? `<em>${html}</em>` : `_${html}_`;
|
|
251
|
+
if (range.bold) html = format === "html" ? `<strong>${html}</strong>` : `**${html}**`;
|
|
246
252
|
return html;
|
|
247
253
|
}
|
|
254
|
+
function escapeText(value, format) {
|
|
255
|
+
return format === "html" ? escapeHtml(value) : escapeMarkdown(value);
|
|
256
|
+
}
|
|
257
|
+
function escapeMarkdown(value) {
|
|
258
|
+
return value.replace(/([\\`*_[\]<>])/g, "\\$1");
|
|
259
|
+
}
|
|
248
260
|
function normalizedColor(value) {
|
|
249
261
|
if (!value || !/^#[\da-f]{6}$/i.test(value)) return void 0;
|
|
250
262
|
const color = value.toLowerCase();
|
|
@@ -336,17 +348,20 @@ function number(value) {
|
|
|
336
348
|
// src/visual-font.ts
|
|
337
349
|
function visualFontAliases(pageNumber, fonts) {
|
|
338
350
|
return new Map(
|
|
339
|
-
fonts.filter(
|
|
351
|
+
fonts.filter(
|
|
352
|
+
(font) => (font.format === "truetype" || font.format === "opentype") && !/(?:courier|^TTE)/i.test(font.family ?? "")
|
|
353
|
+
).map((font) => [font.id, `boxpdf-${pageNumber}-${font.id}`])
|
|
340
354
|
);
|
|
341
355
|
}
|
|
342
356
|
function visualFontFace(font, aliases) {
|
|
343
|
-
if (font.format !== "truetype") return "";
|
|
357
|
+
if (font.format !== "truetype" && font.format !== "opentype") return "";
|
|
344
358
|
const alias = aliases.get(font.id);
|
|
345
359
|
if (!alias) return "";
|
|
346
360
|
const styles2 = visualFontStyles(font.family, alias).filter(
|
|
347
361
|
(style) => !style.startsWith("font-family:")
|
|
348
362
|
);
|
|
349
|
-
|
|
363
|
+
const mime = font.format === "opentype" ? "font/otf" : "font/ttf";
|
|
364
|
+
return `@font-face{font-family:${alias};src:url(data:${mime};base64,${base64(font.data)}) format("${font.format}");${styles2.join(";")}}`;
|
|
350
365
|
}
|
|
351
366
|
function visualFontStyles(fontFamily, alias) {
|
|
352
367
|
const normalized = fontFamily?.toLowerCase() ?? "";
|
|
@@ -380,23 +395,39 @@ function base64(bytes) {
|
|
|
380
395
|
}
|
|
381
396
|
|
|
382
397
|
// src/semantic-media.ts
|
|
383
|
-
function semanticMedia(page) {
|
|
384
|
-
|
|
385
|
-
output
|
|
398
|
+
function semanticMedia(page, imageOptions = "embedded") {
|
|
399
|
+
if (imageOptions === "excluded") return [];
|
|
400
|
+
const output = (page.images ?? []).map(
|
|
401
|
+
(image, index) => rasterMedia(image, page.number, index, imageOptions)
|
|
402
|
+
);
|
|
403
|
+
output.push(...vectorMedia(page, imageOptions));
|
|
386
404
|
return mediaComponents(output, page).sort((left, right) => right.bounds.y - left.bounds.y);
|
|
387
405
|
}
|
|
388
|
-
function
|
|
406
|
+
async function prepareSemanticMedia(page, imageOptions, onImage) {
|
|
407
|
+
const media = semanticMedia(page, imageOptions);
|
|
408
|
+
for (const item of media) {
|
|
409
|
+
for (const asset of item.assets ?? []) await onImage?.(asset);
|
|
410
|
+
delete item.assets;
|
|
411
|
+
}
|
|
412
|
+
return media;
|
|
413
|
+
}
|
|
414
|
+
function rasterMedia(image, pageNumber, index, imageOptions) {
|
|
389
415
|
const bounds2 = transformedUnitBounds(image.transform);
|
|
390
416
|
const mime = image.format === "jpeg" ? "image/jpeg" : "image/bmp";
|
|
391
417
|
const data = image.format === "jpeg" ? image.data : rgbBmp(image);
|
|
418
|
+
const extension = image.format === "jpeg" ? "jpg" : "bmp";
|
|
419
|
+
const name = `page-${pageNumber}-image-${index + 1}.${extension}`;
|
|
420
|
+
const source = imageOptions === "references" ? name : `data:${mime};base64,${base64(data)}`;
|
|
392
421
|
const opacity = unitInterval(image.opacity) ? `;opacity:${number2(image.opacity)}` : "";
|
|
393
422
|
return {
|
|
394
423
|
bounds: bounds2,
|
|
395
424
|
kind: "raster",
|
|
396
|
-
html: `<img class="pdf-semantic-media" src="
|
|
425
|
+
html: `<img class="pdf-semantic-media" src="${source}" width="${number2(bounds2.width)}" height="${number2(bounds2.height)}" alt="" style="max-width:100%;height:auto${opacity}">`,
|
|
426
|
+
markdown: ``,
|
|
427
|
+
...imageOptions === "references" ? { assets: [{ name, mimeType: mime, data }] } : {}
|
|
397
428
|
};
|
|
398
429
|
}
|
|
399
|
-
function vectorMedia(page) {
|
|
430
|
+
function vectorMedia(page, imageOptions) {
|
|
400
431
|
const primitives = [
|
|
401
432
|
...(page.paths ?? []).flatMap((path, index) => {
|
|
402
433
|
const bounds2 = vectorPathBounds(path);
|
|
@@ -412,9 +443,11 @@ function vectorMedia(page) {
|
|
|
412
443
|
);
|
|
413
444
|
const aliases = visualFontAliases(page.number, page.fonts ?? []);
|
|
414
445
|
const visualCodeFonts = new Set(
|
|
415
|
-
(page.fonts ?? []).filter(
|
|
446
|
+
(page.fonts ?? []).filter(
|
|
447
|
+
(font) => (font.format === "truetype" || font.format === "opentype") && font.visualCodeMapping
|
|
448
|
+
).map((font) => font.id)
|
|
416
449
|
);
|
|
417
|
-
return components.map((component) => {
|
|
450
|
+
return components.map((component, componentIndex) => {
|
|
418
451
|
const bounds2 = component.bounds;
|
|
419
452
|
const paths = component.primitives.flatMap(
|
|
420
453
|
(primitive) => primitive.type === "path" ? [{ path: primitive.value, index: primitive.index }] : []
|
|
@@ -431,10 +464,18 @@ function vectorMedia(page) {
|
|
|
431
464
|
);
|
|
432
465
|
const fontIds = new Set(overlay.map((span) => span.fontAssetId));
|
|
433
466
|
const fontFaces = (page.fonts ?? []).filter((font) => fontIds.has(font.id)).map((font) => visualFontFace(font, aliases)).join("");
|
|
467
|
+
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>`;
|
|
468
|
+
const name = `page-${page.number}-vector-${componentIndex + 1}.svg`;
|
|
434
469
|
return {
|
|
435
470
|
bounds: bounds2,
|
|
436
471
|
kind: "vector",
|
|
437
|
-
html: `<
|
|
472
|
+
html: imageOptions === "references" ? `<img class="pdf-semantic-media" src="${name}" width="${number2(bounds2.width)}" height="${number2(bounds2.height)}" alt="">` : svg,
|
|
473
|
+
markdown: imageOptions === "references" ? `` : svg,
|
|
474
|
+
...imageOptions === "references" ? {
|
|
475
|
+
assets: [
|
|
476
|
+
{ name, mimeType: "image/svg+xml", data: new TextEncoder().encode(svg) }
|
|
477
|
+
]
|
|
478
|
+
} : {},
|
|
438
479
|
...consumedSpans.length > 0 ? { consumedSpans } : {}
|
|
439
480
|
};
|
|
440
481
|
});
|
|
@@ -476,7 +517,9 @@ function compositeMedia(items) {
|
|
|
476
517
|
bounds: bounds2,
|
|
477
518
|
kind: "composite",
|
|
478
519
|
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>`,
|
|
479
|
-
|
|
520
|
+
markdown: items.map((item) => item.markdown).join("\n\n"),
|
|
521
|
+
consumedSpans: items.flatMap((item) => item.consumedSpans ?? []),
|
|
522
|
+
assets: items.flatMap((item) => item.assets ?? [])
|
|
480
523
|
};
|
|
481
524
|
}
|
|
482
525
|
function mediaPiecesTouch(left, right) {
|
|
@@ -615,7 +658,7 @@ function escapeHtml2(value) {
|
|
|
615
658
|
}
|
|
616
659
|
|
|
617
660
|
// src/semantic-document.ts
|
|
618
|
-
async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
661
|
+
async function writeSemanticDocument(pages, write, lookaheadPages, imageOptions, onImage, format = "html") {
|
|
619
662
|
const stats = {
|
|
620
663
|
pagesProcessed: 0,
|
|
621
664
|
peakBufferedPages: 0,
|
|
@@ -633,27 +676,30 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
633
676
|
let contentStarted = false;
|
|
634
677
|
let employmentOpen = false;
|
|
635
678
|
let pendingParagraph;
|
|
636
|
-
|
|
679
|
+
const markdown = format === "markdown";
|
|
680
|
+
const output = (html, markdownValue = "") => write(markdown ? markdownValue : html);
|
|
681
|
+
const inlineText = (text, lines, defaultColor, preserveWeight = true) => markdown ? semanticTextMarkdown(text, lines, defaultColor, preserveWeight) : semanticTextHtml(text, lines, defaultColor, preserveWeight);
|
|
682
|
+
await output('<article class="pdf-semantic-document">');
|
|
637
683
|
const closeTable = async () => {
|
|
638
684
|
if (!activeTable) return;
|
|
639
|
-
await
|
|
685
|
+
await output("</table>", "\n");
|
|
640
686
|
activeTable = void 0;
|
|
641
687
|
while (pendingMedia.length > 0) await write(pendingMedia.shift() ?? "");
|
|
642
688
|
};
|
|
643
689
|
const closeSections = async (minimumLevel = 0) => {
|
|
644
690
|
while ((sectionLevels.at(-1) ?? -1) >= minimumLevel && sectionLevels.length > 0) {
|
|
645
|
-
await
|
|
691
|
+
await output("</section>");
|
|
646
692
|
sectionLevels.pop();
|
|
647
693
|
}
|
|
648
694
|
};
|
|
649
695
|
const flushPendingParagraph = async () => {
|
|
650
696
|
if (!pendingParagraph) return;
|
|
651
|
-
await write(
|
|
697
|
+
await write(semanticBlockOutput(pendingParagraph.block, pendingParagraph.defaultColor, format));
|
|
652
698
|
pendingParagraph = void 0;
|
|
653
699
|
};
|
|
654
700
|
const closeEmployment = async () => {
|
|
655
701
|
if (!employmentOpen) return;
|
|
656
|
-
await
|
|
702
|
+
await output("</section>");
|
|
657
703
|
employmentOpen = false;
|
|
658
704
|
};
|
|
659
705
|
const emitPage = async (page, future) => {
|
|
@@ -681,7 +727,11 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
681
727
|
await flushPendingParagraph();
|
|
682
728
|
const item = page.media[mediaIndex];
|
|
683
729
|
if (item && captions.get(block) === item && block.type === "paragraph") {
|
|
684
|
-
const html2 =
|
|
730
|
+
const html2 = markdown ? `${item.markdown}
|
|
731
|
+
|
|
732
|
+
*${inlineText(block.text, block.lines, defaultColor)}*
|
|
733
|
+
|
|
734
|
+
` : `<figure class="pdf-semantic-figure">${item.html}<figcaption>${inlineText(block.text, block.lines, defaultColor)}</figcaption></figure>`;
|
|
685
735
|
if (activeTable) pendingMedia.push(html2);
|
|
686
736
|
else await write(html2);
|
|
687
737
|
emittedMedia.add(item);
|
|
@@ -690,7 +740,9 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
690
740
|
break;
|
|
691
741
|
}
|
|
692
742
|
if (item && captionedMedia.has(item)) break;
|
|
693
|
-
const html =
|
|
743
|
+
const html = markdown ? `${item?.markdown ?? ""}
|
|
744
|
+
|
|
745
|
+
` : `<div class="pdf-semantic-visual">${item?.html}</div>`;
|
|
694
746
|
if (activeTable) pendingMedia.push(html);
|
|
695
747
|
else await write(html);
|
|
696
748
|
mediaIndex += 1;
|
|
@@ -698,7 +750,11 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
698
750
|
const associatedMedia = captions.get(block);
|
|
699
751
|
if (!emittedAsCaption && associatedMedia && block.type === "paragraph") {
|
|
700
752
|
await flushPendingParagraph();
|
|
701
|
-
const html =
|
|
753
|
+
const html = markdown ? `${associatedMedia.markdown}
|
|
754
|
+
|
|
755
|
+
*${inlineText(block.text, block.lines, defaultColor)}*
|
|
756
|
+
|
|
757
|
+
` : `<figure class="pdf-semantic-figure">${associatedMedia.html}<figcaption>${inlineText(block.text, block.lines, defaultColor)}</figcaption></figure>`;
|
|
702
758
|
if (activeTable) pendingMedia.push(html);
|
|
703
759
|
else await write(html);
|
|
704
760
|
emittedMedia.add(associatedMedia);
|
|
@@ -712,8 +768,11 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
712
768
|
await flushPendingParagraph();
|
|
713
769
|
if (employmentOpen && block.type !== "list") await closeEmployment();
|
|
714
770
|
if (!contentStarted && !headerOpen && block.type === "heading" && block.level === 1) {
|
|
715
|
-
await
|
|
716
|
-
`<header><h1>${
|
|
771
|
+
await output(
|
|
772
|
+
`<header><h1>${inlineText(block.text, block.lines, defaultColor, false)}</h1>`,
|
|
773
|
+
`# ${inlineText(block.text, block.lines, defaultColor, false)}
|
|
774
|
+
|
|
775
|
+
`
|
|
717
776
|
);
|
|
718
777
|
headerOpen = true;
|
|
719
778
|
continue;
|
|
@@ -721,19 +780,25 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
721
780
|
if (headerOpen) {
|
|
722
781
|
if (block.type === "paragraph") {
|
|
723
782
|
const tag = isContactBlock(block) ? "address" : "p";
|
|
724
|
-
await
|
|
725
|
-
`<${tag}>${
|
|
783
|
+
await output(
|
|
784
|
+
`<${tag}>${inlineText(block.text, block.lines, defaultColor)}</${tag}>`,
|
|
785
|
+
`${inlineText(block.text, block.lines, defaultColor)}
|
|
786
|
+
|
|
787
|
+
`
|
|
726
788
|
);
|
|
727
789
|
headerHasParagraph = true;
|
|
728
790
|
continue;
|
|
729
791
|
}
|
|
730
792
|
if (block.type === "heading" && !headerHasParagraph && (block.level === 1 || block.text.trimStart().startsWith("#") || block.level === 4 && nextBlock?.type === "paragraph" && isContactBlock(nextBlock))) {
|
|
731
|
-
await
|
|
732
|
-
`<h${block.level}>${
|
|
793
|
+
await output(
|
|
794
|
+
`<h${block.level}>${inlineText(block.text, block.lines, defaultColor, false)}</h${block.level}>`,
|
|
795
|
+
`${"#".repeat(block.level)} ${inlineText(block.text, block.lines, defaultColor, false)}
|
|
796
|
+
|
|
797
|
+
`
|
|
733
798
|
);
|
|
734
799
|
continue;
|
|
735
800
|
}
|
|
736
|
-
await
|
|
801
|
+
await output("</header>");
|
|
737
802
|
headerOpen = false;
|
|
738
803
|
contentStarted = true;
|
|
739
804
|
}
|
|
@@ -741,23 +806,34 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
741
806
|
const rows = tableToRows(block.table);
|
|
742
807
|
if (activeTable && tablesContinue(activeTable.table, block.table, page.width)) {
|
|
743
808
|
const continuationRows = sameRow(activeTable.header, rows[0]) ? rows.slice(1) : rows;
|
|
744
|
-
for (const row of continuationRows)
|
|
809
|
+
for (const row of continuationRows)
|
|
810
|
+
await write(markdown ? markdownTableRow(row) : tableRow(row, false));
|
|
745
811
|
activeTable.table = block.table;
|
|
746
812
|
stats.mergedTables += 1;
|
|
747
813
|
continue;
|
|
748
814
|
}
|
|
749
815
|
await closeTable();
|
|
750
816
|
const header = tableHeader(rows);
|
|
751
|
-
await
|
|
752
|
-
|
|
753
|
-
|
|
817
|
+
await output("<table>", markdownTableStart(rows, header));
|
|
818
|
+
const markdownRows = markdown ? header ? rows.slice(1) : rows : rows;
|
|
819
|
+
for (const [index, row] of markdownRows.entries())
|
|
820
|
+
await write(
|
|
821
|
+
markdown ? markdownTableRow(row) : tableRow(row, Boolean(header && index === 0))
|
|
822
|
+
);
|
|
754
823
|
activeTable = { table: block.table, header };
|
|
755
824
|
continue;
|
|
756
825
|
}
|
|
757
826
|
if (activeTable && block.type === "definitionList" && isFinancialSummary(block)) {
|
|
758
827
|
const columns = activeTable.table.columns.length;
|
|
759
|
-
await
|
|
760
|
-
`<tfoot>${block.entries.map((entry) => financialSummaryRow(entry, columns)).join("")}</tfoot
|
|
828
|
+
await output(
|
|
829
|
+
`<tfoot>${block.entries.map((entry) => financialSummaryRow(entry, columns)).join("")}</tfoot>`,
|
|
830
|
+
block.entries.map(
|
|
831
|
+
(entry) => markdownTableRow([
|
|
832
|
+
entry.term,
|
|
833
|
+
...Array(Math.max(0, columns - 2)).fill(""),
|
|
834
|
+
entry.description
|
|
835
|
+
])
|
|
836
|
+
).join("")
|
|
761
837
|
);
|
|
762
838
|
await closeTable();
|
|
763
839
|
continue;
|
|
@@ -766,8 +842,11 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
766
842
|
if (block.type === "heading") {
|
|
767
843
|
const level = contentStarted && block.level === 1 ? 2 : block.level;
|
|
768
844
|
await closeSections(level);
|
|
769
|
-
await
|
|
770
|
-
`<section data-level="${level}"><h${level}>${
|
|
845
|
+
await output(
|
|
846
|
+
`<section data-level="${level}"><h${level}>${inlineText(block.text, block.lines, defaultColor, false)}</h${level}>`,
|
|
847
|
+
`${"#".repeat(level)} ${inlineText(block.text, block.lines, defaultColor, false)}
|
|
848
|
+
|
|
849
|
+
`
|
|
771
850
|
);
|
|
772
851
|
sectionLevels.push(level);
|
|
773
852
|
continue;
|
|
@@ -775,13 +854,25 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
775
854
|
if (block.type === "paragraph") {
|
|
776
855
|
if (isTitledRecord(block)) {
|
|
777
856
|
const [institution, ...details] = block.lines;
|
|
778
|
-
if (institution)
|
|
779
|
-
|
|
857
|
+
if (institution)
|
|
858
|
+
await output(
|
|
859
|
+
`<h3>${escapeHtml3(institution.text)}</h3>`,
|
|
860
|
+
`### ${escapeMarkdown2(institution.text)}
|
|
861
|
+
|
|
862
|
+
`
|
|
863
|
+
);
|
|
864
|
+
for (const detail of details)
|
|
865
|
+
await output(`<p>${escapeHtml3(detail.text)}</p>`, `${escapeMarkdown2(detail.text)}
|
|
866
|
+
|
|
867
|
+
`);
|
|
780
868
|
continue;
|
|
781
869
|
}
|
|
782
870
|
if (isUnmarkedList(block)) {
|
|
783
|
-
await
|
|
784
|
-
`<ul>${block.lines.map((line) => `<li>${escapeHtml3(line.text)}</li>`).join("")}</ul
|
|
871
|
+
await output(
|
|
872
|
+
`<ul>${block.lines.map((line) => `<li>${escapeHtml3(line.text)}</li>`).join("")}</ul>`,
|
|
873
|
+
`${block.lines.map((line) => `- ${escapeMarkdown2(line.text)}`).join("\n")}
|
|
874
|
+
|
|
875
|
+
`
|
|
785
876
|
);
|
|
786
877
|
continue;
|
|
787
878
|
}
|
|
@@ -789,19 +880,28 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
789
880
|
continue;
|
|
790
881
|
}
|
|
791
882
|
if (block.type === "employment") {
|
|
792
|
-
await
|
|
793
|
-
`<section><h3>${escapeHtml3(block.role)}</h3><p>${escapeHtml3(block.organization)}</p><p>${escapeHtml3(block.date)}</p
|
|
883
|
+
await output(
|
|
884
|
+
`<section><h3>${escapeHtml3(block.role)}</h3><p>${escapeHtml3(block.organization)}</p><p>${escapeHtml3(block.date)}</p>`,
|
|
885
|
+
`### ${escapeMarkdown2(block.role)}
|
|
886
|
+
|
|
887
|
+
${escapeMarkdown2(block.organization)}
|
|
888
|
+
|
|
889
|
+
${escapeMarkdown2(block.date)}
|
|
890
|
+
|
|
891
|
+
`
|
|
794
892
|
);
|
|
795
893
|
employmentOpen = true;
|
|
796
894
|
continue;
|
|
797
895
|
}
|
|
798
|
-
await write(
|
|
896
|
+
await write(semanticBlockOutput(block, defaultColor, format));
|
|
799
897
|
}
|
|
800
898
|
while (mediaIndex < page.media.length) {
|
|
801
899
|
const item = page.media[mediaIndex];
|
|
802
900
|
if (item && !emittedMedia.has(item)) {
|
|
803
901
|
await flushPendingParagraph();
|
|
804
|
-
const html =
|
|
902
|
+
const html = markdown ? `${item.markdown}
|
|
903
|
+
|
|
904
|
+
` : `<div class="pdf-semantic-visual">${item.html}</div>`;
|
|
805
905
|
if (activeTable) pendingMedia.push(html);
|
|
806
906
|
else await write(html);
|
|
807
907
|
}
|
|
@@ -810,9 +910,10 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
810
910
|
for (const signature of marginSignatures(page)) seenFurniture.add(signature);
|
|
811
911
|
};
|
|
812
912
|
for await (const page of pages) {
|
|
813
|
-
const media =
|
|
913
|
+
const media = await prepareSemanticMedia(page, imageOptions, onImage);
|
|
814
914
|
const structured = structurePage(withoutSemanticMediaSpans(page, media));
|
|
815
915
|
buffer.push({ width: page.width, height: page.height, structured, media });
|
|
916
|
+
restoreObservedHyphens(buffer);
|
|
816
917
|
stats.pagesProcessed += 1;
|
|
817
918
|
stats.peakBufferedPages = Math.max(stats.peakBufferedPages, buffer.length);
|
|
818
919
|
stats.peakBufferedLines = Math.max(
|
|
@@ -828,22 +929,82 @@ async function writeSemanticDocument(pages, write, lookaheadPages) {
|
|
|
828
929
|
const ready = buffer.shift();
|
|
829
930
|
if (ready) await emitPage(ready, buffer);
|
|
830
931
|
}
|
|
831
|
-
if (headerOpen) await
|
|
932
|
+
if (headerOpen) await output("</header>");
|
|
832
933
|
await closeTable();
|
|
833
934
|
await closeEmployment();
|
|
834
935
|
if (pendingParagraph && isFooterParagraph(pendingParagraph.block, pendingParagraph.height)) {
|
|
835
936
|
await closeSections();
|
|
836
|
-
await
|
|
837
|
-
`<footer>${semanticBlockHtml(pendingParagraph.block, pendingParagraph.defaultColor)}</footer
|
|
937
|
+
await output(
|
|
938
|
+
`<footer>${semanticBlockHtml(pendingParagraph.block, pendingParagraph.defaultColor)}</footer>`,
|
|
939
|
+
`---
|
|
940
|
+
|
|
941
|
+
${semanticBlockMarkdown(pendingParagraph.block, pendingParagraph.defaultColor)}`
|
|
838
942
|
);
|
|
839
943
|
pendingParagraph = void 0;
|
|
840
944
|
} else {
|
|
841
945
|
await flushPendingParagraph();
|
|
842
946
|
await closeSections();
|
|
843
947
|
}
|
|
844
|
-
await
|
|
948
|
+
await output("</article>");
|
|
845
949
|
return stats;
|
|
846
950
|
}
|
|
951
|
+
function restoreObservedHyphens(buffer) {
|
|
952
|
+
const terms = new Set(
|
|
953
|
+
buffer.flatMap(
|
|
954
|
+
(page) => page.structured.lines.flatMap(
|
|
955
|
+
(line) => line.text.match(/[\p{L}\p{N}]+(?:[-‐‑][\p{L}\p{N}]+)+/gu) ?? []
|
|
956
|
+
)
|
|
957
|
+
)
|
|
958
|
+
);
|
|
959
|
+
for (const page of buffer) {
|
|
960
|
+
for (const block of page.structured.blocks) restoreBlockHyphens(block, terms);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
function restoreBlockHyphens(block, terms) {
|
|
964
|
+
const restore = (value) => restoreTextHyphens(value, terms);
|
|
965
|
+
if (block.type === "insetGroup") {
|
|
966
|
+
for (const nested of block.blocks) restoreBlockHyphens(nested, terms);
|
|
967
|
+
} else if (block.type === "heading" || block.type === "paragraph" || block.type === "preformatted") {
|
|
968
|
+
block.text = restore(block.text);
|
|
969
|
+
} else if (block.type === "list") {
|
|
970
|
+
for (const item of block.items) item.text = restore(item.text);
|
|
971
|
+
} else if (block.type === "definitionList") {
|
|
972
|
+
for (const entry of block.entries) {
|
|
973
|
+
entry.term = restore(entry.term);
|
|
974
|
+
entry.description = restore(entry.description);
|
|
975
|
+
}
|
|
976
|
+
} else if (block.type === "cardList") {
|
|
977
|
+
for (const item of block.items) {
|
|
978
|
+
item.title = restore(item.title);
|
|
979
|
+
item.details = item.details.map(restore);
|
|
980
|
+
}
|
|
981
|
+
} else if (block.type === "sectionGroup") {
|
|
982
|
+
for (const item of block.items) {
|
|
983
|
+
item.label = restore(item.label);
|
|
984
|
+
item.content = item.content.map(restore);
|
|
985
|
+
}
|
|
986
|
+
} else if (block.type === "employment") {
|
|
987
|
+
block.role = restore(block.role);
|
|
988
|
+
block.organization = restore(block.organization);
|
|
989
|
+
block.date = restore(block.date);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
function restoreTextHyphens(value, terms) {
|
|
993
|
+
let output = value;
|
|
994
|
+
for (const term of terms) {
|
|
995
|
+
const collapsed = term.replace(/[-‐‑]/gu, "");
|
|
996
|
+
if (collapsed === term || !output.includes(collapsed)) continue;
|
|
997
|
+
const pattern = new RegExp(
|
|
998
|
+
`(?<![\\p{L}\\p{N}])${escapeRegularExpression(collapsed)}(?![\\p{L}\\p{N}])`,
|
|
999
|
+
"gu"
|
|
1000
|
+
);
|
|
1001
|
+
output = output.replace(pattern, term);
|
|
1002
|
+
}
|
|
1003
|
+
return output;
|
|
1004
|
+
}
|
|
1005
|
+
function escapeRegularExpression(value) {
|
|
1006
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1007
|
+
}
|
|
847
1008
|
function isContactBlock(block) {
|
|
848
1009
|
const text = block.text;
|
|
849
1010
|
const signals = [
|
|
@@ -927,6 +1088,20 @@ function tableRow(row, header) {
|
|
|
927
1088
|
const cell = header ? "th" : "td";
|
|
928
1089
|
return `<tr>${row.map((value) => `<${cell}>${escapeHtml3(value)}</${cell}>`).join("")}</tr>`;
|
|
929
1090
|
}
|
|
1091
|
+
function markdownTableStart(rows, header) {
|
|
1092
|
+
const columns = rows[0]?.length ?? 0;
|
|
1093
|
+
if (columns === 0) return "";
|
|
1094
|
+
const heading = header ?? Array(columns).fill("");
|
|
1095
|
+
return `${markdownTableRow(heading)}${markdownTableRow(Array(columns).fill("---"), false)}`;
|
|
1096
|
+
}
|
|
1097
|
+
function markdownTableRow(row, shouldEscape = true) {
|
|
1098
|
+
const cells = row.map((value) => shouldEscape ? escapeMarkdownTableCell(value) : value);
|
|
1099
|
+
return `| ${cells.join(" | ")} |
|
|
1100
|
+
`;
|
|
1101
|
+
}
|
|
1102
|
+
function escapeMarkdownTableCell(value) {
|
|
1103
|
+
return escapeMarkdown2(value).replaceAll("|", "\\|").replace(/\s*\n\s*/g, "<br>");
|
|
1104
|
+
}
|
|
930
1105
|
function isFinancialSummary(block) {
|
|
931
1106
|
return block.entries.length > 0 && block.entries.every((entry) => isNumericValue(entry.description)) && block.entries.some((entry) => /\p{Sc}|%/u.test(entry.description));
|
|
932
1107
|
}
|
|
@@ -974,6 +1149,90 @@ function semanticBlockHtml(block, defaultColor = "#000000") {
|
|
|
974
1149
|
const tag = block.ordered ? "ol" : "ul";
|
|
975
1150
|
return `<${tag}>${block.items.map((item) => `<li>${semanticTextHtml(item.text, item.lines, defaultColor)}</li>`).join("")}</${tag}>`;
|
|
976
1151
|
}
|
|
1152
|
+
function semanticBlockOutput(block, defaultColor, format) {
|
|
1153
|
+
return format === "markdown" ? semanticBlockMarkdown(block, defaultColor) : semanticBlockHtml(block, defaultColor);
|
|
1154
|
+
}
|
|
1155
|
+
function semanticBlockMarkdown(block, defaultColor = "#000000") {
|
|
1156
|
+
if (block.type === "insetGroup") {
|
|
1157
|
+
const content = block.blocks.map((item) => semanticBlockMarkdown(item, defaultColor)).join("");
|
|
1158
|
+
return `${content.trimEnd().split("\n").map((line) => line ? `> ${line}` : ">").join("\n")}
|
|
1159
|
+
|
|
1160
|
+
`;
|
|
1161
|
+
}
|
|
1162
|
+
if (block.type === "table") {
|
|
1163
|
+
const rows = tableToRows(block.table);
|
|
1164
|
+
const header = tableHeader(rows);
|
|
1165
|
+
return `${markdownTableStart(rows, header)}${(header ? rows.slice(1) : rows).map((row) => markdownTableRow(row)).join("")}
|
|
1166
|
+
`;
|
|
1167
|
+
}
|
|
1168
|
+
if (block.type === "heading") {
|
|
1169
|
+
return `${"#".repeat(block.level)} ${semanticTextMarkdown(block.text, block.lines, defaultColor, false)}
|
|
1170
|
+
|
|
1171
|
+
`;
|
|
1172
|
+
}
|
|
1173
|
+
if (block.type === "paragraph") {
|
|
1174
|
+
return `${semanticTextMarkdown(block.text, block.lines, defaultColor)}
|
|
1175
|
+
|
|
1176
|
+
`;
|
|
1177
|
+
}
|
|
1178
|
+
if (block.type === "preformatted") {
|
|
1179
|
+
const fence = block.text.includes("```") ? "````" : "```";
|
|
1180
|
+
return `${fence}
|
|
1181
|
+
${block.text}
|
|
1182
|
+
${fence}
|
|
1183
|
+
|
|
1184
|
+
`;
|
|
1185
|
+
}
|
|
1186
|
+
if (block.type === "definitionList") {
|
|
1187
|
+
return `${block.entries.map((entry) => `**${escapeMarkdown2(entry.term)}:** ${escapeMarkdown2(entry.description)}`).join("\n\n")}
|
|
1188
|
+
|
|
1189
|
+
`;
|
|
1190
|
+
}
|
|
1191
|
+
if (block.type === "cardList") {
|
|
1192
|
+
const rows = [
|
|
1193
|
+
["Item", "Quantity", "Amount"],
|
|
1194
|
+
...block.items.map((item) => {
|
|
1195
|
+
const trailing = item.details.at(-1) ?? "";
|
|
1196
|
+
const match = /^\s*[×x]\s*(\d+)\s+(.+)$/u.exec(trailing);
|
|
1197
|
+
const detail = item.details.slice(0, -1).join(" ");
|
|
1198
|
+
return [
|
|
1199
|
+
`${item.title}${detail ? ` \u2014 ${detail}` : ""}`,
|
|
1200
|
+
match?.[1] ?? "",
|
|
1201
|
+
match?.[2] ?? trailing
|
|
1202
|
+
];
|
|
1203
|
+
})
|
|
1204
|
+
];
|
|
1205
|
+
return `## Items ordered
|
|
1206
|
+
|
|
1207
|
+
${markdownTableStart(rows, rows[0])}${rows.slice(1).map((row) => markdownTableRow(row)).join("")}
|
|
1208
|
+
`;
|
|
1209
|
+
}
|
|
1210
|
+
if (block.type === "sectionGroup") {
|
|
1211
|
+
return block.items.map(
|
|
1212
|
+
(item) => `## ${escapeMarkdown2(titleCase(item.label))}
|
|
1213
|
+
|
|
1214
|
+
${item.content.map(
|
|
1215
|
+
(content, index) => index === 0 ? `**${escapeMarkdown2(content)}**` : escapeMarkdown2(content)
|
|
1216
|
+
).join("\n\n")}
|
|
1217
|
+
|
|
1218
|
+
`
|
|
1219
|
+
).join("");
|
|
1220
|
+
}
|
|
1221
|
+
if (block.type === "employment") {
|
|
1222
|
+
return `### ${escapeMarkdown2(block.role)}
|
|
1223
|
+
|
|
1224
|
+
${escapeMarkdown2(block.organization)}
|
|
1225
|
+
|
|
1226
|
+
${escapeMarkdown2(block.date)}
|
|
1227
|
+
|
|
1228
|
+
`;
|
|
1229
|
+
}
|
|
1230
|
+
return `${block.items.map(
|
|
1231
|
+
(item, index) => `${block.ordered ? `${index + 1}.` : "-"} ${semanticTextMarkdown(item.text, item.lines, defaultColor)}`
|
|
1232
|
+
).join("\n")}
|
|
1233
|
+
|
|
1234
|
+
`;
|
|
1235
|
+
}
|
|
977
1236
|
function semanticBlockY(block) {
|
|
978
1237
|
const lines = block.type === "list" ? block.items.flatMap((item) => item.lines) : block.lines;
|
|
979
1238
|
return Math.max(...lines.map((line) => line.bounds.y + line.bounds.height));
|
|
@@ -1006,6 +1265,9 @@ function titleCase(value) {
|
|
|
1006
1265
|
function escapeHtml3(value) {
|
|
1007
1266
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1008
1267
|
}
|
|
1268
|
+
function escapeMarkdown2(value) {
|
|
1269
|
+
return value.replace(/([\\`*_[\]<>])/g, "\\$1");
|
|
1270
|
+
}
|
|
1009
1271
|
|
|
1010
1272
|
// src/index.ts
|
|
1011
1273
|
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}`;
|
|
@@ -1022,16 +1284,40 @@ async function writeHtmlDocument(pages, write, options = {}) {
|
|
|
1022
1284
|
await write("</head><body>");
|
|
1023
1285
|
}
|
|
1024
1286
|
await write('<main class="pdf-document">');
|
|
1025
|
-
|
|
1287
|
+
const profile = resolveProfile(options);
|
|
1288
|
+
const imageOptions = resolveImageOptions(profile, options);
|
|
1289
|
+
validateImageOptions(imageOptions, options);
|
|
1290
|
+
if (profile === "semantic") {
|
|
1026
1291
|
const lookahead = semanticLookahead(options.semanticLookaheadPages);
|
|
1027
|
-
const stats = await writeSemanticDocument(
|
|
1292
|
+
const stats = await writeSemanticDocument(
|
|
1293
|
+
pages,
|
|
1294
|
+
write,
|
|
1295
|
+
lookahead,
|
|
1296
|
+
imageOptions,
|
|
1297
|
+
options.onImage
|
|
1298
|
+
);
|
|
1028
1299
|
options.onSemanticStats?.(stats);
|
|
1029
1300
|
} else {
|
|
1030
|
-
|
|
1301
|
+
const documentFonts = { entries: [] };
|
|
1302
|
+
for await (const page of pages) await writePositionedPage(page, write, options, documentFonts);
|
|
1031
1303
|
}
|
|
1032
1304
|
await write("</main>");
|
|
1033
1305
|
if (includeDocument) await write("</body></html>");
|
|
1034
1306
|
}
|
|
1307
|
+
async function writeMarkdownDocument(pages, write, options = {}) {
|
|
1308
|
+
const imageOptions = options.imageOptions ?? "excluded";
|
|
1309
|
+
validateImageOptions(imageOptions, options);
|
|
1310
|
+
const lookahead = semanticLookahead(options.semanticLookaheadPages);
|
|
1311
|
+
const stats = await writeSemanticDocument(
|
|
1312
|
+
pages,
|
|
1313
|
+
write,
|
|
1314
|
+
lookahead,
|
|
1315
|
+
imageOptions,
|
|
1316
|
+
options.onImage,
|
|
1317
|
+
"markdown"
|
|
1318
|
+
);
|
|
1319
|
+
options.onSemanticStats?.(stats);
|
|
1320
|
+
}
|
|
1035
1321
|
function semanticLookahead(value) {
|
|
1036
1322
|
const lookahead = value ?? 4;
|
|
1037
1323
|
if (!Number.isSafeInteger(lookahead) || lookahead < 1 || lookahead > 16) {
|
|
@@ -1040,7 +1326,9 @@ function semanticLookahead(value) {
|
|
|
1040
1326
|
return lookahead;
|
|
1041
1327
|
}
|
|
1042
1328
|
async function writePage(page, write, options = {}) {
|
|
1043
|
-
|
|
1329
|
+
const profile = resolveProfile(options);
|
|
1330
|
+
validateImageOptions(resolveImageOptions(profile, options), options);
|
|
1331
|
+
if (profile === "semantic") await writeFlowPage(page, write, options);
|
|
1044
1332
|
else await writePositionedPage(page, write, options);
|
|
1045
1333
|
}
|
|
1046
1334
|
async function pageToHtml(page, options = {}) {
|
|
@@ -1054,8 +1342,10 @@ async function pageToHtml(page, options = {}) {
|
|
|
1054
1342
|
);
|
|
1055
1343
|
return output;
|
|
1056
1344
|
}
|
|
1057
|
-
async function writePositionedPage(page, write, options) {
|
|
1058
|
-
const
|
|
1345
|
+
async function writePositionedPage(page, write, options, documentFonts) {
|
|
1346
|
+
const imageOptions = resolveImageOptions("visual", options);
|
|
1347
|
+
const visualImages = await prepareVisualImages(page, imageOptions, options.onImage);
|
|
1348
|
+
const visualSpans = coalesceVisualSpans(page.visualSpans ?? page.spans);
|
|
1059
1349
|
const reflectedOverlay = usesReflectedVisualOverlay(page, visualSpans);
|
|
1060
1350
|
const quarterTurn = page.rotate === 90 || page.rotate === 270;
|
|
1061
1351
|
const displayWidth = quarterTurn ? page.height : page.width;
|
|
@@ -1063,29 +1353,40 @@ async function writePositionedPage(page, write, options) {
|
|
|
1063
1353
|
await write(
|
|
1064
1354
|
`<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">`
|
|
1065
1355
|
);
|
|
1066
|
-
const
|
|
1356
|
+
const { aliases: fontAliases, fontsToEmit } = visualPageFonts(
|
|
1357
|
+
page.number,
|
|
1358
|
+
page.fonts ?? [],
|
|
1359
|
+
documentFonts
|
|
1360
|
+
);
|
|
1067
1361
|
const type3Fonts = new Map(
|
|
1068
1362
|
(page.fonts ?? []).filter((font) => font.format === "type3").map((font) => [font.id, font])
|
|
1069
1363
|
);
|
|
1070
|
-
|
|
1364
|
+
const textClasses = options.includeStyles ?? true ? visualTextClasses(page.number, visualSpans, fontAliases) : void 0;
|
|
1365
|
+
if ((options.includeStyles ?? true) && fontsToEmit.length) {
|
|
1071
1366
|
await write(
|
|
1072
|
-
`<style>${
|
|
1367
|
+
`<style>${fontsToEmit.map((font) => visualFontFace(font, fontAliases)).join("")}</style>`
|
|
1073
1368
|
);
|
|
1074
1369
|
}
|
|
1370
|
+
if (textClasses?.css) await write(`<style>${textClasses.css}</style>`);
|
|
1075
1371
|
await write(
|
|
1076
1372
|
`<div class="pdf-page-content pdf-page-content--${page.rotate}" style="width:${number3(page.width)}pt;height:${number3(page.height)}pt${rotationTransform(page)}">`
|
|
1077
1373
|
);
|
|
1078
1374
|
await write(
|
|
1079
1375
|
`<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)}">`
|
|
1080
1376
|
);
|
|
1081
|
-
const clipDefinitions = imageClipDefinitions(
|
|
1377
|
+
const clipDefinitions = imageClipDefinitions(
|
|
1378
|
+
imageOptions === "excluded" ? [] : page.images ?? [],
|
|
1379
|
+
page.number,
|
|
1380
|
+
page.height
|
|
1381
|
+
) + vectorPathClipDefinitions(
|
|
1082
1382
|
(page.paths ?? []).map((path, index) => ({ path, index })),
|
|
1083
1383
|
page.number
|
|
1084
1384
|
);
|
|
1085
1385
|
if (clipDefinitions) await write(`<defs>${clipDefinitions}</defs>`);
|
|
1086
1386
|
if (reflectedOverlay) {
|
|
1087
1387
|
for (const [index, image] of (page.images ?? []).entries()) {
|
|
1088
|
-
|
|
1388
|
+
const source = visualImages[index];
|
|
1389
|
+
if (source) await write(visualImage(image, page.height, page.number, index, source));
|
|
1089
1390
|
}
|
|
1090
1391
|
}
|
|
1091
1392
|
if (page.fills?.length || page.paths?.length) {
|
|
@@ -1098,14 +1399,29 @@ async function writePositionedPage(page, write, options) {
|
|
|
1098
1399
|
}
|
|
1099
1400
|
if (!reflectedOverlay) {
|
|
1100
1401
|
for (const [index, image] of (page.images ?? []).entries()) {
|
|
1101
|
-
|
|
1402
|
+
const source = visualImages[index];
|
|
1403
|
+
if (source) await write(visualImage(image, page.height, page.number, index, source));
|
|
1102
1404
|
}
|
|
1103
1405
|
}
|
|
1104
|
-
for (
|
|
1406
|
+
for (let spanIndex = 0; spanIndex < visualSpans.length; spanIndex += 1) {
|
|
1407
|
+
const span = visualSpans[spanIndex];
|
|
1408
|
+
if (!span) continue;
|
|
1105
1409
|
if (!usesPositionedSpan(span)) {
|
|
1106
1410
|
const type3 = span.fontAssetId ? type3Fonts.get(span.fontAssetId) : void 0;
|
|
1411
|
+
const line = !type3 && textClasses ? visualTextLine(visualSpans, spanIndex, textClasses.names, page.height, fontAliases) : void 0;
|
|
1412
|
+
if (line) {
|
|
1413
|
+
await write(line.html);
|
|
1414
|
+
spanIndex = line.endIndex;
|
|
1415
|
+
continue;
|
|
1416
|
+
}
|
|
1107
1417
|
await write(
|
|
1108
|
-
type3 ? visualType3Text(span, type3, page.height) : visualText(
|
|
1418
|
+
type3 ? visualType3Text(span, type3, page.height) : visualText(
|
|
1419
|
+
span,
|
|
1420
|
+
page.height,
|
|
1421
|
+
fontAliases,
|
|
1422
|
+
reflectedOverlay && page.rotate === 180,
|
|
1423
|
+
textClasses?.names
|
|
1424
|
+
)
|
|
1109
1425
|
);
|
|
1110
1426
|
}
|
|
1111
1427
|
}
|
|
@@ -1115,23 +1431,191 @@ async function writePositionedPage(page, write, options) {
|
|
|
1115
1431
|
}
|
|
1116
1432
|
await write("</div></section>");
|
|
1117
1433
|
}
|
|
1434
|
+
function visualPageFonts(pageNumber, fonts, documentFonts) {
|
|
1435
|
+
const aliases = visualFontAliases(pageNumber, fonts);
|
|
1436
|
+
if (!documentFonts) return { aliases, fontsToEmit: fonts };
|
|
1437
|
+
const fontsToEmit = [];
|
|
1438
|
+
for (const font of fonts) {
|
|
1439
|
+
if (!aliases.has(font.id) || font.format === "type3") continue;
|
|
1440
|
+
const existing = documentFonts.entries.find(
|
|
1441
|
+
(entry) => entry.format === font.format && equalBytes(entry.data, font.data)
|
|
1442
|
+
);
|
|
1443
|
+
if (existing) {
|
|
1444
|
+
aliases.set(font.id, existing.alias);
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
const alias = `boxpdf-document-font-${documentFonts.entries.length + 1}`;
|
|
1448
|
+
aliases.set(font.id, alias);
|
|
1449
|
+
documentFonts.entries.push({ alias, data: font.data, format: font.format });
|
|
1450
|
+
fontsToEmit.push(font);
|
|
1451
|
+
}
|
|
1452
|
+
return { aliases, fontsToEmit };
|
|
1453
|
+
}
|
|
1454
|
+
function equalBytes(left, right) {
|
|
1455
|
+
if (left.length !== right.length) return false;
|
|
1456
|
+
return left.every((value, index) => value === right[index]);
|
|
1457
|
+
}
|
|
1458
|
+
function coalesceVisualSpans(spans) {
|
|
1459
|
+
const output = [];
|
|
1460
|
+
for (const span of spans) {
|
|
1461
|
+
const previous = output.at(-1);
|
|
1462
|
+
if (!previous || !canCoalesceVisualSpans(previous, span)) {
|
|
1463
|
+
output.push(span);
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
output[output.length - 1] = {
|
|
1467
|
+
...previous,
|
|
1468
|
+
text: previous.text + span.text,
|
|
1469
|
+
bounds: {
|
|
1470
|
+
...previous.bounds,
|
|
1471
|
+
width: span.bounds.x + span.bounds.width - previous.bounds.x,
|
|
1472
|
+
height: Math.max(previous.bounds.height, span.bounds.height)
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
return output;
|
|
1477
|
+
}
|
|
1478
|
+
function canCoalesceVisualSpans(left, right) {
|
|
1479
|
+
if (usesPositionedSpan(left) || usesPositionedSpan(right)) return false;
|
|
1480
|
+
if (left.direction !== "ltr" || right.direction !== "ltr") return false;
|
|
1481
|
+
if (/guardian/i.test(left.fontFamily ?? "")) return false;
|
|
1482
|
+
if (left.glyphCodes || right.glyphCodes) return false;
|
|
1483
|
+
if (left.fontAssetId || right.fontAssetId) return false;
|
|
1484
|
+
if (!sameVisualTextState(left, right)) return false;
|
|
1485
|
+
const tolerance = Math.max(0.02, left.fontSize * 0.015);
|
|
1486
|
+
if (Math.abs(left.bounds.y - right.bounds.y) > tolerance) return false;
|
|
1487
|
+
const gap = right.bounds.x - (left.bounds.x + left.bounds.width);
|
|
1488
|
+
if (right.hasLeadingSpace) return false;
|
|
1489
|
+
if (gap >= -tolerance && gap <= tolerance) return true;
|
|
1490
|
+
return right.textAdjustmentBefore !== void 0 && right.textAdjustmentBefore < 0 && Math.abs(right.textAdjustmentBefore - gap) <= 1e-3;
|
|
1491
|
+
}
|
|
1492
|
+
function sameVisualTextState(left, right) {
|
|
1493
|
+
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);
|
|
1494
|
+
}
|
|
1495
|
+
function sameTransform(left, right) {
|
|
1496
|
+
if (!left || !right) return left === right;
|
|
1497
|
+
return left.every((value, index) => Math.abs(value - (right[index] ?? 0)) <= 1e-6);
|
|
1498
|
+
}
|
|
1499
|
+
function visualTextClasses(pageNumber, spans, fontAliases) {
|
|
1500
|
+
const names = /* @__PURE__ */ new Map();
|
|
1501
|
+
let css = "";
|
|
1502
|
+
for (const span of spans) {
|
|
1503
|
+
if (usesPositionedSpan(span) || span.glyphCodes) {
|
|
1504
|
+
continue;
|
|
1505
|
+
}
|
|
1506
|
+
const style = visualTextClassStyle(span, fontAliases);
|
|
1507
|
+
if (!style || names.has(style)) continue;
|
|
1508
|
+
const name = `boxpdf-p${number3(pageNumber)}-t${names.size + 1}`;
|
|
1509
|
+
names.set(style, name);
|
|
1510
|
+
css += `.${name}{${style}}`;
|
|
1511
|
+
}
|
|
1512
|
+
return { css, names };
|
|
1513
|
+
}
|
|
1514
|
+
function visualTextLine(spans, startIndex, styleClasses, pageHeight, fontAliases) {
|
|
1515
|
+
const first = spans[startIndex];
|
|
1516
|
+
if (!first || !canGroupVisualTextLine(first)) return void 0;
|
|
1517
|
+
const style = visualTextClassStyle(first, fontAliases);
|
|
1518
|
+
const className = styleClasses.get(style);
|
|
1519
|
+
if (!className) return void 0;
|
|
1520
|
+
let endIndex = startIndex;
|
|
1521
|
+
while (endIndex + 1 < spans.length) {
|
|
1522
|
+
const next = spans[endIndex + 1];
|
|
1523
|
+
if (!next || !canGroupVisualTextLine(next) || Math.abs(next.bounds.y - first.bounds.y) > 1e-3 || visualTextClassStyle(next, fontAliases) !== style) {
|
|
1524
|
+
break;
|
|
1525
|
+
}
|
|
1526
|
+
endIndex += 1;
|
|
1527
|
+
}
|
|
1528
|
+
if (endIndex === startIndex) return void 0;
|
|
1529
|
+
const baseline = pageHeight - first.bounds.y;
|
|
1530
|
+
const lineSpans = spans.slice(startIndex, endIndex + 1);
|
|
1531
|
+
const dxRun = visualDxTextRun(lineSpans, fontAliases);
|
|
1532
|
+
if (dxRun) {
|
|
1533
|
+
return {
|
|
1534
|
+
html: `<text class="${className}" x="${number3(first.bounds.x)}" y="${number3(baseline)}" dx="${dxRun.offsets.map(number3).join(" ")}">${escapeHtml4(dxRun.text)}</text>`,
|
|
1535
|
+
endIndex
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
const content = lineSpans.map(
|
|
1539
|
+
(span, index) => visualTextTspan(span, index > 0 ? textSpanGap(lineSpans[index - 1], span) : void 0)
|
|
1540
|
+
).join("");
|
|
1541
|
+
return {
|
|
1542
|
+
html: `<text class="${className}" x="${number3(first.bounds.x)}" y="${number3(baseline)}">${content}</text>`,
|
|
1543
|
+
endIndex
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
function visualDxTextRun(spans, fontAliases) {
|
|
1547
|
+
const first = spans[0];
|
|
1548
|
+
if (!first?.fontAssetId || !fontAliases.has(first.fontAssetId) || spans.length < 2)
|
|
1549
|
+
return void 0;
|
|
1550
|
+
let text = first.text;
|
|
1551
|
+
const offsets = characterOffsets(first, 0);
|
|
1552
|
+
for (let index = 1; index < spans.length; index += 1) {
|
|
1553
|
+
const previous = spans[index - 1];
|
|
1554
|
+
const current = spans[index];
|
|
1555
|
+
if (!previous || !current || current.fontAssetId !== first.fontAssetId) return void 0;
|
|
1556
|
+
const characters = [...current.text];
|
|
1557
|
+
if (characters.length === 0 || previous.naturalWidth === void 0) return void 0;
|
|
1558
|
+
const gap = current.bounds.x - (previous.bounds.x + previous.bounds.width);
|
|
1559
|
+
const trailingSpacing = (previous.characterSpacing ?? 0) + (previous.text.endsWith(" ") ? previous.wordSpacing ?? 0 : 0);
|
|
1560
|
+
offsets.push(...characterOffsets(current, gap + trailingSpacing));
|
|
1561
|
+
text += current.text;
|
|
1562
|
+
}
|
|
1563
|
+
while (offsets.at(-1) === 0) offsets.pop();
|
|
1564
|
+
return offsets.length > 0 ? { text, offsets } : void 0;
|
|
1565
|
+
}
|
|
1566
|
+
function characterOffsets(span, first) {
|
|
1567
|
+
const characters = [...span.text];
|
|
1568
|
+
return characters.map(
|
|
1569
|
+
(_, index) => index === 0 ? first : (span.characterSpacing ?? 0) + (characters[index - 1] === " " ? span.wordSpacing ?? 0 : 0)
|
|
1570
|
+
);
|
|
1571
|
+
}
|
|
1572
|
+
function visualTextTspan(span, dx) {
|
|
1573
|
+
const extent = span.bounds.width;
|
|
1574
|
+
const offset = dx === void 0 || number3(dx) === "0" ? "" : ` dx="${number3(dx)}"`;
|
|
1575
|
+
const length = extent > 0 ? ` textLength="${number3(extent)}" lengthAdjust="${usesSpacingAdjustment(span) ? "spacing" : "spacingAndGlyphs"}"` : "";
|
|
1576
|
+
return `<tspan${offset}${length}>${escapeHtml4(span.text)}</tspan>`;
|
|
1577
|
+
}
|
|
1578
|
+
function textSpanGap(previous, current) {
|
|
1579
|
+
if (!previous) return 0;
|
|
1580
|
+
const gap = current.bounds.x - (previous.bounds.x + previous.bounds.width);
|
|
1581
|
+
const adjustment = current.textAdjustmentBefore;
|
|
1582
|
+
return adjustment !== void 0 && Math.abs(adjustment - gap) <= 1e-3 ? adjustment : gap;
|
|
1583
|
+
}
|
|
1584
|
+
function canGroupVisualTextLine(span) {
|
|
1585
|
+
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));
|
|
1586
|
+
}
|
|
1118
1587
|
function usesReflectedVisualOverlay(page, spans) {
|
|
1119
1588
|
return Boolean(page.images?.length) && Boolean(page.paths?.length || page.fills?.length) && spans.length > 0 && spans.every(
|
|
1120
1589
|
(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
|
|
1121
1590
|
);
|
|
1122
1591
|
}
|
|
1123
|
-
function visualImage(image, pageHeight, pageNumber, imageIndex) {
|
|
1592
|
+
function visualImage(image, pageHeight, pageNumber, imageIndex, source) {
|
|
1124
1593
|
const [a, b, c, d, e, f] = image.transform;
|
|
1125
1594
|
const transform = [a, -b, -c, d, c + e, pageHeight - d - f].map(number3).join(" ");
|
|
1126
1595
|
const opacity = isUnitInterval2(image.opacity) ? ` opacity="${number3(image.opacity)}"` : "";
|
|
1127
|
-
|
|
1128
|
-
const data = image.format === "jpeg" ? image.data : rgbBmp2(image);
|
|
1129
|
-
let output = `<image width="1" height="1" preserveAspectRatio="none" transform="matrix(${transform})" href="data:${mime};base64,${base64(data)}"${opacity}/>`;
|
|
1596
|
+
let output = `<image width="1" height="1" preserveAspectRatio="none" transform="matrix(${transform})" href="${source}"${opacity}/>`;
|
|
1130
1597
|
for (let index = (image.clips?.length ?? 0) - 1; index >= 0; index -= 1) {
|
|
1131
1598
|
output = `<g clip-path="url(#${imageClipId(pageNumber, imageIndex, index)})">${output}</g>`;
|
|
1132
1599
|
}
|
|
1133
1600
|
return output;
|
|
1134
1601
|
}
|
|
1602
|
+
async function prepareVisualImages(page, imageOptions, onImage) {
|
|
1603
|
+
if (imageOptions === "excluded") return [];
|
|
1604
|
+
const sources = [];
|
|
1605
|
+
for (const [index, image] of (page.images ?? []).entries()) {
|
|
1606
|
+
const mimeType = image.format === "jpeg" ? "image/jpeg" : "image/bmp";
|
|
1607
|
+
const data = image.format === "jpeg" ? image.data : rgbBmp2(image);
|
|
1608
|
+
if (imageOptions === "embedded") {
|
|
1609
|
+
sources.push(`data:${mimeType};base64,${base64(data)}`);
|
|
1610
|
+
continue;
|
|
1611
|
+
}
|
|
1612
|
+
const extension = image.format === "jpeg" ? "jpg" : "bmp";
|
|
1613
|
+
const name = `page-${page.number}-image-${index + 1}.${extension}`;
|
|
1614
|
+
await onImage?.({ name, mimeType, data });
|
|
1615
|
+
sources.push(name);
|
|
1616
|
+
}
|
|
1617
|
+
return sources;
|
|
1618
|
+
}
|
|
1135
1619
|
function imageClipDefinitions(images, pageNumber, pageHeight) {
|
|
1136
1620
|
return images.flatMap(
|
|
1137
1621
|
(image, imageIndex) => (image.clips ?? []).map((clip, clipIndex) => {
|
|
@@ -1198,8 +1682,9 @@ function positionedSpan(span, fontAliases) {
|
|
|
1198
1682
|
].join(";");
|
|
1199
1683
|
return `<span class="pdf-span"${direction} style="${style}">${escapeHtml4(span.text)}</span>`;
|
|
1200
1684
|
}
|
|
1201
|
-
async function writeFlowPage(page, write) {
|
|
1202
|
-
const
|
|
1685
|
+
async function writeFlowPage(page, write, options) {
|
|
1686
|
+
const imageOptions = resolveImageOptions("semantic", options);
|
|
1687
|
+
const media = await prepareSemanticMedia(page, imageOptions, options.onImage);
|
|
1203
1688
|
const structured = structurePage2(withoutSemanticMediaSpans(page, media));
|
|
1204
1689
|
const defaultColor = dominantTextColor(structured.lines);
|
|
1205
1690
|
let mediaIndex = 0;
|
|
@@ -1336,10 +1821,37 @@ function semanticBlockY2(block) {
|
|
|
1336
1821
|
const lines = block.type === "list" ? block.items.flatMap((item) => item.lines) : block.lines;
|
|
1337
1822
|
return Math.max(...lines.map((line) => line.bounds.y + line.bounds.height));
|
|
1338
1823
|
}
|
|
1339
|
-
function visualText(span, pageHeight, fontAliases, counterRotateReflectedText = false) {
|
|
1824
|
+
function visualText(span, pageHeight, fontAliases, counterRotateReflectedText = false, styleClasses) {
|
|
1340
1825
|
if (span.renderingMode === 3 || span.renderingMode === 7) return "";
|
|
1341
1826
|
if (!span.fontAssetId && isAdobeCjkFont(span.fontFamily)) return "";
|
|
1342
1827
|
const direction = directionAttribute([span]);
|
|
1828
|
+
const style = visualTextStyle(span, fontAliases);
|
|
1829
|
+
const styleClass = styleClasses?.get(visualTextClassStyle(span, fontAliases));
|
|
1830
|
+
const presentation = styleClass ? ` class="${styleClass}"` : style ? ` style="${style}"` : "";
|
|
1831
|
+
const fontSize = styleClass ? "" : ` font-size="${number3(span.fontSize)}"`;
|
|
1832
|
+
const textExtent = span.direction === "ttb" ? span.bounds.height : span.bounds.width;
|
|
1833
|
+
const textLength = textExtent > 0 && !isHebrewPaintOrder(span) ? ` textLength="${number3(textExtent)}" lengthAdjust="${span.direction === "ttb" || usesSpacingAdjustment(span) ? "spacing" : "spacingAndGlyphs"}"` : "";
|
|
1834
|
+
const transform = counterRotateReflectedText && span.transform ? [
|
|
1835
|
+
span.transform[0],
|
|
1836
|
+
span.transform[1],
|
|
1837
|
+
span.transform[2],
|
|
1838
|
+
-span.transform[3]
|
|
1839
|
+
] : span.transform;
|
|
1840
|
+
const transformed = hasNonIdentityTransform(transform);
|
|
1841
|
+
const rtlOffset = span.direction === "rtl" ? span.bounds.width : 0;
|
|
1842
|
+
const basisX = transform?.[0] ?? 1;
|
|
1843
|
+
const basisY = transform?.[1] ?? 0;
|
|
1844
|
+
const anchorX = span.bounds.x + basisX * rtlOffset;
|
|
1845
|
+
const anchorY = pageHeight - span.bounds.y + basisY * rtlOffset;
|
|
1846
|
+
const position = transformed ? ` x="0" y="0" transform="matrix(${transform?.map(number3).join(" ")} ${number3(anchorX)} ${number3(anchorY)})"` : ` x="${number3(anchorX)}" y="${number3(anchorY)}"`;
|
|
1847
|
+
return `<text${direction}${position}${fontSize}${textLength}${presentation}>${escapeHtml4(span.text)}</text>`;
|
|
1848
|
+
}
|
|
1849
|
+
function visualTextClassStyle(span, fontAliases) {
|
|
1850
|
+
const style = visualTextStyle(span, fontAliases);
|
|
1851
|
+
const fontSize = `font-size:${number3(span.fontSize)}px`;
|
|
1852
|
+
return style ? `${style};${fontSize}` : fontSize;
|
|
1853
|
+
}
|
|
1854
|
+
function visualTextStyle(span, fontAliases) {
|
|
1343
1855
|
const font = visualFontStyles(
|
|
1344
1856
|
span.fontFamily,
|
|
1345
1857
|
span.fontAssetId ? fontAliases.get(span.fontAssetId) : void 0
|
|
@@ -1349,7 +1861,7 @@ function visualText(span, pageHeight, fontAliases, counterRotateReflectedText =
|
|
|
1349
1861
|
const strokeOnly = span.renderingMode === 1 || span.renderingMode === 5;
|
|
1350
1862
|
const fillOpacity = isUnitInterval2(span.fillOpacity) ? `fill-opacity:${number3(span.fillOpacity)}` : "";
|
|
1351
1863
|
const strokeOpacity = isUnitInterval2(span.strokeOpacity) ? `stroke-opacity:${number3(span.strokeOpacity)}` : "";
|
|
1352
|
-
|
|
1864
|
+
return [
|
|
1353
1865
|
isHebrewPaintOrder(span) ? "unicode-bidi:bidi-override;direction:ltr" : "",
|
|
1354
1866
|
span.direction === "ttb" ? "writing-mode:vertical-rl" : "",
|
|
1355
1867
|
strokeOnly ? "fill:none" : isCssHexColor2(span.color) ? `fill:${span.color}` : "",
|
|
@@ -1359,22 +1871,6 @@ function visualText(span, pageHeight, fontAliases, counterRotateReflectedText =
|
|
|
1359
1871
|
strokeOpacity,
|
|
1360
1872
|
font
|
|
1361
1873
|
].filter(Boolean).join(";");
|
|
1362
|
-
const textExtent = span.direction === "ttb" ? span.bounds.height : span.bounds.width;
|
|
1363
|
-
const textLength = textExtent > 0 && !isHebrewPaintOrder(span) ? ` textLength="${number3(textExtent)}" lengthAdjust="${span.direction === "ttb" || usesSpacingAdjustment(span) ? "spacing" : "spacingAndGlyphs"}"` : "";
|
|
1364
|
-
const transform = counterRotateReflectedText && span.transform ? [
|
|
1365
|
-
span.transform[0],
|
|
1366
|
-
span.transform[1],
|
|
1367
|
-
span.transform[2],
|
|
1368
|
-
-span.transform[3]
|
|
1369
|
-
] : span.transform;
|
|
1370
|
-
const transformed = hasNonIdentityTransform(transform);
|
|
1371
|
-
const rtlOffset = span.direction === "rtl" ? span.bounds.width : 0;
|
|
1372
|
-
const basisX = transform?.[0] ?? 1;
|
|
1373
|
-
const basisY = transform?.[1] ?? 0;
|
|
1374
|
-
const anchorX = span.bounds.x + basisX * rtlOffset;
|
|
1375
|
-
const anchorY = pageHeight - span.bounds.y + basisY * rtlOffset;
|
|
1376
|
-
const position = transformed ? ` x="0" y="0" transform="matrix(${transform?.map(number3).join(" ")} ${number3(anchorX)} ${number3(anchorY)})"` : ` x="${number3(anchorX)}" y="${number3(anchorY)}"`;
|
|
1377
|
-
return `<text${direction}${position} font-size="${number3(span.fontSize)}"${textLength}${style ? ` style="${style}"` : ""}>${escapeHtml4(span.text)}</text>`;
|
|
1378
1874
|
}
|
|
1379
1875
|
function isAdobeCjkFont(fontFamily) {
|
|
1380
1876
|
return /^Adobe(?:Heiti|Song|Kaiti|Ming|Gothic|Mincho)Std-/i.test(fontFamily ?? "");
|
|
@@ -1469,9 +1965,18 @@ function resolveProfile(options) {
|
|
|
1469
1965
|
}
|
|
1470
1966
|
return options.profile ?? legacyProfile;
|
|
1471
1967
|
}
|
|
1968
|
+
function resolveImageOptions(profile, options) {
|
|
1969
|
+
return options.imageOptions ?? (profile === "semantic" ? "excluded" : "embedded");
|
|
1970
|
+
}
|
|
1971
|
+
function validateImageOptions(imageOptions, options) {
|
|
1972
|
+
if (imageOptions === "references" && !options.onImage) {
|
|
1973
|
+
throw new Error('imageOptions "references" requires an onImage callback');
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1472
1976
|
export {
|
|
1473
1977
|
pageToHtml,
|
|
1474
1978
|
writeHtmlDocument,
|
|
1979
|
+
writeMarkdownDocument,
|
|
1475
1980
|
writePage
|
|
1476
1981
|
};
|
|
1477
1982
|
//# sourceMappingURL=index.js.map
|