@bendyline/squisq-formats 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-6OVREALI.js → chunk-CLGUOVYR.js} +163 -11
- package/dist/chunk-CLGUOVYR.js.map +1 -0
- package/dist/{chunk-PP5N46YD.js → chunk-S3Y7H2BK.js} +129 -1
- package/dist/chunk-S3Y7H2BK.js.map +1 -0
- package/dist/docx/export.d.ts +9 -0
- package/dist/docx/export.d.ts.map +1 -1
- package/dist/docx/export.js +120 -12
- package/dist/docx/export.js.map +1 -1
- package/dist/docx/import.d.ts +13 -0
- package/dist/docx/import.d.ts.map +1 -1
- package/dist/docx/import.js +102 -10
- package/dist/docx/import.js.map +1 -1
- package/dist/docx/index.d.ts +1 -1
- package/dist/docx/index.d.ts.map +1 -1
- package/dist/docx/index.js +1 -1
- package/dist/docx/index.js.map +1 -1
- package/dist/pdf/import.d.ts +16 -0
- package/dist/pdf/import.d.ts.map +1 -1
- package/dist/pdf/import.js +177 -0
- package/dist/pdf/import.js.map +1 -1
- package/dist/pdf/index.d.ts +1 -1
- package/dist/pdf/index.d.ts.map +1 -1
- package/dist/pdf/index.js +1 -1
- package/dist/pdf/index.js.map +1 -1
- package/package.json +2 -2
- package/src/docx/export.ts +147 -12
- package/src/docx/import.ts +127 -10
- package/src/docx/index.ts +1 -1
- package/src/pdf/import.ts +228 -0
- package/src/pdf/index.ts +1 -1
- package/dist/chunk-6OVREALI.js.map +0 -1
- package/dist/chunk-PP5N46YD.js.map +0 -1
|
@@ -711,6 +711,7 @@ function hexToRgb(hex) {
|
|
|
711
711
|
|
|
712
712
|
// src/pdf/import.ts
|
|
713
713
|
import { markdownToDoc } from "@bendyline/squisq/doc";
|
|
714
|
+
import { MemoryContentContainer } from "@bendyline/squisq/storage";
|
|
714
715
|
async function pdfToMarkdownDoc(data, options = {}) {
|
|
715
716
|
const bytes = data instanceof Blob ? new Uint8Array(await data.arrayBuffer()) : data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
716
717
|
const textLines = await extractTextLines(bytes);
|
|
@@ -725,6 +726,132 @@ async function pdfToDoc(data, options = {}) {
|
|
|
725
726
|
const markdownDoc = await pdfToMarkdownDoc(data, options);
|
|
726
727
|
return markdownToDoc(markdownDoc);
|
|
727
728
|
}
|
|
729
|
+
async function pdfToContainer(data, options = {}) {
|
|
730
|
+
const bytes = data instanceof Blob ? new Uint8Array(await data.arrayBuffer()) : data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
731
|
+
const textLines = await extractTextLines(bytes);
|
|
732
|
+
const images = await extractImages(bytes);
|
|
733
|
+
const bodySize = options.bodyFontSize ?? detectBodyFontSize(textLines);
|
|
734
|
+
let blocks = classifyLines(textLines, bodySize, options);
|
|
735
|
+
if (images.length > 0) {
|
|
736
|
+
blocks = insertImageBlocks(blocks, images);
|
|
737
|
+
}
|
|
738
|
+
const markdownDoc = { type: "document", children: blocks };
|
|
739
|
+
const { stringifyMarkdown } = await import("@bendyline/squisq/markdown");
|
|
740
|
+
const markdown = stringifyMarkdown(markdownDoc);
|
|
741
|
+
const container = new MemoryContentContainer();
|
|
742
|
+
await container.writeDocument(markdown);
|
|
743
|
+
for (const img of images) {
|
|
744
|
+
await container.writeFile(img.path, new Uint8Array(img.data), "image/png");
|
|
745
|
+
}
|
|
746
|
+
return container;
|
|
747
|
+
}
|
|
748
|
+
async function extractImages(data) {
|
|
749
|
+
if (typeof document === "undefined") return [];
|
|
750
|
+
let pdfjsLib;
|
|
751
|
+
try {
|
|
752
|
+
pdfjsLib = await import("pdfjs-dist/legacy/build/pdf.mjs");
|
|
753
|
+
} catch {
|
|
754
|
+
pdfjsLib = await import("pdfjs-dist");
|
|
755
|
+
}
|
|
756
|
+
await applyWorkerConfig(pdfjsLib);
|
|
757
|
+
const OPS_paintImageXObject = pdfjsLib.OPS?.paintImageXObject ?? 85;
|
|
758
|
+
const loadingTask = pdfjsLib.getDocument({
|
|
759
|
+
data,
|
|
760
|
+
isEvalSupported: false,
|
|
761
|
+
useSystemFonts: true
|
|
762
|
+
});
|
|
763
|
+
const pdf = await loadingTask.promise;
|
|
764
|
+
const images = [];
|
|
765
|
+
let counter = 0;
|
|
766
|
+
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
|
|
767
|
+
const page = await pdf.getPage(pageNum);
|
|
768
|
+
if (!page.getOperatorList) continue;
|
|
769
|
+
const opList = await page.getOperatorList();
|
|
770
|
+
const seen = /* @__PURE__ */ new Set();
|
|
771
|
+
for (let i = 0; i < opList.fnArray.length; i++) {
|
|
772
|
+
if (opList.fnArray[i] !== OPS_paintImageXObject) continue;
|
|
773
|
+
const imgName = opList.argsArray[i]?.[0];
|
|
774
|
+
if (!imgName || typeof imgName !== "string" || seen.has(imgName)) continue;
|
|
775
|
+
seen.add(imgName);
|
|
776
|
+
try {
|
|
777
|
+
const imgData = page.objs?.get(imgName);
|
|
778
|
+
if (!imgData?.data || !imgData.width || !imgData.height) continue;
|
|
779
|
+
const pngData = imageDataToPng(imgData);
|
|
780
|
+
if (!pngData) continue;
|
|
781
|
+
counter++;
|
|
782
|
+
images.push({
|
|
783
|
+
path: `images/image${counter}.png`,
|
|
784
|
+
data: pngData,
|
|
785
|
+
page: pageNum - 1,
|
|
786
|
+
y: 0
|
|
787
|
+
});
|
|
788
|
+
} catch {
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return images;
|
|
793
|
+
}
|
|
794
|
+
function imageDataToPng(img) {
|
|
795
|
+
try {
|
|
796
|
+
const canvas = document.createElement("canvas");
|
|
797
|
+
canvas.width = img.width;
|
|
798
|
+
canvas.height = img.height;
|
|
799
|
+
const ctx = canvas.getContext("2d");
|
|
800
|
+
if (!ctx) return null;
|
|
801
|
+
let imageData;
|
|
802
|
+
if (img.kind === 3 || img.data.length === img.width * img.height * 4) {
|
|
803
|
+
imageData = new ImageData(new Uint8ClampedArray(img.data), img.width, img.height);
|
|
804
|
+
} else if (img.kind === 2 || img.data.length === img.width * img.height * 3) {
|
|
805
|
+
const rgba = new Uint8ClampedArray(img.width * img.height * 4);
|
|
806
|
+
for (let j = 0, k = 0; j < img.data.length; j += 3, k += 4) {
|
|
807
|
+
rgba[k] = img.data[j];
|
|
808
|
+
rgba[k + 1] = img.data[j + 1];
|
|
809
|
+
rgba[k + 2] = img.data[j + 2];
|
|
810
|
+
rgba[k + 3] = 255;
|
|
811
|
+
}
|
|
812
|
+
imageData = new ImageData(rgba, img.width, img.height);
|
|
813
|
+
} else if (img.kind === 1 || img.data.length === img.width * img.height) {
|
|
814
|
+
const rgba = new Uint8ClampedArray(img.width * img.height * 4);
|
|
815
|
+
for (let j = 0, k = 0; j < img.data.length; j++, k += 4) {
|
|
816
|
+
rgba[k] = img.data[j];
|
|
817
|
+
rgba[k + 1] = img.data[j];
|
|
818
|
+
rgba[k + 2] = img.data[j];
|
|
819
|
+
rgba[k + 3] = 255;
|
|
820
|
+
}
|
|
821
|
+
imageData = new ImageData(rgba, img.width, img.height);
|
|
822
|
+
} else {
|
|
823
|
+
return null;
|
|
824
|
+
}
|
|
825
|
+
ctx.putImageData(imageData, 0, 0);
|
|
826
|
+
const dataUrl = canvas.toDataURL("image/png");
|
|
827
|
+
const base64 = dataUrl.split(",")[1];
|
|
828
|
+
const binaryStr = atob(base64);
|
|
829
|
+
const bytes = new Uint8Array(binaryStr.length);
|
|
830
|
+
for (let i = 0; i < binaryStr.length; i++) {
|
|
831
|
+
bytes[i] = binaryStr.charCodeAt(i);
|
|
832
|
+
}
|
|
833
|
+
return bytes.buffer;
|
|
834
|
+
} catch {
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
function insertImageBlocks(blocks, images) {
|
|
839
|
+
if (images.length === 0) return blocks;
|
|
840
|
+
const result = [...blocks];
|
|
841
|
+
for (const img of images) {
|
|
842
|
+
const imgNode = {
|
|
843
|
+
type: "image",
|
|
844
|
+
url: img.path,
|
|
845
|
+
alt: `Image ${img.path.replace("images/image", "").replace(".png", "")}`
|
|
846
|
+
};
|
|
847
|
+
const para = {
|
|
848
|
+
type: "paragraph",
|
|
849
|
+
children: [imgNode]
|
|
850
|
+
};
|
|
851
|
+
result.push(para);
|
|
852
|
+
}
|
|
853
|
+
return result;
|
|
854
|
+
}
|
|
728
855
|
function configurePdfWorker(workerSrc) {
|
|
729
856
|
_workerSrc = workerSrc;
|
|
730
857
|
}
|
|
@@ -1153,6 +1280,7 @@ export {
|
|
|
1153
1280
|
docToPdf,
|
|
1154
1281
|
pdfToMarkdownDoc,
|
|
1155
1282
|
pdfToDoc,
|
|
1283
|
+
pdfToContainer,
|
|
1156
1284
|
configurePdfWorker
|
|
1157
1285
|
};
|
|
1158
|
-
//# sourceMappingURL=chunk-
|
|
1286
|
+
//# sourceMappingURL=chunk-S3Y7H2BK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/pdf/export.ts","../src/pdf/styles.ts","../src/pdf/import.ts"],"sourcesContent":["/**\n * PDF Export\n *\n * Converts a squisq MarkdownDocument (or Doc) into a PDF file\n * using pdf-lib. Generates paginated, styled output with support for\n * headings, paragraphs, inline formatting, lists, code blocks,\n * blockquotes, tables, thematic breaks, and hyperlinks.\n *\n * Uses only the 14 standard PDF fonts (no font embedding required),\n * keeping output size small and rendering fast.\n *\n * @example\n * ```ts\n * import { parseMarkdown } from '@bendyline/squisq/markdown';\n * import { markdownDocToPdf } from '@bendyline/squisq-formats/pdf';\n *\n * const md = parseMarkdown('# Hello\\n\\nWorld **bold** text');\n * const buffer = await markdownDocToPdf(md);\n * ```\n */\n\nimport { PDFDocument, StandardFonts, rgb, PDFFont, PDFPage } from 'pdf-lib';\n\nimport type { Doc } from '@bendyline/squisq/schemas';\nimport { resolveTheme } from '@bendyline/squisq/schemas';\nimport { docToMarkdown } from '@bendyline/squisq/doc';\nimport type {\n MarkdownDocument,\n MarkdownBlockNode,\n MarkdownInlineNode,\n MarkdownHeading,\n MarkdownParagraph,\n MarkdownBlockquote,\n MarkdownList,\n MarkdownListItem,\n MarkdownCodeBlock,\n MarkdownTable,\n MarkdownTableRow,\n MarkdownTableCell,\n MarkdownHtmlBlock,\n MarkdownMathBlock,\n MarkdownFootnoteDefinition,\n MarkdownText,\n MarkdownEmphasis,\n MarkdownStrong,\n MarkdownStrikethrough,\n MarkdownInlineCode,\n MarkdownLink,\n MarkdownImage,\n MarkdownInlineHtml,\n MarkdownInlineMath,\n MarkdownFootnoteReference,\n} from '@bendyline/squisq/markdown';\n\nimport {\n PAGE_WIDTH_LETTER,\n PAGE_HEIGHT_LETTER,\n PAGE_WIDTH_A4,\n PAGE_HEIGHT_A4,\n DEFAULT_MARGIN,\n DEFAULT_FONT_SIZE,\n HEADING_SIZES,\n CODE_FONT_SIZE,\n LINE_HEIGHT_FACTOR,\n HEADING_SPACE_BEFORE,\n HEADING_SPACE_AFTER,\n PARAGRAPH_SPACING,\n LIST_INDENT,\n BULLET_CHAR,\n BLOCKQUOTE_INDENT,\n BLOCKQUOTE_BAR_WIDTH,\n COLOR_TEXT,\n COLOR_HEADING,\n COLOR_LINK,\n COLOR_CODE_TEXT,\n COLOR_BLOCKQUOTE_BAR,\n COLOR_BLOCKQUOTE_TEXT,\n COLOR_THEMATIC_BREAK,\n COLOR_TABLE_BORDER,\n COLOR_TABLE_HEADER_BG,\n TABLE_CELL_PAD_X,\n TABLE_CELL_PAD_Y,\n TABLE_BORDER_WIDTH,\n} from './styles.js';\n\n// ============================================\n// Public API\n// ============================================\n\n/** Page size presets. */\nexport type PdfPageSize = 'letter' | 'a4';\n\n/**\n * Options for PDF export.\n */\nexport interface PdfExportOptions {\n /** Document title (PDF metadata). */\n title?: string;\n /** Document author (PDF metadata). */\n author?: string;\n /** Page size preset. Default: \"letter\". */\n pageSize?: PdfPageSize;\n /** Page margins in points. Default: 72 (1 inch). */\n margin?: number;\n /** Default body font size in points. Default: 11. */\n defaultFontSize?: number;\n /**\n * Squisq theme ID to apply (e.g., 'documentary', 'cinematic').\n * When set, overrides heading, text, and link colors with the theme palette.\n * Font changes are not supported (pdf-lib uses standard 14 PDF fonts only).\n */\n themeId?: string;\n}\n\n/**\n * Convert a MarkdownDocument to a PDF ArrayBuffer.\n */\nexport async function markdownDocToPdf(\n doc: MarkdownDocument,\n options: PdfExportOptions = {},\n): Promise<ArrayBuffer> {\n const pdfDoc = await PDFDocument.create();\n\n // Metadata\n if (options.title) pdfDoc.setTitle(options.title);\n if (options.author) pdfDoc.setAuthor(options.author);\n pdfDoc.setCreationDate(new Date());\n pdfDoc.setModificationDate(new Date());\n\n const ctx = await createExportContext(pdfDoc, options);\n\n renderBlocks(doc.children, ctx, 0);\n\n const bytes = await pdfDoc.save();\n return bytes.buffer as ArrayBuffer;\n}\n\n/**\n * Convert a squisq Doc to a PDF ArrayBuffer.\n *\n * Convenience wrapper: Doc → MarkdownDocument → PDF.\n */\nexport async function docToPdf(doc: Doc, options: PdfExportOptions = {}): Promise<ArrayBuffer> {\n const markdownDoc = docToMarkdown(doc);\n return markdownDocToPdf(markdownDoc, options);\n}\n\n// ============================================\n// Export Context — tracks cursor, fonts, pages\n// ============================================\n\ninterface FontSet {\n regular: PDFFont;\n bold: PDFFont;\n italic: PDFFont;\n boldItalic: PDFFont;\n mono: PDFFont;\n monoBold: PDFFont;\n}\n\ninterface RgbColor {\n r: number;\n g: number;\n b: number;\n}\n\ninterface ExportContext {\n pdfDoc: PDFDocument;\n fonts: FontSet;\n pageWidth: number;\n pageHeight: number;\n margin: number;\n fontSize: number;\n /** Current page being drawn on. */\n page: PDFPage;\n /** Current y position (from top of page, decreasing). */\n y: number;\n /** Content area width = pageWidth - 2*margin. */\n contentWidth: number;\n /** Bottom margin y position. */\n bottomY: number;\n /** Resolved colors (may be overridden by theme). */\n colors: {\n text: RgbColor;\n heading: RgbColor;\n link: RgbColor;\n };\n}\n\nasync function createExportContext(\n pdfDoc: PDFDocument,\n options: PdfExportOptions,\n): Promise<ExportContext> {\n const [regular, bold, italic, boldItalic, mono, monoBold] = await Promise.all([\n pdfDoc.embedFont(StandardFonts.Helvetica),\n pdfDoc.embedFont(StandardFonts.HelveticaBold),\n pdfDoc.embedFont(StandardFonts.HelveticaOblique),\n pdfDoc.embedFont(StandardFonts.HelveticaBoldOblique),\n pdfDoc.embedFont(StandardFonts.Courier),\n pdfDoc.embedFont(StandardFonts.CourierBold),\n ]);\n\n const isA4 = options.pageSize === 'a4';\n const pageWidth = isA4 ? PAGE_WIDTH_A4 : PAGE_WIDTH_LETTER;\n const pageHeight = isA4 ? PAGE_HEIGHT_A4 : PAGE_HEIGHT_LETTER;\n const margin = options.margin ?? DEFAULT_MARGIN;\n const fontSize = options.defaultFontSize ?? DEFAULT_FONT_SIZE;\n\n const page = pdfDoc.addPage([pageWidth, pageHeight]);\n\n // Resolve theme colors if themeId is set\n let colorText = COLOR_TEXT;\n let colorHeading = COLOR_HEADING;\n let colorLink = COLOR_LINK;\n\n if (options.themeId) {\n const theme = resolveTheme(options.themeId);\n if (theme.colors) {\n colorText = hexToRgb(theme.colors.text) ?? COLOR_TEXT;\n colorHeading = hexToRgb(theme.colors.primary) ?? COLOR_HEADING;\n colorLink = hexToRgb(theme.colors.highlight || theme.colors.secondary) ?? COLOR_LINK;\n }\n }\n\n return {\n pdfDoc,\n fonts: { regular, bold, italic, boldItalic, mono, monoBold },\n pageWidth,\n pageHeight,\n margin,\n fontSize,\n page,\n y: pageHeight - margin,\n contentWidth: pageWidth - 2 * margin,\n bottomY: margin,\n colors: { text: colorText, heading: colorHeading, link: colorLink },\n };\n}\n\n// ============================================\n// Page Break Management\n// ============================================\n\nfunction ensureSpace(ctx: ExportContext, needed: number): void {\n if (ctx.y - needed < ctx.bottomY) {\n newPage(ctx);\n }\n}\n\nfunction newPage(ctx: ExportContext): void {\n const page = ctx.pdfDoc.addPage([ctx.pageWidth, ctx.pageHeight]);\n ctx.page = page;\n ctx.y = ctx.pageHeight - ctx.margin;\n}\n\n// ============================================\n// Inline \"Span\" Model\n// ============================================\n\ninterface TextSpan {\n text: string;\n font: PDFFont;\n fontSize: number;\n color: { r: number; g: number; b: number };\n link?: string;\n strikethrough?: boolean;\n}\n\n/**\n * Flatten an inline node tree into a flat list of TextSpans,\n * accumulating bold/italic/code state as we recurse.\n */\nfunction flattenInlines(\n nodes: MarkdownInlineNode[],\n ctx: ExportContext,\n state: {\n bold: boolean;\n italic: boolean;\n code: boolean;\n link?: string;\n color?: { r: number; g: number; b: number };\n strikethrough?: boolean;\n },\n): TextSpan[] {\n const spans: TextSpan[] = [];\n\n for (const node of nodes) {\n switch (node.type) {\n case 'text': {\n const font = state.code\n ? state.bold\n ? ctx.fonts.monoBold\n : ctx.fonts.mono\n : pickFont(ctx, state.bold, state.italic);\n spans.push({\n text: (node as MarkdownText).value,\n font,\n fontSize: state.code ? CODE_FONT_SIZE : ctx.fontSize,\n color: state.code ? COLOR_CODE_TEXT : (state.color ?? ctx.colors.text),\n link: state.link,\n strikethrough: state.strikethrough,\n });\n break;\n }\n\n case 'strong':\n spans.push(\n ...flattenInlines((node as MarkdownStrong).children, ctx, { ...state, bold: true }),\n );\n break;\n\n case 'emphasis':\n spans.push(\n ...flattenInlines((node as MarkdownEmphasis).children, ctx, { ...state, italic: true }),\n );\n break;\n\n case 'delete':\n spans.push(\n ...flattenInlines((node as MarkdownStrikethrough).children, ctx, {\n ...state,\n strikethrough: true,\n }),\n );\n break;\n\n case 'inlineCode': {\n spans.push({\n text: (node as MarkdownInlineCode).value,\n font: ctx.fonts.mono,\n fontSize: CODE_FONT_SIZE,\n color: COLOR_CODE_TEXT,\n link: state.link,\n });\n break;\n }\n\n case 'link': {\n const linkNode = node as MarkdownLink;\n spans.push(\n ...flattenInlines(linkNode.children, ctx, {\n ...state,\n link: linkNode.url,\n color: ctx.colors.link,\n }),\n );\n break;\n }\n\n case 'image': {\n const imgNode = node as MarkdownImage;\n spans.push({\n text: imgNode.alt ? `[Image: ${imgNode.alt}]` : '[Image]',\n font: ctx.fonts.italic,\n fontSize: ctx.fontSize,\n color: COLOR_BLOCKQUOTE_TEXT,\n });\n break;\n }\n\n case 'break':\n spans.push({\n text: '\\n',\n font: ctx.fonts.regular,\n fontSize: ctx.fontSize,\n color: ctx.colors.text,\n });\n break;\n\n case 'htmlInline': {\n const html = (node as MarkdownInlineHtml).rawHtml;\n if (html) {\n spans.push({\n text: html,\n font: ctx.fonts.mono,\n fontSize: CODE_FONT_SIZE,\n color: COLOR_CODE_TEXT,\n });\n }\n break;\n }\n\n case 'inlineMath':\n spans.push({\n text: (node as MarkdownInlineMath).value,\n font: ctx.fonts.mono,\n fontSize: CODE_FONT_SIZE,\n color: COLOR_CODE_TEXT,\n });\n break;\n\n case 'footnoteReference': {\n const ref = node as MarkdownFootnoteReference;\n spans.push({\n text: `[${ref.identifier}]`,\n font: ctx.fonts.regular,\n fontSize: ctx.fontSize * 0.75,\n color: ctx.colors.link,\n });\n break;\n }\n\n // linkReference, imageReference, textDirective — render children or identifier\n default: {\n const fallback = node as unknown as { children?: MarkdownInlineNode[]; value?: string };\n if (fallback.children) {\n spans.push(...flattenInlines(fallback.children, ctx, state));\n } else if (fallback.value) {\n spans.push({\n text: fallback.value,\n font: pickFont(ctx, state.bold, state.italic),\n fontSize: ctx.fontSize,\n color: state.color ?? ctx.colors.text,\n });\n }\n break;\n }\n }\n }\n\n return spans;\n}\n\nfunction pickFont(ctx: ExportContext, bold: boolean, italic: boolean): PDFFont {\n if (bold && italic) return ctx.fonts.boldItalic;\n if (bold) return ctx.fonts.bold;\n if (italic) return ctx.fonts.italic;\n return ctx.fonts.regular;\n}\n\n// ============================================\n// Word-Wrap & Draw\n// ============================================\n\n/**\n * Word-wraps and draws a flat list of TextSpans within the given\n * available width, starting at ctx.y. Updates ctx.y after drawing.\n * Returns the y position after the last line.\n */\nfunction drawSpans(\n spans: TextSpan[],\n ctx: ExportContext,\n availableWidth: number,\n x0: number,\n): void {\n if (spans.length === 0) return;\n\n // Split spans at \\n and word boundaries, then wrap into lines\n const lines = wrapSpans(spans, availableWidth);\n\n for (const line of lines) {\n const lineHeight = getLineHeight(line);\n ensureSpace(ctx, lineHeight);\n\n let x = x0;\n for (const span of line) {\n ctx.page.drawText(span.text, {\n x,\n y: ctx.y - span.fontSize, // pdf-lib y is baseline\n size: span.fontSize,\n font: span.font,\n color: rgb(span.color.r, span.color.g, span.color.b),\n });\n\n const textWidth = span.font.widthOfTextAtSize(span.text, span.fontSize);\n\n // Underline for links\n if (span.link) {\n ctx.page.drawLine({\n start: { x, y: ctx.y - span.fontSize - 1 },\n end: { x: x + textWidth, y: ctx.y - span.fontSize - 1 },\n thickness: 0.5,\n color: rgb(span.color.r, span.color.g, span.color.b),\n });\n }\n\n // Strikethrough\n if (span.strikethrough) {\n const midY = ctx.y - span.fontSize * 0.6;\n ctx.page.drawLine({\n start: { x, y: midY },\n end: { x: x + textWidth, y: midY },\n thickness: 0.5,\n color: rgb(span.color.r, span.color.g, span.color.b),\n });\n }\n\n x += textWidth;\n }\n\n ctx.y -= lineHeight;\n }\n}\n\n/**\n * Break a flat list of spans into wrapped lines that fit within\n * `maxWidth`. Respects explicit \\n characters.\n */\nfunction wrapSpans(spans: TextSpan[], maxWidth: number): TextSpan[][] {\n const lines: TextSpan[][] = [];\n let currentLine: TextSpan[] = [];\n let lineWidth = 0;\n\n for (const span of spans) {\n // Handle explicit newlines\n if (span.text === '\\n') {\n lines.push(currentLine);\n currentLine = [];\n lineWidth = 0;\n continue;\n }\n\n // Split by whitespace for word-wrapping\n const words = splitIntoWords(span.text);\n\n for (const word of words) {\n const wordWidth = span.font.widthOfTextAtSize(word, span.fontSize);\n\n if (lineWidth + wordWidth > maxWidth && currentLine.length > 0 && word.trim().length > 0) {\n // Wrap to next line\n lines.push(currentLine);\n currentLine = [];\n lineWidth = 0;\n }\n\n // Trim leading space on new line\n const trimmedWord = currentLine.length === 0 ? word.replace(/^\\s+/, '') : word;\n if (trimmedWord.length > 0) {\n const tw = span.font.widthOfTextAtSize(trimmedWord, span.fontSize);\n currentLine.push({ ...span, text: trimmedWord });\n lineWidth += tw;\n }\n }\n }\n\n if (currentLine.length > 0) {\n lines.push(currentLine);\n }\n\n return lines.length > 0 ? lines : [[]];\n}\n\n/**\n * Split text into \"words\" preserving whitespace as separate tokens\n * so the wrapping logic can break at whitespace.\n */\nfunction splitIntoWords(text: string): string[] {\n const tokens: string[] = [];\n let current = '';\n let inSpace = false;\n\n for (const ch of text) {\n const isSpace = ch === ' ' || ch === '\\t';\n if (isSpace !== inSpace && current.length > 0) {\n tokens.push(current);\n current = '';\n }\n current += ch;\n inSpace = isSpace;\n }\n if (current.length > 0) tokens.push(current);\n return tokens;\n}\n\nfunction getLineHeight(line: TextSpan[]): number {\n let maxSize = 0;\n for (const span of line) {\n if (span.fontSize > maxSize) maxSize = span.fontSize;\n }\n return (maxSize || DEFAULT_FONT_SIZE) * LINE_HEIGHT_FACTOR;\n}\n\n// ============================================\n// Block Renderers\n// ============================================\n\nfunction renderBlocks(nodes: MarkdownBlockNode[], ctx: ExportContext, extraIndent: number): void {\n for (const node of nodes) {\n renderBlock(node, ctx, extraIndent);\n }\n}\n\nfunction renderBlock(node: MarkdownBlockNode, ctx: ExportContext, extraIndent: number): void {\n switch (node.type) {\n case 'heading':\n renderHeading(node as MarkdownHeading, ctx, extraIndent);\n break;\n case 'paragraph':\n renderParagraph(node as MarkdownParagraph, ctx, extraIndent);\n break;\n case 'blockquote':\n renderBlockquote(node as MarkdownBlockquote, ctx, extraIndent);\n break;\n case 'list':\n renderList(node as MarkdownList, ctx, extraIndent, 0);\n break;\n case 'code':\n renderCodeBlock(node as MarkdownCodeBlock, ctx, extraIndent);\n break;\n case 'table':\n renderTable(node as MarkdownTable, ctx, extraIndent);\n break;\n case 'thematicBreak':\n renderThematicBreak(ctx, extraIndent);\n break;\n case 'htmlBlock':\n renderHtmlBlock(node as MarkdownHtmlBlock, ctx, extraIndent);\n break;\n case 'math':\n renderMathBlock(node as MarkdownMathBlock, ctx, extraIndent);\n break;\n case 'footnoteDefinition':\n renderFootnoteDefinition(node as MarkdownFootnoteDefinition, ctx, extraIndent);\n break;\n default:\n // containerDirective, leafDirective, definitionList, etc.\n // Render any children or value as best-effort\n renderFallbackBlock(node, ctx, extraIndent);\n break;\n }\n}\n\n// ---- Heading ----\n\nfunction renderHeading(node: MarkdownHeading, ctx: ExportContext, extraIndent: number): void {\n const origFontSize = ctx.fontSize;\n ctx.fontSize = HEADING_SIZES[node.depth] ?? DEFAULT_FONT_SIZE;\n\n ctx.y -= HEADING_SPACE_BEFORE;\n\n const x0 = ctx.margin + extraIndent;\n const w = ctx.contentWidth - extraIndent;\n\n const spans = flattenInlines(node.children, ctx, {\n bold: true,\n italic: false,\n code: false,\n color: ctx.colors.heading,\n });\n\n drawSpans(spans, ctx, w, x0);\n\n ctx.y -= HEADING_SPACE_AFTER;\n ctx.fontSize = origFontSize;\n}\n\n// ---- Paragraph ----\n\nfunction renderParagraph(\n node: MarkdownParagraph,\n ctx: ExportContext,\n extraIndent: number,\n colorOverride?: { r: number; g: number; b: number },\n): void {\n const x0 = ctx.margin + extraIndent;\n const w = ctx.contentWidth - extraIndent;\n\n const spans = flattenInlines(node.children, ctx, {\n bold: false,\n italic: false,\n code: false,\n color: colorOverride,\n });\n\n drawSpans(spans, ctx, w, x0);\n ctx.y -= PARAGRAPH_SPACING;\n}\n\n// ---- Blockquote ----\n\nfunction renderBlockquote(node: MarkdownBlockquote, ctx: ExportContext, extraIndent: number): void {\n const barX = ctx.margin + extraIndent + 4;\n const indent = extraIndent + BLOCKQUOTE_INDENT;\n const startY = ctx.y;\n\n for (const child of node.children) {\n if (child.type === 'paragraph') {\n renderParagraph(child as MarkdownParagraph, ctx, indent, COLOR_BLOCKQUOTE_TEXT);\n } else {\n renderBlock(child, ctx, indent);\n }\n }\n\n // Draw left bar from startY to ctx.y\n const endY = ctx.y + PARAGRAPH_SPACING; // undo last paragraph spacing\n if (startY > endY) {\n // Bar might span pages — draw on current page only\n ctx.page.drawRectangle({\n x: barX,\n y: endY,\n width: BLOCKQUOTE_BAR_WIDTH,\n height: startY - endY,\n color: rgb(COLOR_BLOCKQUOTE_BAR.r, COLOR_BLOCKQUOTE_BAR.g, COLOR_BLOCKQUOTE_BAR.b),\n });\n }\n\n ctx.y -= PARAGRAPH_SPACING;\n}\n\n// ---- List ----\n\nfunction renderList(\n node: MarkdownList,\n ctx: ExportContext,\n extraIndent: number,\n depth: number,\n): void {\n const ordered = node.ordered ?? false;\n let counter = node.start ?? 1;\n\n for (const child of node.children) {\n if (child.type === 'listItem') {\n renderListItem(child as MarkdownListItem, ctx, extraIndent, depth, ordered, counter);\n if (ordered) counter++;\n }\n }\n}\n\nfunction renderListItem(\n item: MarkdownListItem,\n ctx: ExportContext,\n extraIndent: number,\n depth: number,\n ordered: boolean,\n counter: number,\n): void {\n const indent = extraIndent + depth * LIST_INDENT;\n const bullet = ordered ? `${counter}.` : BULLET_CHAR;\n const bulletFont = ctx.fonts.regular;\n const bulletWidth = bulletFont.widthOfTextAtSize(bullet + ' ', ctx.fontSize);\n\n // Draw bullet\n const lineHeight = ctx.fontSize * LINE_HEIGHT_FACTOR;\n ensureSpace(ctx, lineHeight);\n\n ctx.page.drawText(bullet, {\n x: ctx.margin + indent,\n y: ctx.y - ctx.fontSize,\n size: ctx.fontSize,\n font: bulletFont,\n color: rgb(ctx.colors.text.r, ctx.colors.text.g, ctx.colors.text.b),\n });\n\n const textIndent = indent + bulletWidth + 4;\n const textWidth = ctx.contentWidth - textIndent;\n\n // Render children at the text indent\n let isFirstChild = true;\n for (const child of item.children) {\n if (child.type === 'paragraph' && isFirstChild) {\n // First paragraph: render on same line as bullet\n const spans = flattenInlines((child as MarkdownParagraph).children, ctx, {\n bold: false,\n italic: false,\n code: false,\n });\n drawSpans(spans, ctx, textWidth, ctx.margin + textIndent);\n ctx.y -= PARAGRAPH_SPACING / 2;\n } else if (child.type === 'list') {\n renderList(child as MarkdownList, ctx, extraIndent, depth + 1);\n } else {\n renderBlock(child, ctx, textIndent);\n }\n isFirstChild = false;\n }\n}\n\n// ---- Code Block ----\n\nfunction renderCodeBlock(node: MarkdownCodeBlock, ctx: ExportContext, extraIndent: number): void {\n const x0 = ctx.margin + extraIndent;\n const _w = ctx.contentWidth - extraIndent;\n const lines = node.value.split('\\n');\n const lineH = CODE_FONT_SIZE * LINE_HEIGHT_FACTOR;\n const totalHeight = lines.length * lineH + 12; // 12 = vertical padding\n\n ensureSpace(ctx, Math.min(totalHeight, ctx.y - ctx.bottomY));\n\n const _bgTop = ctx.y;\n\n // Draw background first (we'll adjust after we know the final y)\n const _bgStartY = ctx.y;\n ctx.y -= 6; // top padding\n\n for (const line of lines) {\n ensureSpace(ctx, lineH);\n if (line.length > 0) {\n ctx.page.drawText(line, {\n x: x0 + 8,\n y: ctx.y - CODE_FONT_SIZE,\n size: CODE_FONT_SIZE,\n font: ctx.fonts.mono,\n color: rgb(COLOR_CODE_TEXT.r, COLOR_CODE_TEXT.g, COLOR_CODE_TEXT.b),\n });\n }\n ctx.y -= lineH;\n }\n\n ctx.y -= 6; // bottom padding\n\n // Draw background rectangle (behind text — pdf-lib draws on top,\n // so we could draw it first on a separate pass, but since we can't\n // easily re-order, we leave it as a visual approximation).\n // For a production implementation, you'd use a two-pass approach.\n // Here we draw the bg rect at the saved position. Text drawn after\n // will already be on the page. Since pdf-lib paints in order, we\n // accept that the bg goes on top — but only if bg is on same page.\n // A simpler approach: draw bg *before* text per page.\n\n // Actually, let's use a cleaner approach: draw line by line with bg\n // We already drew the text. For the background box, we'll just\n // not draw it behind (it would overlay). This is a known limitation\n // with pdf-lib's immediate mode. In practice the light grey bg makes\n // text hard to read if drawn on top. So we skip the bg for now and\n // rely on the monospace font to visually distinguish code.\n\n ctx.y -= PARAGRAPH_SPACING;\n}\n\n// ---- Table ----\n\nfunction renderTable(node: MarkdownTable, ctx: ExportContext, extraIndent: number): void {\n if (node.children.length === 0) return;\n\n const x0 = ctx.margin + extraIndent;\n const tableWidth = ctx.contentWidth - extraIndent;\n\n // Measure columns: get max width per column across all rows\n const numCols = Math.max(\n ...node.children.map((row) => (row as MarkdownTableRow).children.length),\n );\n if (numCols === 0) return;\n\n // Simple equal-width columns\n const colWidth = tableWidth / numCols;\n\n for (let rowIdx = 0; rowIdx < node.children.length; rowIdx++) {\n const row = node.children[rowIdx] as MarkdownTableRow;\n const isHeader = rowIdx === 0 && row.children.some((c) => (c as MarkdownTableCell).isHeader);\n\n // Calculate row height\n const rowCellHeights = row.children.map((cell) => {\n const cellNode = cell as MarkdownTableCell;\n const spans = flattenInlines(cellNode.children, ctx, {\n bold: isHeader,\n italic: false,\n code: false,\n });\n const lines = wrapSpans(spans, colWidth - 2 * TABLE_CELL_PAD_X);\n return lines.length * ctx.fontSize * LINE_HEIGHT_FACTOR + 2 * TABLE_CELL_PAD_Y;\n });\n const rowHeight = Math.max(\n ...rowCellHeights,\n ctx.fontSize * LINE_HEIGHT_FACTOR + 2 * TABLE_CELL_PAD_Y,\n );\n\n ensureSpace(ctx, rowHeight);\n\n // Draw header background\n if (isHeader) {\n ctx.page.drawRectangle({\n x: x0,\n y: ctx.y - rowHeight,\n width: tableWidth,\n height: rowHeight,\n color: rgb(COLOR_TABLE_HEADER_BG.r, COLOR_TABLE_HEADER_BG.g, COLOR_TABLE_HEADER_BG.b),\n });\n }\n\n // Draw cell borders and text\n for (let colIdx = 0; colIdx < numCols; colIdx++) {\n const cellX = x0 + colIdx * colWidth;\n\n // Draw cell border\n ctx.page.drawRectangle({\n x: cellX,\n y: ctx.y - rowHeight,\n width: colWidth,\n height: rowHeight,\n borderColor: rgb(COLOR_TABLE_BORDER.r, COLOR_TABLE_BORDER.g, COLOR_TABLE_BORDER.b),\n borderWidth: TABLE_BORDER_WIDTH,\n });\n\n if (colIdx < row.children.length) {\n const cellNode = row.children[colIdx] as MarkdownTableCell;\n const spans = flattenInlines(cellNode.children, ctx, {\n bold: isHeader,\n italic: false,\n code: false,\n });\n\n // Draw text inside cell\n const savedY = ctx.y;\n ctx.y = ctx.y - TABLE_CELL_PAD_Y;\n\n const cellAvailableWidth = colWidth - 2 * TABLE_CELL_PAD_X;\n const wrappedLines = wrapSpans(spans, cellAvailableWidth);\n\n for (const line of wrappedLines) {\n let lx = cellX + TABLE_CELL_PAD_X;\n for (const span of line) {\n ctx.page.drawText(span.text, {\n x: lx,\n y: ctx.y - span.fontSize,\n size: span.fontSize,\n font: span.font,\n color: rgb(span.color.r, span.color.g, span.color.b),\n });\n lx += span.font.widthOfTextAtSize(span.text, span.fontSize);\n }\n ctx.y -= getLineHeight(line);\n }\n\n ctx.y = savedY; // restore for next cell\n }\n }\n\n ctx.y -= rowHeight;\n }\n\n ctx.y -= PARAGRAPH_SPACING;\n}\n\n// ---- Thematic Break ----\n\nfunction renderThematicBreak(ctx: ExportContext, extraIndent: number): void {\n ctx.y -= PARAGRAPH_SPACING;\n ensureSpace(ctx, 10);\n\n const x0 = ctx.margin + extraIndent;\n const x1 = ctx.margin + ctx.contentWidth;\n\n ctx.page.drawLine({\n start: { x: x0, y: ctx.y },\n end: { x: x1, y: ctx.y },\n thickness: 1,\n color: rgb(COLOR_THEMATIC_BREAK.r, COLOR_THEMATIC_BREAK.g, COLOR_THEMATIC_BREAK.b),\n });\n\n ctx.y -= PARAGRAPH_SPACING;\n}\n\n// ---- HTML Block ----\n\nfunction renderHtmlBlock(node: MarkdownHtmlBlock, ctx: ExportContext, extraIndent: number): void {\n if (!node.rawHtml) return;\n // Render raw HTML as monospace text (best effort)\n const lines = node.rawHtml.split('\\n');\n const x0 = ctx.margin + extraIndent;\n for (const line of lines) {\n if (line.trim().length === 0) continue;\n const lineH = CODE_FONT_SIZE * LINE_HEIGHT_FACTOR;\n ensureSpace(ctx, lineH);\n ctx.page.drawText(line, {\n x: x0,\n y: ctx.y - CODE_FONT_SIZE,\n size: CODE_FONT_SIZE,\n font: ctx.fonts.mono,\n color: rgb(COLOR_CODE_TEXT.r, COLOR_CODE_TEXT.g, COLOR_CODE_TEXT.b),\n });\n ctx.y -= lineH;\n }\n ctx.y -= PARAGRAPH_SPACING;\n}\n\n// ---- Math Block ----\n\nfunction renderMathBlock(node: MarkdownMathBlock, ctx: ExportContext, extraIndent: number): void {\n // Render LaTeX source in monospace as fallback\n const lines = node.value.split('\\n');\n const x0 = ctx.margin + extraIndent;\n for (const line of lines) {\n const lineH = CODE_FONT_SIZE * LINE_HEIGHT_FACTOR;\n ensureSpace(ctx, lineH);\n if (line.length > 0) {\n ctx.page.drawText(line, {\n x: x0,\n y: ctx.y - CODE_FONT_SIZE,\n size: CODE_FONT_SIZE,\n font: ctx.fonts.mono,\n color: rgb(COLOR_CODE_TEXT.r, COLOR_CODE_TEXT.g, COLOR_CODE_TEXT.b),\n });\n }\n ctx.y -= lineH;\n }\n ctx.y -= PARAGRAPH_SPACING;\n}\n\n// ---- Footnote Definition ----\n\nfunction renderFootnoteDefinition(\n node: MarkdownFootnoteDefinition,\n ctx: ExportContext,\n extraIndent: number,\n): void {\n // Draw footnote identifier\n const label = `[${node.identifier}]`;\n const lineH = ctx.fontSize * LINE_HEIGHT_FACTOR;\n ensureSpace(ctx, lineH);\n\n ctx.page.drawText(label, {\n x: ctx.margin + extraIndent,\n y: ctx.y - ctx.fontSize * 0.75,\n size: ctx.fontSize * 0.75,\n font: ctx.fonts.bold,\n color: rgb(ctx.colors.link.r, ctx.colors.link.g, ctx.colors.link.b),\n });\n\n // Render children indented\n renderBlocks(node.children, ctx, extraIndent + LIST_INDENT);\n}\n\n// ---- Fallback ----\n\nfunction renderFallbackBlock(\n node: MarkdownBlockNode,\n ctx: ExportContext,\n extraIndent: number,\n): void {\n const fallback = node as unknown as {\n children?: (MarkdownBlockNode | MarkdownInlineNode)[];\n value?: string;\n };\n if (fallback.children && Array.isArray(fallback.children)) {\n // Could be block children or inline children\n if (fallback.children.length > 0 && typeof fallback.children[0]?.type === 'string') {\n const firstType = fallback.children[0].type;\n // Heuristic: if first child looks like an inline node, wrap as paragraph\n const inlineTypes = new Set([\n 'text',\n 'strong',\n 'emphasis',\n 'delete',\n 'inlineCode',\n 'link',\n 'image',\n 'break',\n ]);\n if (inlineTypes.has(firstType)) {\n const spans = flattenInlines(fallback.children as MarkdownInlineNode[], ctx, {\n bold: false,\n italic: false,\n code: false,\n });\n drawSpans(spans, ctx, ctx.contentWidth - extraIndent, ctx.margin + extraIndent);\n ctx.y -= PARAGRAPH_SPACING;\n return;\n }\n }\n renderBlocks(fallback.children as MarkdownBlockNode[], ctx, extraIndent);\n } else if (fallback.value && typeof fallback.value === 'string') {\n const lineH = ctx.fontSize * LINE_HEIGHT_FACTOR;\n ensureSpace(ctx, lineH);\n ctx.page.drawText(fallback.value, {\n x: ctx.margin + extraIndent,\n y: ctx.y - ctx.fontSize,\n size: ctx.fontSize,\n font: ctx.fonts.regular,\n color: rgb(ctx.colors.text.r, ctx.colors.text.g, ctx.colors.text.b),\n });\n ctx.y -= lineH + PARAGRAPH_SPACING;\n }\n}\n\n// ============================================\n// Color Helpers\n// ============================================\n\nfunction hexToRgb(hex: string): RgbColor | undefined {\n const h = hex.startsWith('#') ? hex.slice(1) : hex;\n if (h.length !== 6) return undefined;\n const r = parseInt(h.slice(0, 2), 16);\n const g = parseInt(h.slice(2, 4), 16);\n const b = parseInt(h.slice(4, 6), 16);\n if (isNaN(r) || isNaN(g) || isNaN(b)) return undefined;\n return { r: r / 255, g: g / 255, b: b / 255 };\n}\n","/**\n * PDF Style / Layout Constants\n *\n * Shared constants used by both PDF export and import. Defines page\n * dimensions, margins, font sizes, colours, and spacing for rendering\n * MarkdownDocument content as a paginated PDF.\n */\n\n// ============================================\n// Page Layout\n// ============================================\n\n/** Standard US Letter page width in points (8.5 in × 72 pt/in). */\nexport const PAGE_WIDTH_LETTER = 612;\n\n/** Standard US Letter page height in points (11 in × 72 pt/in). */\nexport const PAGE_HEIGHT_LETTER = 792;\n\n/** A4 page width in points (210 mm ≈ 595 pt). */\nexport const PAGE_WIDTH_A4 = 595.28;\n\n/** A4 page height in points (297 mm ≈ 842 pt). */\nexport const PAGE_HEIGHT_A4 = 841.89;\n\n/** Default page margins in points (1 inch = 72 pt). */\nexport const DEFAULT_MARGIN = 72;\n\n// ============================================\n// Fonts — pdf-lib standard font names\n// ============================================\n\nexport const FONT_REGULAR = 'Helvetica';\nexport const FONT_BOLD = 'Helvetica-Bold';\nexport const FONT_ITALIC = 'Helvetica-Oblique';\nexport const FONT_BOLD_ITALIC = 'Helvetica-BoldOblique';\nexport const FONT_MONO = 'Courier';\nexport const FONT_MONO_BOLD = 'Courier-Bold';\n\n// ============================================\n// Font Sizes\n// ============================================\n\n/** Default body font size in points. */\nexport const DEFAULT_FONT_SIZE = 11;\n\n/** Heading font sizes indexed by depth (1-based). */\nexport const HEADING_SIZES: Record<number, number> = {\n 1: 24,\n 2: 20,\n 3: 16,\n 4: 14,\n 5: 13,\n 6: 12,\n};\n\n/** Font size used for code blocks and inline code. */\nexport const CODE_FONT_SIZE = 10;\n\n// ============================================\n// Spacing (in points)\n// ============================================\n\n/** Line height multiplier (leading). */\nexport const LINE_HEIGHT_FACTOR = 1.4;\n\n/** Space above a heading. */\nexport const HEADING_SPACE_BEFORE = 18;\n\n/** Space below a heading. */\nexport const HEADING_SPACE_AFTER = 6;\n\n/** Space between paragraphs. */\nexport const PARAGRAPH_SPACING = 8;\n\n/** Extra indent per list nesting level. */\nexport const LIST_INDENT = 24;\n\n/** Bullet character for unordered lists. */\nexport const BULLET_CHAR = '\\u2022'; // •\n\n/** Indent for blockquotes. */\nexport const BLOCKQUOTE_INDENT = 24;\n\n/** Width of the blockquote left bar. */\nexport const BLOCKQUOTE_BAR_WIDTH = 3;\n\n// ============================================\n// Colours (RGB 0-1)\n// ============================================\n\nexport const COLOR_TEXT = { r: 0.12, g: 0.12, b: 0.14 };\nexport const COLOR_HEADING = { r: 0.08, g: 0.08, b: 0.1 };\nexport const COLOR_LINK = { r: 0.05, g: 0.27, b: 0.73 };\nexport const COLOR_CODE_BG = { r: 0.95, g: 0.95, b: 0.97 };\nexport const COLOR_CODE_TEXT = { r: 0.2, g: 0.2, b: 0.22 };\nexport const COLOR_BLOCKQUOTE_BAR = { r: 0.75, g: 0.75, b: 0.78 };\nexport const COLOR_BLOCKQUOTE_TEXT = { r: 0.35, g: 0.35, b: 0.38 };\nexport const COLOR_THEMATIC_BREAK = { r: 0.78, g: 0.78, b: 0.8 };\nexport const COLOR_TABLE_BORDER = { r: 0.75, g: 0.75, b: 0.78 };\nexport const COLOR_TABLE_HEADER_BG = { r: 0.93, g: 0.93, b: 0.95 };\n\n// ============================================\n// Table Constants\n// ============================================\n\n/** Table cell horizontal padding in points. */\nexport const TABLE_CELL_PAD_X = 6;\n\n/** Table cell vertical padding in points. */\nexport const TABLE_CELL_PAD_Y = 4;\n\n/** Table border line width. */\nexport const TABLE_BORDER_WIDTH = 0.5;\n\n// ============================================\n// Import Heuristic Thresholds\n// ============================================\n\n/**\n * Minimum font size (in PDF points) to consider as a heading.\n * Anything ≥ this and larger than the detected body size is a heading.\n */\nexport const IMPORT_HEADING_MIN_SIZE = 13;\n\n/**\n * Font size ranges mapped to heading depth for import heuristics.\n * Applied when a text item's font size exceeds the body size.\n */\nexport const IMPORT_HEADING_SIZE_RANGES: Array<{ min: number; depth: number }> = [\n { min: 22, depth: 1 },\n { min: 18, depth: 2 },\n { min: 15, depth: 3 },\n { min: 13.5, depth: 4 },\n { min: 12.5, depth: 5 },\n { min: 12, depth: 6 },\n];\n\n/**\n * Y-distance (in points) between lines that implies a paragraph break\n * rather than a continuation of the same paragraph.\n */\nexport const IMPORT_PARAGRAPH_GAP = 4;\n\n/**\n * Characters that indicate an unordered list bullet at the start of a line.\n */\nexport const IMPORT_BULLET_CHARS = new Set([\n '\\u2022',\n '\\u2023',\n '\\u25E6',\n '\\u2043',\n '-',\n '*',\n '\\u2013',\n '\\u2014',\n '\\u25AA',\n '\\u25AB',\n]);\n\n/**\n * Regex to detect an ordered list prefix (e.g. \"1.\", \"2)\", \"iv.\").\n */\nexport const IMPORT_ORDERED_PREFIX = /^(\\d+|[a-z]+|[ivxlcdm]+)[.)]\\s/i;\n\n/**\n * Column alignment tolerance — text items whose x-positions differ by\n * less than this are considered to be in the same column (for table detection).\n */\nexport const IMPORT_COLUMN_TOLERANCE = 8;\n\n/**\n * Minimum number of consecutive rows with the same column count to\n * consider them a table.\n */\nexport const IMPORT_TABLE_MIN_ROWS = 2;\n\n/**\n * URL pattern for detecting links in extracted text.\n */\nexport const IMPORT_URL_PATTERN = /https?:\\/\\/[^\\s)>\\]]+/g;\n","/**\n * PDF Import\n *\n * Parses a PDF file and converts its content into a squisq\n * MarkdownDocument (or Doc) using heuristic detection of headings,\n * lists, code blocks, tables, blockquotes, and hyperlinks.\n *\n * Uses pdfjs-dist (Mozilla pdf.js) for text extraction — a battle-tested,\n * browser-compatible PDF parser. Since PDFs encode positioned glyphs\n * rather than semantic structure, all structure detection is inherently\n * heuristic and works best on simply-formatted documents.\n *\n * @example\n * ```ts\n * import { pdfToMarkdownDoc } from '@bendyline/squisq-formats/pdf';\n *\n * const response = await fetch('document.pdf');\n * const data = await response.arrayBuffer();\n * const doc = await pdfToMarkdownDoc(data);\n * ```\n */\n\nimport type { Doc } from '@bendyline/squisq/schemas';\nimport { markdownToDoc } from '@bendyline/squisq/doc';\nimport type {\n MarkdownDocument,\n MarkdownBlockNode,\n MarkdownInlineNode,\n MarkdownHeading,\n MarkdownParagraph,\n MarkdownBlockquote,\n MarkdownList,\n MarkdownListItem,\n MarkdownCodeBlock,\n MarkdownTable,\n MarkdownTableRow,\n MarkdownTableCell,\n MarkdownText,\n MarkdownEmphasis,\n MarkdownStrong,\n MarkdownInlineCode,\n MarkdownLink,\n MarkdownImage,\n} from '@bendyline/squisq/markdown';\n\nimport { MemoryContentContainer } from '@bendyline/squisq/storage';\nimport type { ContentContainer } from '@bendyline/squisq/storage';\n\nimport {\n DEFAULT_FONT_SIZE,\n IMPORT_HEADING_MIN_SIZE,\n IMPORT_HEADING_SIZE_RANGES,\n IMPORT_PARAGRAPH_GAP,\n IMPORT_BULLET_CHARS,\n IMPORT_ORDERED_PREFIX,\n IMPORT_COLUMN_TOLERANCE,\n IMPORT_TABLE_MIN_ROWS,\n IMPORT_URL_PATTERN,\n} from './styles.js';\n\n// ============================================\n// Public API\n// ============================================\n\n/**\n * Options for PDF import.\n */\nexport interface PdfImportOptions {\n /**\n * Hint for the body font size used in the PDF (in points).\n * Text items larger than this are considered headings.\n * If not provided, the importer detects the most common font size.\n */\n bodyFontSize?: number;\n\n /** Whether to detect tables from column-aligned text. Default: true. */\n detectTables?: boolean;\n\n /** Whether to detect code blocks from monospace fonts. Default: true. */\n detectCodeBlocks?: boolean;\n\n /** Whether to detect blockquotes from indentation. Default: true. */\n detectBlockquotes?: boolean;\n\n /** Whether to detect URLs in text and convert to links. Default: true. */\n detectLinks?: boolean;\n}\n\n/**\n * Convert a PDF file to a MarkdownDocument.\n *\n * Structure detection is heuristic — results are best-effort.\n *\n * @param data - The raw PDF file as ArrayBuffer, Uint8Array, or Blob\n * @param options - Import options\n * @returns A MarkdownDocument representing the detected content\n */\nexport async function pdfToMarkdownDoc(\n data: ArrayBuffer | Uint8Array | Blob,\n options: PdfImportOptions = {},\n): Promise<MarkdownDocument> {\n const bytes =\n data instanceof Blob\n ? new Uint8Array(await data.arrayBuffer())\n : data instanceof ArrayBuffer\n ? new Uint8Array(data)\n : data;\n\n const textLines = await extractTextLines(bytes);\n\n if (textLines.length === 0) {\n return { type: 'document', children: [] };\n }\n\n const bodySize = options.bodyFontSize ?? detectBodyFontSize(textLines);\n const blocks = classifyLines(textLines, bodySize, options);\n\n return { type: 'document', children: blocks };\n}\n\n/**\n * Convert a PDF file to a squisq Doc.\n *\n * Convenience wrapper: PDF → MarkdownDocument → Doc.\n */\nexport async function pdfToDoc(\n data: ArrayBuffer | Uint8Array | Blob,\n options: PdfImportOptions = {},\n): Promise<Doc> {\n const markdownDoc = await pdfToMarkdownDoc(data, options);\n return markdownToDoc(markdownDoc);\n}\n\n/**\n * Convert a PDF file to a ContentContainer with markdown + extracted images.\n *\n * The container will contain:\n * - The primary markdown document (index.md)\n * - Any embedded images under images/ (e.g., images/image1.png)\n *\n * Image extraction uses pdfjs-dist's operator list API and requires a browser\n * environment (canvas is used to encode pixel data to PNG).\n *\n * @param data - The raw PDF file as ArrayBuffer, Uint8Array, or Blob\n * @param options - Import options\n * @returns A ContentContainer with the document and its media\n */\nexport async function pdfToContainer(\n data: ArrayBuffer | Uint8Array | Blob,\n options: PdfImportOptions = {},\n): Promise<ContentContainer> {\n const bytes =\n data instanceof Blob\n ? new Uint8Array(await data.arrayBuffer())\n : data instanceof ArrayBuffer\n ? new Uint8Array(data)\n : data;\n\n const textLines = await extractTextLines(bytes);\n const images = await extractImages(bytes);\n\n const bodySize = options.bodyFontSize ?? detectBodyFontSize(textLines);\n\n // If we have images, insert image references into the block list\n let blocks = classifyLines(textLines, bodySize, options);\n if (images.length > 0) {\n blocks = insertImageBlocks(blocks, images);\n }\n\n const markdownDoc: MarkdownDocument = { type: 'document', children: blocks };\n\n const { stringifyMarkdown } = await import('@bendyline/squisq/markdown');\n const markdown = stringifyMarkdown(markdownDoc);\n\n const container = new MemoryContentContainer();\n await container.writeDocument(markdown);\n\n for (const img of images) {\n await container.writeFile(img.path, new Uint8Array(img.data), 'image/png');\n }\n\n return container;\n}\n\n/** Extracted image with position info for placement. */\ninterface ExtractedImage {\n path: string;\n data: ArrayBuffer;\n page: number;\n y: number;\n}\n\n/**\n * Extract embedded images from a PDF using pdfjs-dist operator list API.\n * Requires browser canvas for PNG encoding.\n */\nasync function extractImages(data: Uint8Array): Promise<ExtractedImage[]> {\n // Canvas is required for PNG encoding — skip in non-browser environments\n if (typeof document === 'undefined') return [];\n\n let pdfjsLib: PdfjsLib & { OPS?: Record<string, number> };\n try {\n pdfjsLib = (await import('pdfjs-dist/legacy/build/pdf.mjs')) as unknown as PdfjsLib & {\n OPS?: Record<string, number>;\n };\n } catch {\n pdfjsLib = (await import('pdfjs-dist')) as unknown as PdfjsLib & {\n OPS?: Record<string, number>;\n };\n }\n\n await applyWorkerConfig(pdfjsLib);\n\n const OPS_paintImageXObject = pdfjsLib.OPS?.paintImageXObject ?? 85;\n\n const loadingTask = pdfjsLib.getDocument({\n data,\n isEvalSupported: false,\n useSystemFonts: true,\n });\n\n const pdf = await loadingTask.promise;\n const images: ExtractedImage[] = [];\n let counter = 0;\n\n for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {\n const page = (await pdf.getPage(pageNum)) as PdfjsPageFull;\n if (!page.getOperatorList) continue;\n\n const opList = await page.getOperatorList();\n const seen = new Set<string>();\n\n for (let i = 0; i < opList.fnArray.length; i++) {\n if (opList.fnArray[i] !== OPS_paintImageXObject) continue;\n\n const imgName = opList.argsArray[i]?.[0];\n if (!imgName || typeof imgName !== 'string' || seen.has(imgName)) continue;\n seen.add(imgName);\n\n try {\n const imgData = page.objs?.get(imgName) as PdfjsImageData | null;\n if (!imgData?.data || !imgData.width || !imgData.height) continue;\n\n const pngData = imageDataToPng(imgData);\n if (!pngData) continue;\n\n counter++;\n images.push({\n path: `images/image${counter}.png`,\n data: pngData,\n page: pageNum - 1,\n y: 0,\n });\n } catch {\n // Skip images that fail to extract\n }\n }\n }\n\n return images;\n}\n\n/** Minimal pdfjs image data shape. */\ninterface PdfjsImageData {\n width: number;\n height: number;\n data: Uint8ClampedArray;\n kind?: number;\n}\n\n/** Extended PdfjsPage with operator list and objs access. */\ninterface PdfjsPageFull extends PdfjsPage {\n getOperatorList(): Promise<{ fnArray: number[]; argsArray: unknown[][] }>;\n objs?: { get(name: string): unknown; has?(name: string): boolean };\n}\n\n/** Encode pdfjs image data to PNG using a canvas element. */\nfunction imageDataToPng(img: PdfjsImageData): ArrayBuffer | null {\n try {\n const canvas = document.createElement('canvas');\n canvas.width = img.width;\n canvas.height = img.height;\n const ctx = canvas.getContext('2d');\n if (!ctx) return null;\n\n // pdfjs kind=1 is GRAYSCALE, kind=2 is RGB, kind=3 is RGBA\n let imageData: ImageData;\n if (img.kind === 3 || img.data.length === img.width * img.height * 4) {\n // RGBA — use directly\n imageData = new ImageData(new Uint8ClampedArray(img.data), img.width, img.height);\n } else if (img.kind === 2 || img.data.length === img.width * img.height * 3) {\n // RGB — expand to RGBA\n const rgba = new Uint8ClampedArray(img.width * img.height * 4);\n for (let j = 0, k = 0; j < img.data.length; j += 3, k += 4) {\n rgba[k] = img.data[j];\n rgba[k + 1] = img.data[j + 1];\n rgba[k + 2] = img.data[j + 2];\n rgba[k + 3] = 255;\n }\n imageData = new ImageData(rgba, img.width, img.height);\n } else if (img.kind === 1 || img.data.length === img.width * img.height) {\n // Grayscale — expand to RGBA\n const rgba = new Uint8ClampedArray(img.width * img.height * 4);\n for (let j = 0, k = 0; j < img.data.length; j++, k += 4) {\n rgba[k] = img.data[j];\n rgba[k + 1] = img.data[j];\n rgba[k + 2] = img.data[j];\n rgba[k + 3] = 255;\n }\n imageData = new ImageData(rgba, img.width, img.height);\n } else {\n return null;\n }\n\n ctx.putImageData(imageData, 0, 0);\n\n // Convert canvas to PNG ArrayBuffer\n const dataUrl = canvas.toDataURL('image/png');\n const base64 = dataUrl.split(',')[1];\n const binaryStr = atob(base64);\n const bytes = new Uint8Array(binaryStr.length);\n for (let i = 0; i < binaryStr.length; i++) {\n bytes[i] = binaryStr.charCodeAt(i);\n }\n return bytes.buffer;\n } catch {\n return null;\n }\n}\n\n/**\n * Insert image reference blocks between text blocks.\n * Places each image after the last text block on the same page (or at the end).\n */\nfunction insertImageBlocks(\n blocks: MarkdownBlockNode[],\n images: ExtractedImage[],\n): MarkdownBlockNode[] {\n if (images.length === 0) return blocks;\n\n // Simple strategy: append all images at the end as paragraphs\n const result = [...blocks];\n for (const img of images) {\n const imgNode: MarkdownImage = {\n type: 'image',\n url: img.path,\n alt: `Image ${img.path.replace('images/image', '').replace('.png', '')}`,\n };\n const para: MarkdownParagraph = {\n type: 'paragraph',\n children: [imgNode],\n };\n result.push(para);\n }\n return result;\n}\n\n// ============================================\n// Internal Types\n// ============================================\n\n/** A single text item extracted from pdfjs. */\ninterface TextItem {\n str: string;\n x: number;\n y: number;\n width: number;\n height: number;\n /** Internal font ID from pdfjs (e.g. \"g_d0_f1\") */\n fontName: string;\n /** Resolved font family from pdfjs styles (e.g. \"sans-serif\", \"monospace\") */\n fontFamily: string;\n}\n\n/** A logical line: text items at roughly the same y-coordinate. */\ninterface TextLine {\n items: TextItem[];\n y: number;\n /** The page this line is on (0-based). */\n page: number;\n /** The predominant font size on this line. */\n fontSize: number;\n /** The predominant font family on this line. */\n fontFamily: string;\n /** The predominant font ID on this line (may contain bold/italic hints for embedded fonts). */\n fontName: string;\n /** The minimum x position (left edge). */\n minX: number;\n /** Full concatenated text. */\n text: string;\n}\n\n// ============================================\n// PDF Text Extraction (pdfjs-dist)\n// ============================================\n\n/**\n * Configure the pdfjs-dist PDF worker source URL.\n *\n * pdfjs-dist requires a worker for PDF parsing. In the **browser**, bundlers\n * (Vite, webpack) typically handle this automatically, or you can point to a\n * CDN-hosted worker script. In **Node.js / SSR / test** environments, call\n * this with a `file://` URL to the worker module **before** any import call.\n *\n * @example\n * ```ts\n * // Browser — CDN\n * configurePdfWorker('https://cdn.jsdelivr.net/npm/pdfjs-dist@4/legacy/build/pdf.worker.min.mjs');\n *\n * // Node / vitest — file URL\n * import { pathToFileURL } from 'url';\n * configurePdfWorker(pathToFileURL(require.resolve('pdfjs-dist/legacy/build/pdf.worker.mjs')).href);\n * ```\n */\nexport function configurePdfWorker(workerSrc: string): void {\n _workerSrc = workerSrc;\n}\n\n/** Module-level storage for the worker source URL. */\nlet _workerSrc: string | undefined;\n\n/** Minimal typed surface of the pdfjs-dist library used by the import path. */\ninterface PdfjsLib {\n GlobalWorkerOptions?: { workerSrc?: string };\n getDocument(params: { data: Uint8Array; isEvalSupported?: boolean; useSystemFonts?: boolean }): {\n promise: Promise<PdfjsDocument>;\n };\n}\n\ninterface PdfjsDocument {\n numPages: number;\n getPage(pageNum: number): Promise<PdfjsPage>;\n}\n\ninterface PdfjsPage {\n getTextContent(): Promise<{\n items: Array<{\n str: string;\n transform: number[];\n height: number;\n width?: number;\n fontName?: string;\n }>;\n styles?: Record<string, { fontFamily?: string }>;\n }>;\n}\n\nasync function applyWorkerConfig(pdfjsLib: PdfjsLib): Promise<void> {\n if (!pdfjsLib.GlobalWorkerOptions) return;\n if (pdfjsLib.GlobalWorkerOptions.workerSrc) return;\n\n if (_workerSrc) {\n pdfjsLib.GlobalWorkerOptions.workerSrc = _workerSrc;\n }\n // If no workerSrc is set, pdfjs-dist's legacy build will attempt its\n // built-in fake-worker fallback. In browsers this usually works; in\n // Node.js the caller must have called configurePdfWorker() first.\n}\n\nasync function extractTextLines(data: Uint8Array): Promise<TextLine[]> {\n // Dynamic import — the legacy build bundles a fake-worker fallback\n // that avoids a real Web Worker in environments that don't support it.\n let pdfjsLib: PdfjsLib;\n try {\n pdfjsLib = (await import('pdfjs-dist/legacy/build/pdf.mjs')) as unknown as PdfjsLib;\n } catch {\n pdfjsLib = (await import('pdfjs-dist')) as unknown as PdfjsLib;\n }\n\n await applyWorkerConfig(pdfjsLib);\n\n const loadingTask = pdfjsLib.getDocument({\n data,\n isEvalSupported: false,\n useSystemFonts: true,\n });\n\n const pdf = await loadingTask.promise;\n const allLines: TextLine[] = [];\n\n for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {\n const page = await pdf.getPage(pageNum);\n const content = await page.getTextContent();\n\n // Build a fontName → fontFamily lookup from pdfjs styles\n const styleMap = content.styles || {};\n\n // Group text items into lines by y-coordinate\n const items: TextItem[] = [];\n for (const item of content.items) {\n if (!item.str || item.str.trim().length === 0) continue;\n const transform = item.transform || [1, 0, 0, 1, 0, 0];\n const x = transform[4];\n const y = transform[5];\n const height = Math.abs(transform[3]) || item.height || 12;\n const width = item.width || 0;\n const fontName = item.fontName || '';\n const fontFamily = styleMap[fontName]?.fontFamily || '';\n items.push({ str: item.str, x, y, width, height, fontName, fontFamily });\n }\n\n // Group into lines (items within 2pt of same y are same line)\n const lineMap = new Map<number, TextItem[]>();\n for (const item of items) {\n const roundedY = Math.round(item.y * 2) / 2;\n let foundKey: number | undefined;\n for (const key of lineMap.keys()) {\n if (Math.abs(key - roundedY) < 2) {\n foundKey = key;\n break;\n }\n }\n if (foundKey !== undefined) {\n lineMap.get(foundKey)!.push(item);\n } else {\n lineMap.set(roundedY, [item]);\n }\n }\n\n // Sort lines top-to-bottom (highest y first), items left-to-right\n const sortedKeys = [...lineMap.keys()].sort((a, b) => b - a);\n for (const key of sortedKeys) {\n const lineItems = lineMap.get(key)!.sort((a, b) => a.x - b.x);\n\n const fontSizes = lineItems.map((i) => i.height);\n const fontSize = mode(fontSizes) || 12;\n const fontFamilies = lineItems.map((i) => i.fontFamily);\n const fontFamily = modeStr(fontFamilies) || '';\n const fontNames = lineItems.map((i) => i.fontName);\n const fontName = modeStr(fontNames) || '';\n const minX = Math.min(...lineItems.map((i) => i.x));\n const text = lineItems.map((i) => i.str).join(' ');\n\n allLines.push({\n items: lineItems,\n y: key,\n page: pageNum - 1,\n fontSize,\n fontFamily,\n fontName,\n minX,\n text,\n });\n }\n }\n\n return allLines;\n}\n\n// ============================================\n// Font Size Detection\n// ============================================\n\nfunction detectBodyFontSize(lines: TextLine[]): number {\n const sizes = lines.map((l) => Math.round(l.fontSize * 2) / 2);\n return mode(sizes) || DEFAULT_FONT_SIZE;\n}\n\nfunction mode(arr: number[]): number {\n const freq = new Map<number, number>();\n for (const v of arr) freq.set(v, (freq.get(v) || 0) + 1);\n let maxCount = 0;\n let maxVal = 0;\n for (const [v, c] of freq) {\n if (c > maxCount) {\n maxCount = c;\n maxVal = v;\n }\n }\n return maxVal;\n}\n\nfunction modeStr(arr: string[]): string {\n const freq = new Map<string, number>();\n for (const v of arr) freq.set(v, (freq.get(v) || 0) + 1);\n let maxCount = 0;\n let maxVal = '';\n for (const [v, c] of freq) {\n if (c > maxCount) {\n maxCount = c;\n maxVal = v;\n }\n }\n return maxVal;\n}\n\n// ============================================\n// Line Classification → MarkdownBlockNode[]\n// ============================================\n\nfunction classifyLines(\n lines: TextLine[],\n bodySize: number,\n options: PdfImportOptions,\n): MarkdownBlockNode[] {\n const blocks: MarkdownBlockNode[] = [];\n const detectTables = options.detectTables !== false;\n const detectCodeBlocks = options.detectCodeBlocks !== false;\n const detectBlockquotes = options.detectBlockquotes !== false;\n const _detectLinks = options.detectLinks !== false;\n\n // Determine typical left margin (most common minX)\n const leftMargins = lines.map((l) => Math.round(l.minX));\n const typicalLeftMargin = mode(leftMargins) || 72;\n\n let i = 0;\n while (i < lines.length) {\n const line = lines[i];\n\n // --- Heading detection ---\n if (line.fontSize >= IMPORT_HEADING_MIN_SIZE && line.fontSize > bodySize + 1) {\n const depth = sizeToHeadingDepth(line.fontSize);\n blocks.push({\n type: 'heading',\n depth,\n children: buildInlineNodes(line, options),\n } as MarkdownHeading);\n i++;\n continue;\n }\n\n // --- Code block detection (monospace font runs) ---\n if (detectCodeBlocks && isMonospaceLine(line)) {\n const codeLines: string[] = [];\n while (i < lines.length && isMonospaceLine(lines[i])) {\n codeLines.push(lines[i].text);\n i++;\n }\n blocks.push({\n type: 'code',\n value: codeLines.join('\\n'),\n } as MarkdownCodeBlock);\n continue;\n }\n\n // --- Table detection (column-aligned consecutive lines) ---\n if (detectTables && i + 1 < lines.length) {\n const tableLines = tryDetectTable(lines, i, typicalLeftMargin);\n if (tableLines > 0) {\n const table = buildTable(lines.slice(i, i + tableLines), options);\n if (table) {\n blocks.push(table);\n i += tableLines;\n continue;\n }\n }\n }\n\n // --- List detection ---\n const bulletMatch = tryMatchBullet(line.text);\n const orderedMatch = line.text.match(IMPORT_ORDERED_PREFIX);\n if (bulletMatch || orderedMatch) {\n const listResult = consumeList(lines, i, typicalLeftMargin, bodySize, options);\n blocks.push(listResult.list);\n i = listResult.nextIndex;\n continue;\n }\n\n // --- Blockquote detection (indented text) ---\n if (detectBlockquotes && line.minX > typicalLeftMargin + 20) {\n const quoteLines: TextLine[] = [];\n while (\n i < lines.length &&\n lines[i].minX > typicalLeftMargin + 20 &&\n !isMonospaceLine(lines[i]) &&\n lines[i].fontSize <= bodySize + 1\n ) {\n quoteLines.push(lines[i]);\n i++;\n }\n const quoteBlocks: MarkdownBlockNode[] = quoteLines.map(\n (ql) =>\n ({\n type: 'paragraph',\n children: buildInlineNodes(ql, options),\n }) as MarkdownParagraph,\n );\n blocks.push({\n type: 'blockquote',\n children: quoteBlocks,\n } as MarkdownBlockquote);\n continue;\n }\n\n // --- Regular paragraph ---\n // Merge consecutive body-sized lines on the same page with small y-gaps\n const paraLines: TextLine[] = [line];\n i++;\n while (i < lines.length) {\n const next = lines[i];\n // Same page, same-ish font size, close y (within line-height gap), not bullet/heading\n if (\n next.page === line.page &&\n Math.abs(next.fontSize - bodySize) <= 1 &&\n !isMonospaceLine(next) &&\n next.minX <= typicalLeftMargin + 15 &&\n !tryMatchBullet(next.text) &&\n !next.text.match(IMPORT_ORDERED_PREFIX)\n ) {\n // Check y-gap: lines are sorted top-to-bottom so y decreases\n const yGap = paraLines[paraLines.length - 1].y - next.y;\n const lineHeight = bodySize * 1.6;\n if (yGap > 0 && yGap < lineHeight + IMPORT_PARAGRAPH_GAP) {\n paraLines.push(next);\n i++;\n } else {\n break;\n }\n } else {\n break;\n }\n }\n\n // Build paragraph from merged lines\n const allInlines: MarkdownInlineNode[] = [];\n for (let j = 0; j < paraLines.length; j++) {\n if (j > 0) {\n allInlines.push({ type: 'text', value: ' ' } as MarkdownText);\n }\n allInlines.push(...buildInlineNodes(paraLines[j], options));\n }\n\n if (allInlines.length > 0) {\n blocks.push({\n type: 'paragraph',\n children: mergeAdjacentText(allInlines),\n } as MarkdownParagraph);\n }\n }\n\n return blocks;\n}\n\n// ============================================\n// Heading Depth Mapping\n// ============================================\n\nfunction sizeToHeadingDepth(fontSize: number): 1 | 2 | 3 | 4 | 5 | 6 {\n for (const range of IMPORT_HEADING_SIZE_RANGES) {\n if (fontSize >= range.min) return range.depth as 1 | 2 | 3 | 4 | 5 | 6;\n }\n return 6;\n}\n\n// ============================================\n// Font Heuristics\n// ============================================\n\n/**\n * Check if a line is predominantly monospace.\n * Uses the resolved fontFamily from pdfjs styles first,\n * falls back to fontName pattern matching for embedded fonts.\n */\nfunction isMonospaceLine(line: TextLine): boolean {\n return isMonospaceFamily(line.fontFamily) || isMonospaceName(line.fontName);\n}\n\n/**\n * Check if a text item is monospace.\n */\nfunction isMonospaceItem(item: TextItem): boolean {\n return isMonospaceFamily(item.fontFamily) || isMonospaceName(item.fontName);\n}\n\nfunction isMonospaceFamily(fontFamily: string): boolean {\n const lower = fontFamily.toLowerCase();\n return lower === 'monospace' || lower.includes('monospace');\n}\n\nfunction isMonospaceName(fontName: string): boolean {\n const lower = fontName.toLowerCase();\n return (\n lower.includes('courier') ||\n lower.includes('mono') ||\n lower.includes('consolas') ||\n lower.includes('menlo') ||\n lower.includes('inconsolata') ||\n lower.includes('firacode') ||\n lower.includes('source code') ||\n lower.includes('dejavu sans mono')\n );\n}\n\nfunction isBoldFont(fontName: string): boolean {\n const lower = fontName.toLowerCase();\n return lower.includes('bold') || lower.includes('black') || lower.includes('heavy');\n}\n\nfunction isItalicFont(fontName: string): boolean {\n const lower = fontName.toLowerCase();\n return lower.includes('italic') || lower.includes('oblique') || lower.includes('slanted');\n}\n\n// ============================================\n// Inline Node Construction\n// ============================================\n\nfunction buildInlineNodes(line: TextLine, options: PdfImportOptions): MarkdownInlineNode[] {\n const nodes: MarkdownInlineNode[] = [];\n const detectLinksOpt = options.detectLinks !== false;\n\n for (const item of line.items) {\n const text = item.str;\n if (!text || text.trim().length === 0) continue;\n\n const bold = isBoldFont(item.fontName);\n const italic = isItalicFont(item.fontName);\n const mono = isMonospaceItem(item);\n\n let inlineNodes: MarkdownInlineNode[];\n\n if (mono) {\n inlineNodes = [{ type: 'inlineCode', value: text } as MarkdownInlineCode];\n } else if (detectLinksOpt) {\n inlineNodes = splitTextWithLinks(text);\n } else {\n inlineNodes = [{ type: 'text', value: text } as MarkdownText];\n }\n\n // Wrap in formatting\n for (const node of inlineNodes) {\n let wrapped: MarkdownInlineNode = node;\n if (italic) {\n wrapped = { type: 'emphasis', children: [wrapped] } as MarkdownEmphasis;\n }\n if (bold) {\n wrapped = { type: 'strong', children: [wrapped] } as MarkdownStrong;\n }\n nodes.push(wrapped);\n }\n }\n\n return nodes;\n}\n\n/**\n * Split a text string into text nodes and link nodes wherever\n * URL patterns are found.\n */\nfunction splitTextWithLinks(text: string): MarkdownInlineNode[] {\n const nodes: MarkdownInlineNode[] = [];\n let lastIndex = 0;\n\n // Reset regex state\n IMPORT_URL_PATTERN.lastIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = IMPORT_URL_PATTERN.exec(text)) !== null) {\n // Text before URL\n if (match.index > lastIndex) {\n nodes.push({ type: 'text', value: text.slice(lastIndex, match.index) } as MarkdownText);\n }\n // URL as link\n const url = match[0];\n nodes.push({\n type: 'link',\n url,\n children: [{ type: 'text', value: url } as MarkdownText],\n } as MarkdownLink);\n lastIndex = match.index + url.length;\n }\n\n // Remaining text\n if (lastIndex < text.length) {\n nodes.push({ type: 'text', value: text.slice(lastIndex) } as MarkdownText);\n }\n\n return nodes.length > 0 ? nodes : [{ type: 'text', value: text } as MarkdownText];\n}\n\n// ============================================\n// List Detection\n// ============================================\n\nfunction tryMatchBullet(text: string): boolean {\n if (text.length === 0) return false;\n return IMPORT_BULLET_CHARS.has(text[0]) || IMPORT_BULLET_CHARS.has(text.trimStart()[0]);\n}\n\nfunction stripBullet(text: string): string {\n const trimmed = text.trimStart();\n if (IMPORT_BULLET_CHARS.has(trimmed[0])) {\n return trimmed.slice(1).trimStart();\n }\n return text;\n}\n\nfunction stripOrderedPrefix(text: string): string {\n return text.replace(IMPORT_ORDERED_PREFIX, '');\n}\n\ninterface ListResult {\n list: MarkdownList;\n nextIndex: number;\n}\n\nfunction consumeList(\n lines: TextLine[],\n startIdx: number,\n _typicalLeftMargin: number,\n _bodySize: number,\n _options: PdfImportOptions,\n): ListResult {\n const firstLine = lines[startIdx];\n const isOrdered = !!firstLine.text.match(IMPORT_ORDERED_PREFIX);\n const items: MarkdownListItem[] = [];\n let i = startIdx;\n\n while (i < lines.length) {\n const line = lines[i];\n const isBullet = tryMatchBullet(line.text);\n const isOrd = !!line.text.match(IMPORT_ORDERED_PREFIX);\n\n if (!isBullet && !isOrd) break;\n // All items in one list should be same type\n if (isOrdered && !isOrd) break;\n if (!isOrdered && !isBullet) break;\n\n const cleanText = isOrdered ? stripOrderedPrefix(line.text) : stripBullet(line.text);\n const para: MarkdownParagraph = {\n type: 'paragraph',\n children: splitTextWithLinks(cleanText),\n };\n items.push({\n type: 'listItem',\n children: [para],\n } as MarkdownListItem);\n i++;\n }\n\n return {\n list: {\n type: 'list',\n ordered: isOrdered,\n children: items,\n } as MarkdownList,\n nextIndex: i,\n };\n}\n\n// ============================================\n// Table Detection\n// ============================================\n\n/**\n * Look ahead from index `start` and return the number of consecutive\n * lines that form an aligned table, or 0 if no table detected.\n */\nfunction tryDetectTable(lines: TextLine[], start: number, _typicalLeftMargin: number): number {\n // A table needs multiple items per line (columns) on consecutive lines\n // with roughly the same x-alignment pattern.\n\n const firstLine = lines[start];\n if (firstLine.items.length < 2) return 0;\n\n const cols = getColumnPositions(firstLine);\n if (cols.length < 2) return 0;\n\n let count = 1;\n for (let i = start + 1; i < lines.length; i++) {\n const line = lines[i];\n if (line.items.length < 2) break;\n\n // Check if this line's columns align with the first line's\n const lineCols = getColumnPositions(line);\n if (lineCols.length !== cols.length) break;\n\n let aligned = true;\n for (let c = 0; c < cols.length; c++) {\n if (Math.abs(lineCols[c] - cols[c]) > IMPORT_COLUMN_TOLERANCE) {\n aligned = false;\n break;\n }\n }\n if (!aligned) break;\n count++;\n }\n\n return count >= IMPORT_TABLE_MIN_ROWS ? count : 0;\n}\n\nfunction getColumnPositions(line: TextLine): number[] {\n // Cluster item x-positions\n const positions: number[] = [];\n for (const item of line.items) {\n const x = Math.round(item.x);\n // Check if this x is close to an existing column\n let found = false;\n for (const p of positions) {\n if (Math.abs(p - x) < IMPORT_COLUMN_TOLERANCE) {\n found = true;\n break;\n }\n }\n if (!found) positions.push(x);\n }\n return positions.sort((a, b) => a - b);\n}\n\nfunction buildTable(lines: TextLine[], _options: PdfImportOptions): MarkdownTable | null {\n if (lines.length === 0) return null;\n\n // Use the first line's column positions as anchors\n const cols = getColumnPositions(lines[0]);\n if (cols.length < 2) return null;\n\n const rows: MarkdownTableRow[] = [];\n\n for (let ri = 0; ri < lines.length; ri++) {\n const line = lines[ri];\n const cells: MarkdownTableCell[] = [];\n\n for (let ci = 0; ci < cols.length; ci++) {\n const colLeft = cols[ci] - IMPORT_COLUMN_TOLERANCE;\n const colRight = ci + 1 < cols.length ? cols[ci + 1] - IMPORT_COLUMN_TOLERANCE : Infinity;\n\n // Collect items in this column\n const cellItems = line.items.filter((item) => item.x >= colLeft && item.x < colRight);\n const text = cellItems\n .map((i) => i.str)\n .join(' ')\n .trim();\n\n cells.push({\n type: 'tableCell',\n isHeader: ri === 0,\n children: text.length > 0 ? [{ type: 'text', value: text } as MarkdownText] : [],\n } as MarkdownTableCell);\n }\n\n rows.push({\n type: 'tableRow',\n children: cells,\n } as MarkdownTableRow);\n }\n\n return {\n type: 'table',\n children: rows,\n } as MarkdownTable;\n}\n\n// ============================================\n// Text Merging\n// ============================================\n\n/**\n * Merge adjacent text nodes to reduce fragmentation.\n */\nfunction mergeAdjacentText(nodes: MarkdownInlineNode[]): MarkdownInlineNode[] {\n if (nodes.length <= 1) return nodes;\n\n const result: MarkdownInlineNode[] = [];\n for (const node of nodes) {\n const prev = result[result.length - 1];\n if (prev && prev.type === 'text' && node.type === 'text') {\n (prev as MarkdownText).value += (node as MarkdownText).value;\n } else {\n result.push(node);\n }\n }\n return result;\n}\n"],"mappings":"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","names":[]}
|
package/dist/docx/export.d.ts
CHANGED
|
@@ -38,6 +38,15 @@ export interface DocxExportOptions {
|
|
|
38
38
|
* the theme's primary color to headings.
|
|
39
39
|
*/
|
|
40
40
|
themeId?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Pre-resolved image data keyed by image URL/path as it appears in the
|
|
43
|
+
* markdown source. When provided, images are embedded in the .docx file
|
|
44
|
+
* as binary parts instead of emitting placeholder text.
|
|
45
|
+
*/
|
|
46
|
+
images?: Map<string, {
|
|
47
|
+
data: ArrayBuffer | Uint8Array;
|
|
48
|
+
contentType: string;
|
|
49
|
+
}>;
|
|
41
50
|
}
|
|
42
51
|
/**
|
|
43
52
|
* Convert a MarkdownDocument to a .docx Blob.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../src/docx/export.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAS,MAAM,2BAA2B,CAAC;AAG5D,OAAO,KAAK,EACV,gBAAgB,EAkBjB,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../src/docx/export.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAS,MAAM,2BAA2B,CAAC;AAG5D,OAAO,KAAK,EACV,gBAAgB,EAkBjB,MAAM,4BAA4B,CAAC;AAuCpC;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC/E;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,gBAAgB,EACrB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,WAAW,CAAC,CAItB;AAED;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC,CAG/F"}
|
package/dist/docx/export.js
CHANGED
|
@@ -20,7 +20,7 @@ import { resolveTheme } from '@bendyline/squisq/schemas';
|
|
|
20
20
|
import { docToMarkdown } from '@bendyline/squisq/doc';
|
|
21
21
|
import { createPackage } from '../ooxml/writer.js';
|
|
22
22
|
import { xmlDeclaration, escapeXml } from '../ooxml/xmlUtils.js';
|
|
23
|
-
import { NS_WML, NS_R, NS_MC, REL_OFFICE_DOCUMENT, REL_STYLES, REL_NUMBERING, REL_SETTINGS, REL_FONT_TABLE, REL_HYPERLINK, REL_FOOTNOTES, CONTENT_TYPE_DOCX_DOCUMENT, CONTENT_TYPE_DOCX_STYLES, CONTENT_TYPE_DOCX_NUMBERING, CONTENT_TYPE_DOCX_SETTINGS, CONTENT_TYPE_DOCX_FONT_TABLE, CONTENT_TYPE_DOCX_FOOTNOTES, } from '../ooxml/namespaces.js';
|
|
23
|
+
import { NS_WML, NS_R, NS_MC, REL_OFFICE_DOCUMENT, REL_STYLES, REL_NUMBERING, REL_SETTINGS, REL_FONT_TABLE, REL_HYPERLINK, REL_IMAGE, REL_FOOTNOTES, CONTENT_TYPE_DOCX_DOCUMENT, CONTENT_TYPE_DOCX_STYLES, CONTENT_TYPE_DOCX_NUMBERING, CONTENT_TYPE_DOCX_SETTINGS, CONTENT_TYPE_DOCX_FONT_TABLE, CONTENT_TYPE_DOCX_FOOTNOTES, } from '../ooxml/namespaces.js';
|
|
24
24
|
import { DEPTH_TO_STYLE_ID, HEADING_FONT_SIZES, DEFAULT_FONT, DEFAULT_HEADING_FONT, DEFAULT_FONT_SIZE_HALF_POINTS, DEFAULT_CODE_FONT, DEFAULT_CODE_FONT_SIZE, HYPERLINK_COLOR, pointsToTwips, } from './styles.js';
|
|
25
25
|
/**
|
|
26
26
|
* Convert a MarkdownDocument to a .docx Blob.
|
|
@@ -73,6 +73,7 @@ class ExportContext {
|
|
|
73
73
|
this.hasLists = false;
|
|
74
74
|
/** Whether we have any footnotes */
|
|
75
75
|
this.hasFootnotes = false;
|
|
76
|
+
this.nextDocPrId = 1;
|
|
76
77
|
let themeFont;
|
|
77
78
|
let themeTitleFont;
|
|
78
79
|
let themeHeadingColor;
|
|
@@ -91,6 +92,7 @@ class ExportContext {
|
|
|
91
92
|
? options.defaultFontSize * 2
|
|
92
93
|
: DEFAULT_FONT_SIZE_HALF_POINTS;
|
|
93
94
|
this.headingColor = themeHeadingColor;
|
|
95
|
+
this.resolvedImages = options.images ?? new Map();
|
|
94
96
|
}
|
|
95
97
|
/** Allocate a new relationship ID */
|
|
96
98
|
allocRelId() {
|
|
@@ -107,6 +109,19 @@ class ExportContext {
|
|
|
107
109
|
});
|
|
108
110
|
return id;
|
|
109
111
|
}
|
|
112
|
+
/** Add an embedded image and return the rId and docPrId */
|
|
113
|
+
addImage(data, contentType, filename) {
|
|
114
|
+
const relId = this.allocRelId();
|
|
115
|
+
const docPrId = this.nextDocPrId++;
|
|
116
|
+
const path = `word/media/${filename}`;
|
|
117
|
+
this.images.push({ relId, path, data, contentType });
|
|
118
|
+
this.relationships.push({
|
|
119
|
+
id: relId,
|
|
120
|
+
type: REL_IMAGE,
|
|
121
|
+
target: `media/${filename}`,
|
|
122
|
+
});
|
|
123
|
+
return { relId, docPrId };
|
|
124
|
+
}
|
|
110
125
|
/** Allocate a numbering definition for a list */
|
|
111
126
|
allocNumbering(ordered) {
|
|
112
127
|
const numId = this.nextNumId++;
|
|
@@ -352,7 +367,7 @@ function convertInline(node, ctx, format) {
|
|
|
352
367
|
case 'link':
|
|
353
368
|
return convertLink(node, ctx, format);
|
|
354
369
|
case 'image':
|
|
355
|
-
return convertImage(node);
|
|
370
|
+
return convertImage(node, ctx);
|
|
356
371
|
case 'break':
|
|
357
372
|
return `<w:r><w:br/></w:r>`;
|
|
358
373
|
case 'htmlInline':
|
|
@@ -385,9 +400,6 @@ function makeRun(text, format) {
|
|
|
385
400
|
}
|
|
386
401
|
function convertLink(node, ctx, format) {
|
|
387
402
|
const rId = ctx.addHyperlink(node.url);
|
|
388
|
-
const _runs = convertInlines(node.children, ctx, { ...format });
|
|
389
|
-
// Wrap each run's rPr with hyperlink styling
|
|
390
|
-
// For simplicity, emit inline runs with hyperlink color + underline
|
|
391
403
|
const styledRuns = convertInlinesWithHyperlinkStyle(node.children, ctx, format);
|
|
392
404
|
return `<w:hyperlink r:id="${rId}">${styledRuns}</w:hyperlink>`;
|
|
393
405
|
}
|
|
@@ -419,13 +431,109 @@ function makeHyperlinkRun(text, format) {
|
|
|
419
431
|
return (`<w:r><w:rPr>${rPrParts.join('')}</w:rPr>` +
|
|
420
432
|
`<w:t xml:space="preserve">${escapeXml(text)}</w:t></w:r>`);
|
|
421
433
|
}
|
|
422
|
-
function convertImage(
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
434
|
+
function convertImage(node, ctx) {
|
|
435
|
+
const imageEntry = ctx.resolvedImages.get(node.url);
|
|
436
|
+
if (!imageEntry) {
|
|
437
|
+
// No resolved data — emit placeholder text
|
|
438
|
+
const alt = node.alt || node.url;
|
|
439
|
+
return makeRun(`[Image: ${alt}]`, { italic: true });
|
|
440
|
+
}
|
|
441
|
+
const { data, contentType } = imageEntry;
|
|
442
|
+
const ext = contentType.split('/')[1]?.replace('jpeg', 'jpg') || 'png';
|
|
443
|
+
const filename = `image${ctx.images.length + 1}.${ext}`;
|
|
444
|
+
const { relId, docPrId } = ctx.addImage(data, contentType, filename);
|
|
445
|
+
// Read dimensions from binary header; fall back to 5×3 inches
|
|
446
|
+
const dims = readImageDimensions(data);
|
|
447
|
+
const EMU_PER_INCH = 914400;
|
|
448
|
+
const MAX_WIDTH_EMU = 6 * EMU_PER_INCH; // 6 inch content width
|
|
449
|
+
let cx;
|
|
450
|
+
let cy;
|
|
451
|
+
if (dims) {
|
|
452
|
+
// Scale to fit within max width, assuming 96 DPI for pixel → inch
|
|
453
|
+
const widthEmu = (dims.width / 96) * EMU_PER_INCH;
|
|
454
|
+
const heightEmu = (dims.height / 96) * EMU_PER_INCH;
|
|
455
|
+
if (widthEmu > MAX_WIDTH_EMU) {
|
|
456
|
+
const scale = MAX_WIDTH_EMU / widthEmu;
|
|
457
|
+
cx = MAX_WIDTH_EMU;
|
|
458
|
+
cy = Math.round(heightEmu * scale);
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
cx = Math.round(widthEmu);
|
|
462
|
+
cy = Math.round(heightEmu);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
cx = 5 * EMU_PER_INCH;
|
|
467
|
+
cy = 3 * EMU_PER_INCH;
|
|
468
|
+
}
|
|
469
|
+
const name = escapeXml(node.alt || filename);
|
|
470
|
+
const NS_A = 'http://schemas.openxmlformats.org/drawingml/2006/main';
|
|
471
|
+
const NS_PIC = 'http://schemas.openxmlformats.org/drawingml/2006/picture';
|
|
472
|
+
return (`<w:r><w:drawing>` +
|
|
473
|
+
`<wp:inline distT="0" distB="0" distL="0" distR="0">` +
|
|
474
|
+
`<wp:extent cx="${cx}" cy="${cy}"/>` +
|
|
475
|
+
`<wp:docPr id="${docPrId}" name="${name}"/>` +
|
|
476
|
+
`<wp:cNvGraphicFramePr>` +
|
|
477
|
+
`<a:graphicFrameLocks xmlns:a="${NS_A}" noChangeAspect="1"/>` +
|
|
478
|
+
`</wp:cNvGraphicFramePr>` +
|
|
479
|
+
`<a:graphic xmlns:a="${NS_A}">` +
|
|
480
|
+
`<a:graphicData uri="${NS_PIC}">` +
|
|
481
|
+
`<pic:pic xmlns:pic="${NS_PIC}">` +
|
|
482
|
+
`<pic:nvPicPr>` +
|
|
483
|
+
`<pic:cNvPr id="0" name="${name}"/>` +
|
|
484
|
+
`<pic:cNvPicPr/>` +
|
|
485
|
+
`</pic:nvPicPr>` +
|
|
486
|
+
`<pic:blipFill>` +
|
|
487
|
+
`<a:blip r:embed="${relId}"/>` +
|
|
488
|
+
`<a:stretch><a:fillRect/></a:stretch>` +
|
|
489
|
+
`</pic:blipFill>` +
|
|
490
|
+
`<pic:spPr>` +
|
|
491
|
+
`<a:xfrm>` +
|
|
492
|
+
`<a:off x="0" y="0"/>` +
|
|
493
|
+
`<a:ext cx="${cx}" cy="${cy}"/>` +
|
|
494
|
+
`</a:xfrm>` +
|
|
495
|
+
`<a:prstGeom prst="rect"><a:avLst/></a:prstGeom>` +
|
|
496
|
+
`</pic:spPr>` +
|
|
497
|
+
`</pic:pic>` +
|
|
498
|
+
`</a:graphicData>` +
|
|
499
|
+
`</a:graphic>` +
|
|
500
|
+
`</wp:inline>` +
|
|
501
|
+
`</w:drawing></w:r>`);
|
|
502
|
+
}
|
|
503
|
+
/** Read width/height from PNG or JPEG binary headers. */
|
|
504
|
+
function readImageDimensions(data) {
|
|
505
|
+
const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
506
|
+
if (bytes.length < 24)
|
|
507
|
+
return null;
|
|
508
|
+
// PNG: signature 0x89504E47, IHDR chunk at byte 16
|
|
509
|
+
if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47) {
|
|
510
|
+
const width = (bytes[16] << 24) | (bytes[17] << 16) | (bytes[18] << 8) | bytes[19];
|
|
511
|
+
const height = (bytes[20] << 24) | (bytes[21] << 16) | (bytes[22] << 8) | bytes[23];
|
|
512
|
+
return { width, height };
|
|
513
|
+
}
|
|
514
|
+
// JPEG: search for SOF0 (0xFFC0) or SOF2 (0xFFC2) marker
|
|
515
|
+
if (bytes[0] === 0xff && bytes[1] === 0xd8) {
|
|
516
|
+
let offset = 2;
|
|
517
|
+
while (offset < bytes.length - 9) {
|
|
518
|
+
if (bytes[offset] !== 0xff)
|
|
519
|
+
break;
|
|
520
|
+
const marker = bytes[offset + 1];
|
|
521
|
+
if (marker === 0xc0 || marker === 0xc2) {
|
|
522
|
+
const height = (bytes[offset + 5] << 8) | bytes[offset + 6];
|
|
523
|
+
const width = (bytes[offset + 7] << 8) | bytes[offset + 8];
|
|
524
|
+
return { width, height };
|
|
525
|
+
}
|
|
526
|
+
const segLen = (bytes[offset + 2] << 8) | bytes[offset + 3];
|
|
527
|
+
offset += 2 + segLen;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
// GIF: width at bytes 6-7, height at bytes 8-9 (little-endian)
|
|
531
|
+
if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46) {
|
|
532
|
+
const width = bytes[6] | (bytes[7] << 8);
|
|
533
|
+
const height = bytes[8] | (bytes[9] << 8);
|
|
534
|
+
return { width, height };
|
|
535
|
+
}
|
|
536
|
+
return null;
|
|
429
537
|
}
|
|
430
538
|
function convertFootnoteRef(node, ctx) {
|
|
431
539
|
const fnId = ctx.getFootnoteId(node.identifier);
|