@ai-matrx/print 0.5.10 → 0.6.1

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/pdf.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/pdf.ts"],"sourcesContent":["/**\n * DOM-Capture Print Utilities — Tier 2 Full-Message Print\n *\n * Captures a rendered DOM element using html2canvas, then pipes it into jsPDF.\n * This handles ALL block types because it screenshots the actual rendered output.\n *\n * Problem: Tailwind CSS 4 uses modern CSS color functions (oklch, lab, color(display-p3))\n * that html2canvas cannot parse. It calls console.error for each one, which gets\n * captured by AdminDebugContextCollector and shows up as false errors.\n *\n * Solution: Patch window.getComputedStyle during the capture so html2canvas always\n * receives safe hex fallbacks instead of unparseable color function strings.\n *\n * ---\n *\n * This module is the packaged union of three source files that always shipped\n * together:\n * • dom-capture-print-utils — the capture/PDF renderers (above),\n * • dom-capture-block-printer — the BlockPrinter factory + captureBlockElement,\n * • markdown-pdf — markdownToPdfBlob.\n *\n * Package-level seams (the only deliberate departures from the originals):\n * • `markdownToPdfBlob` takes its markdown→HTML converter and its stylesheet\n * loader INJECTED (`MarkdownPdfDeps`) instead of dynamically importing app\n * modules. The package cannot reach into an application's feature folders.\n * • `sanitizeForCapture` is a package-added export (it was module-private in\n * the original) so the sanitizer can be tested on its own.\n * • `captureBlockElement` calls `captureToPDF` directly rather than through a\n * dynamic import — html2canvas and jspdf are still lazily imported inside\n * the render functions, which is the laziness that actually matters.\n *\n * html2canvas and jspdf stay behind call-time dynamic imports: importing this\n * module pulls in neither, and the module touches `window`/`document` only when\n * one of its functions is called.\n */\n\nimport type { BlockPrinter } from \"./core\";\nimport type { MarkdownSkin } from \"./markdown\";\n\n/** Type-only view of the lazily imported defaults module. */\ntype MarkdownModule = typeof import(\"./markdown\");\n\nexport interface DomCaptureOptions {\n /** Paper size — 'a4' (default) or 'letter' */\n paperSize?: \"a4\" | \"letter\";\n /** Orientation */\n orientation?: \"portrait\" | \"landscape\";\n /** Scale factor for html2canvas (higher = sharper, slower). Default: 2 */\n scale?: number;\n /** Background colour for the capture. Default: '#ffffff' */\n background?: string;\n /** Filename for the downloaded PDF (without extension). Default: 'ai-response' */\n filename?: string;\n /** Image encoding inside the PDF. PNG is lossless; JPEG is much smaller for reports. */\n imageFormat?: \"png\" | \"jpeg\";\n /** JPEG quality from 0 to 1. Ignored for PNG. Default: 0.92. */\n imageQuality?: number;\n /** Called after each page is captured, with current page / total estimate */\n onProgress?: (page: number, total: number) => void;\n /**\n * Color-fallback theme for unparseable (oklch/lab) computed colors.\n * \"auto\" (default) follows the app's dark class — right for capturing\n * on-screen UI. Pass \"light\" for always-light offscreen document renders\n * so dark-mode fallbacks never bleed into a white-background PDF.\n */\n theme?: \"auto\" | \"light\";\n}\n\nconst PAGE_DIMS = {\n a4: { w: 210, h: 297 },\n letter: { w: 215.9, h: 279.4 },\n} as const;\n\n// Matches any modern CSS color function html2canvas cannot parse\nconst UNSAFE_COLOR_RE = /\\b(oklch|oklab|lab|lch|color)\\s*\\(/i;\n\nfunction isSafeColor(value: string): boolean {\n return !UNSAFE_COLOR_RE.test(value);\n}\n\n/**\n * Replaces modern CSS color functions in a single value string with a\n * context-appropriate hex fallback.\n */\nfunction safenColor(value: string, prop: string, isDark: boolean): string {\n if (isSafeColor(value)) return value;\n const lp = prop.toLowerCase();\n if (lp.includes(\"background\")) return isDark ? \"#1f2937\" : \"#ffffff\";\n if (lp === \"color\") return isDark ? \"#f3f4f6\" : \"#111827\";\n if (lp.includes(\"border\") || lp.includes(\"outline\"))\n return isDark ? \"#4b5563\" : \"#d1d5db\";\n if (lp === \"fill\") return isDark ? \"#9ca3af\" : \"#374151\";\n if (lp === \"stroke\") return isDark ? \"#6b7280\" : \"#6b7280\";\n return isDark ? \"#6b7280\" : \"#6b7280\";\n}\n\n/**\n * Patch window.getComputedStyle so that any call during the capture returns\n * safe hex values instead of oklch/lab/etc.\n *\n * html2canvas calls getComputedStyle extensively — this is the authoritative\n * intercept point. Returns a restore function.\n */\nfunction patchGetComputedStyle(isDark: boolean): () => void {\n // Two references on purpose. `original` is the UNBOUND function that was\n // installed before us and is what the restore puts back — restoring a bound\n // wrapper instead would leave one more `.bind` layer installed after every\n // capture, so the real function identity would never come back. `callOriginal`\n // is the bound form we invoke, because getComputedStyle needs `window` as its\n // receiver.\n const original = window.getComputedStyle;\n const callOriginal = original.bind(window);\n\n window.getComputedStyle = function patchedGetComputedStyle(\n elt: Element,\n pseudoElt?: string | null,\n ): CSSStyleDeclaration {\n const computed = callOriginal(elt, pseudoElt);\n\n return new Proxy(computed, {\n get(target, prop) {\n // CSSStyleDeclaration accessors require the native object as their\n // receiver. Using the Proxy as the receiver throws \"Illegal\n // invocation\" while html2canvas clones inline styles.\n const value: unknown = Reflect.get(target, prop, target);\n\n // Intercept string property accesses (color values)\n if (typeof prop === \"string\" && typeof value === \"string\" && value) {\n if (!isSafeColor(value)) {\n return safenColor(value, prop, isDark);\n }\n }\n\n // Intercept getPropertyValue\n if (prop === \"getPropertyValue\") {\n return (propName: string) => {\n const raw = target.getPropertyValue(propName);\n if (raw && !isSafeColor(raw)) {\n return safenColor(raw, propName, isDark);\n }\n return raw;\n };\n }\n\n if (typeof value === \"function\") {\n const fn = value as (...args: unknown[]) => unknown;\n return fn.bind(target);\n }\n\n return value;\n },\n });\n };\n\n return () => {\n window.getComputedStyle = original;\n };\n}\n\n/**\n * Capture a DOM element and export it as a multi-page PDF download.\n */\nexport async function captureToPDF(\n element: HTMLElement,\n options: DomCaptureOptions = {},\n): Promise<void> {\n const pdf = await renderElementToPdf(element, options);\n pdf.save(`${options.filename ?? \"ai-response\"}.pdf`);\n}\n\n/**\n * Capture a DOM element and return the multi-page PDF as a Blob — for\n * callers that upload/persist the PDF instead of downloading it (e.g. the\n * chat \"Save as → PDF Document\" action saving into the user's Files).\n */\nexport async function captureToPDFBlob(\n element: HTMLElement,\n options: DomCaptureOptions = {},\n): Promise<Blob> {\n const pdf = await renderElementToPdf(element, options);\n return pdf.output(\"blob\");\n}\n\n/**\n * Capture deliberately paginated DOM elements as one PDF page each.\n *\n * Use this for designed reports whose page boundaries are part of the layout.\n * The ordinary capture helpers intentionally slice one continuous canvas; this\n * variant preserves explicit report pages and therefore cannot cut a section\n * in half at an arbitrary paper boundary.\n */\nexport async function captureElementsToPDF(\n elements: HTMLElement[],\n options: DomCaptureOptions = {},\n): Promise<void> {\n const pdf = await renderElementsToPdf(elements, options);\n pdf.save(`${options.filename ?? \"ai-response\"}.pdf`);\n}\n\n/** Blob form of captureElementsToPDF for durable file-system uploads. */\nexport async function captureElementsToPDFBlob(\n elements: HTMLElement[],\n options: DomCaptureOptions = {},\n): Promise<Blob> {\n const pdf = await renderElementsToPdf(elements, options);\n return pdf.output(\"blob\");\n}\n\nasync function renderElementsToPdf(\n elements: HTMLElement[],\n options: DomCaptureOptions,\n) {\n if (elements.length === 0) {\n throw new Error(\"No report pages are available to export.\");\n }\n\n const {\n paperSize = \"a4\",\n orientation = \"portrait\",\n scale = 2,\n background = \"#ffffff\",\n imageFormat = \"png\",\n imageQuality = 0.92,\n onProgress,\n } = options;\n const [{ default: html2canvas }, { default: jsPDF }] = await Promise.all([\n import(\"html2canvas\"),\n import(\"jspdf\"),\n ]);\n const dims = PAGE_DIMS[paperSize];\n const pageW = orientation === \"portrait\" ? dims.w : dims.h;\n const pageH = orientation === \"portrait\" ? dims.h : dims.w;\n const pdf = new jsPDF({ orientation, unit: \"mm\", format: paperSize });\n const restoreGetComputedStyle = patchGetComputedStyle(false);\n\n try {\n for (const [index, element] of elements.entries()) {\n onProgress?.(index, elements.length);\n const canvas = await html2canvas(element, {\n scale,\n useCORS: true,\n backgroundColor: background,\n logging: false,\n windowWidth: element.scrollWidth,\n windowHeight: element.scrollHeight,\n });\n if (index > 0) pdf.addPage();\n const isJpeg = imageFormat === \"jpeg\";\n pdf.addImage(\n canvas.toDataURL(\n isJpeg ? \"image/jpeg\" : \"image/png\",\n isJpeg ? imageQuality : undefined,\n ),\n isJpeg ? \"JPEG\" : \"PNG\",\n 0,\n 0,\n pageW,\n pageH,\n );\n onProgress?.(index + 1, elements.length);\n }\n } finally {\n restoreGetComputedStyle();\n }\n\n return pdf;\n}\n\nasync function renderElementToPdf(\n element: HTMLElement,\n options: DomCaptureOptions = {},\n) {\n const {\n paperSize = \"a4\",\n orientation = \"portrait\",\n scale = 2,\n background = \"#ffffff\",\n imageFormat = \"png\",\n imageQuality = 0.92,\n onProgress,\n } = options;\n\n const [{ default: html2canvas }, { default: jsPDF }] = await Promise.all([\n import(\"html2canvas\"),\n import(\"jspdf\"),\n ]);\n\n const dims = PAGE_DIMS[paperSize];\n const pageW = orientation === \"portrait\" ? dims.w : dims.h;\n const pageH = orientation === \"portrait\" ? dims.h : dims.w;\n\n onProgress?.(0, 1);\n\n const isDark =\n options.theme === \"light\"\n ? false\n : document.documentElement.classList.contains(\"dark\") ||\n window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n\n const bgColor = background === \"#ffffff\" && isDark ? \"#111827\" : background;\n\n const restoreGetComputedStyle = patchGetComputedStyle(isDark);\n\n let canvas: HTMLCanvasElement;\n try {\n canvas = await html2canvas(element, {\n scale,\n useCORS: true,\n backgroundColor: bgColor,\n logging: false,\n windowWidth: element.scrollWidth,\n windowHeight: element.scrollHeight,\n });\n } finally {\n restoreGetComputedStyle();\n }\n\n // imageFormat/imageQuality are honoured here too. They are documented on\n // DomCaptureOptions with no \"elements-only\" caveat, and the paginated\n // renderer above already obeys them; this path used to hardcode PNG and\n // silently drop whatever the caller asked for. PNG remains the default, so\n // callers that never set the option are unaffected.\n const isJpeg = imageFormat === \"jpeg\";\n const imgData = canvas.toDataURL(\n isJpeg ? \"image/jpeg\" : \"image/png\",\n isJpeg ? imageQuality : undefined,\n );\n const imgFormat = isJpeg ? \"JPEG\" : \"PNG\";\n const imgW = pageW;\n const imgH = (canvas.height * pageW) / canvas.width;\n\n const pdf = new jsPDF({ orientation, unit: \"mm\", format: paperSize });\n\n let heightLeft = imgH;\n let yOffset = 0;\n let page = 0;\n\n pdf.addImage(imgData, imgFormat, 0, yOffset, imgW, imgH);\n heightLeft -= pageH;\n page++;\n\n const totalPages = Math.ceil(imgH / pageH);\n while (heightLeft > 0) {\n yOffset -= pageH;\n pdf.addPage();\n pdf.addImage(imgData, imgFormat, 0, yOffset, imgW, imgH);\n heightLeft -= pageH;\n page++;\n onProgress?.(page, totalPages);\n }\n\n return pdf;\n}\n\n/**\n * Capture a DOM element and copy it to clipboard (or download) as PNG.\n */\nexport async function captureToClipboardImage(\n element: HTMLElement,\n): Promise<void> {\n const { default: html2canvas } = await import(\"html2canvas\");\n\n const isDark =\n document.documentElement.classList.contains(\"dark\") ||\n window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n\n const restoreGetComputedStyle = patchGetComputedStyle(isDark);\n\n let canvas: HTMLCanvasElement;\n try {\n canvas = await html2canvas(element, {\n scale: 2,\n useCORS: true,\n backgroundColor: isDark ? \"#111827\" : \"#ffffff\",\n logging: false,\n });\n } finally {\n restoreGetComputedStyle();\n }\n\n // The returned promise covers the copy/download, not just the capture. The\n // original resolved the moment toBlob was *scheduled*, so `await\n // captureToClipboardImage(el)` followed by a \"Copied!\" toast fired before\n // anything had been copied, and a throw inside the fallback path escaped as\n // an unhandled rejection.\n await new Promise<void>((resolve, reject) => {\n canvas.toBlob(async (blob) => {\n if (!blob) {\n resolve();\n return;\n }\n try {\n try {\n await navigator.clipboard.write([\n new ClipboardItem({ \"image/png\": blob }),\n ]);\n } catch {\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = \"capture.png\";\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n }\n resolve();\n } catch (error) {\n reject(error instanceof Error ? error : new Error(String(error)));\n }\n }, \"image/png\");\n });\n}\n\n/**\n * DOM-Capture BlockPrinter factory\n *\n * Creates a BlockPrinter that captures a provided DOM element and exports it as PDF.\n * Used by visual blocks (Timeline, ComparisonTable, DecisionTree, Diagram) where\n * the rendered DOM is the most faithful representation.\n *\n * Usage inside a block component:\n * const { captureRef, printAction } = useBlockDomCapture('Timeline');\n * // attach captureRef to the block's root element\n * // add printAction to customActions or controls\n */\n\n/**\n * Create a BlockPrinter backed by DOM capture.\n * The printer's `print(data)` call is a no-op — actual printing is triggered\n * imperatively via `captureElement(el)`.\n *\n * This exists so the block can register itself in the BlockPrinter contract\n * while keeping the DOM reference local to the component.\n */\nexport function createDomCapturePrinter(blockLabel: string): BlockPrinter {\n return {\n label: `Print ${blockLabel}`,\n variants: [],\n // No-op: DOM capture blocks use captureElement() directly\n print: () => {},\n };\n}\n\n/**\n * Capture a DOM element as PDF.\n * Called directly by block components when their print button is clicked.\n *\n * @param element - The DOM element to capture\n * @param filename - Output filename (without extension)\n * @param orientation - \"landscape\" (default) for wide visual blocks; \"portrait\" for tall/list content\n */\nexport async function captureBlockElement(\n element: HTMLElement,\n filename: string,\n orientation: \"landscape\" | \"portrait\" = \"landscape\",\n): Promise<void> {\n await captureToPDF(element, {\n filename,\n scale: 2,\n orientation,\n });\n}\n\n/**\n * markdownToPdfBlob — render a markdown string to a styled, multi-page PDF\n * Blob without any visible UI.\n *\n * Pipeline: markdown → WordPress-styled HTML (the same converter the\n * publish/copy-HTML flows use) → sanitized offscreen DOM host → html2canvas +\n * jsPDF via `captureToPDFBlob`. Everything heavy stays lazy-loaded behind\n * this module.\n *\n * Used by the chat \"Save as → PDF Document\" action, which uploads the blob\n * into the user's Files. Reuse this for any \"content → PDF file\" need — do\n * not fork another markdown-to-pdf path.\n */\n\n/**\n * OVERRIDES for markdownToPdfBlob — every field is optional.\n *\n * Both ports ship a working default in `@ai-matrx/print/markdown`\n * (`markdownToHtml` + `getMarkdownStylesheet`), so `markdownToPdfBlob(md)`\n * alone produces a styled PDF in a fresh app. Supplying a field replaces that\n * default; supplying neither is the normal case. Until 0.3.0 both fields were\n * REQUIRED, which forced every host to build the hard half itself.\n */\nexport interface MarkdownPdfDeps {\n /** markdown → HTML body string. Default: the package's `markdownToHtml`. */\n convertToHtml?: (markdown: string) => string;\n /** the stylesheet the rendered HTML expects. Default: the \"document\" skin. */\n loadCss?: () => Promise<string> | string;\n /** Which shipped look to use when `convertToHtml`/`loadCss` are not given. */\n skin?: MarkdownSkin;\n /** Host brand token overrides appended after the package's default values. */\n tokens?: string;\n /**\n * Where degraded conversion and unreachable images are announced. Nothing\n * fails silently: without a sink these still surface as `console.warn`.\n */\n onNotice?: (notice: MarkdownPdfNotice) => void;\n /**\n * How long to wait for images to decode before capturing, in ms.\n * Default 8000. html2canvas paints whatever has loaded, so an un-awaited\n * image is a blank rectangle in the PDF with no error anywhere.\n */\n imageTimeoutMs?: number;\n}\n\n/** A degradation the PDF lane detected, with the remedy. Never swallowed. */\nexport interface MarkdownPdfNotice {\n code:\n | \"flashcard-malformed\"\n | \"empty-output\"\n | \"html-stripped\"\n | \"image-unreachable\"\n | \"image-timeout\";\n message: string;\n remedy: string;\n}\n\n/**\n * Markdown can carry raw HTML; we are about to attach it to the live\n * document, so strip anything executable. html2canvas ignores scripts, but\n * inline handlers (`onerror`) and javascript: URLs would fire on attach.\n */\nexport function sanitizeForCapture(html: string): DocumentFragment {\n const template = document.createElement(\"template\");\n template.innerHTML = html;\n const fragment = template.content;\n\n fragment\n .querySelectorAll(\"script, iframe, object, embed, link, meta\")\n .forEach((el) => el.remove());\n fragment.querySelectorAll(\"*\").forEach((el) => {\n for (const attr of Array.from(el.attributes)) {\n const name = attr.name.toLowerCase();\n if (name.startsWith(\"on\")) el.removeAttribute(attr.name);\n if (\n (name === \"href\" || name === \"src\") &&\n attr.value.trim().toLowerCase().startsWith(\"javascript:\")\n ) {\n el.removeAttribute(attr.name);\n }\n }\n });\n return fragment;\n}\n\n/**\n * Wait for every `<img>` inside a host to finish decoding, and REPORT the ones\n * that never will.\n *\n * html2canvas captures whatever the browser has painted: an image still in\n * flight becomes a blank rectangle and an image that 404s becomes a blank\n * rectangle, both without a single error anywhere. This is the difference\n * between a PDF that is missing a figure and a PDF that is missing a figure\n * AND says so.\n */\nasync function settleImages(\n host: HTMLElement,\n timeoutMs: number,\n notify: (notice: MarkdownPdfNotice) => void,\n): Promise<void> {\n const images = Array.from(host.querySelectorAll(\"img\"));\n if (images.length === 0) return;\n\n const timedOut: string[] = [];\n await Promise.all(\n images.map(\n (img) =>\n new Promise<void>((resolve) => {\n if (img.complete) {\n if (img.naturalWidth === 0) {\n notify({\n code: \"image-unreachable\",\n message: `An image could not be loaded and will be blank in the PDF: ${img.src}`,\n remedy:\n \"Use a durable, publicly reachable https URL — a print render is an unauthenticated document and cannot use session-signed or expiring links.\",\n });\n }\n resolve();\n return;\n }\n const timer = setTimeout(() => {\n timedOut.push(img.src);\n resolve();\n }, timeoutMs);\n const done = (ok: boolean): void => {\n clearTimeout(timer);\n if (!ok) {\n notify({\n code: \"image-unreachable\",\n message: `An image could not be loaded and will be blank in the PDF: ${img.src}`,\n remedy:\n \"Use a durable, publicly reachable https URL — a print render is an unauthenticated document and cannot use session-signed or expiring links.\",\n });\n }\n resolve();\n };\n img.addEventListener(\"load\", () => done(true), { once: true });\n img.addEventListener(\"error\", () => done(false), { once: true });\n }),\n ),\n );\n\n if (timedOut.length > 0) {\n notify({\n code: \"image-timeout\",\n message: `${timedOut.length} image(s) did not finish loading in ${timeoutMs}ms and may be blank in the PDF: ${timedOut.join(\", \")}`,\n remedy:\n \"Raise `imageTimeoutMs`, or serve the images from a faster/CDN origin before exporting.\",\n });\n }\n}\n\nexport async function markdownToPdfBlob(\n markdown: string,\n deps: MarkdownPdfDeps = {},\n): Promise<Blob> {\n if (!markdown.trim()) {\n throw new Error(\"Nothing to convert — the content is empty.\");\n }\n\n // A caller with no sink still gets told. Silence is not an option the API\n // offers, because a silently degraded PDF looks exactly like a correct one.\n const notify = (notice: MarkdownPdfNotice): void => {\n if (deps.onNotice) {\n deps.onNotice(notice);\n return;\n }\n console.warn(`[@ai-matrx/print] ${notice.message} → ${notice.remedy}`);\n };\n\n // The defaults live behind a lazy import: a consumer that injects its own\n // converter and stylesheet never pulls the ~20KB of package CSS into its\n // bundle, and neither does one that imports /pdf for DOM capture only.\n //\n // The specifier is the package's OWN public name, not a relative path: tsup\n // runs with `splitting: false`, so `import(\"./markdown\")` gets inlined into\n // this chunk and the lazy edge silently disappears (measured — dist/pdf.js\n // grew by the full stylesheet). A self-reference is external to the bundle\n // and resolves through the export map at runtime. Same pattern as\n // @ai-matrx/associations `./react/lazy`.\n const needsDefaults = !deps.convertToHtml || !deps.loadCss;\n const markdownModule = needsDefaults\n ? ((await import(\"@ai-matrx/print/markdown\")) as MarkdownModule)\n : undefined;\n\n const bodyHtml = deps.convertToHtml\n ? deps.convertToHtml(markdown)\n : (markdownModule as MarkdownModule).markdownToHtmlForSkin(\n markdown,\n deps.skin ?? \"document\",\n { onNotice: notify },\n );\n\n const css = deps.loadCss\n ? await deps.loadCss()\n : (markdownModule as MarkdownModule).getMarkdownStylesheet({\n skin: deps.skin ?? \"document\",\n ...(deps.tokens === undefined ? {} : { tokens: deps.tokens }),\n });\n\n if (!bodyHtml.trim()) {\n throw new Error(\n \"The markdown converted to an empty document — nothing would be printed. \" +\n \"Check the content is more than whitespace or reasoning blocks.\",\n );\n }\n\n // Offscreen host — must be in the document for html2canvas to compute\n // styles, but never visible and never intercepting input.\n const host = document.createElement(\"div\");\n host.style.cssText =\n \"position:fixed;left:-10000px;top:0;width:800px;background:#ffffff;color:#111827;pointer-events:none;\";\n host.setAttribute(\"aria-hidden\", \"true\");\n\n const style = document.createElement(\"style\");\n style.textContent = css;\n host.appendChild(style);\n\n const contentEl = document.createElement(\"div\");\n // The document stylesheet is class-scoped (.matrx-*) and expects this\n // container; the article sheet styles bare tags under `.content`.\n contentEl.className =\n (deps.skin ?? \"document\") === \"article\" ? \"content\" : \"matrx-content-container\";\n contentEl.style.cssText = \"padding:32px;background:#ffffff;\";\n contentEl.appendChild(sanitizeForCapture(bodyHtml));\n host.appendChild(contentEl);\n\n document.body.appendChild(host);\n try {\n await settleImages(host, deps.imageTimeoutMs ?? 8000, notify);\n // Always-light document render: \"#fff\" (not \"#ffffff\") bypasses the\n // dark-mode background flip, and theme:\"light\" keeps the oklch-fallback\n // patch from substituting dark text colors while the app is in dark mode.\n return await captureToPDFBlob(host, { background: \"#fff\", theme: \"light\" });\n } catch (error) {\n // The raw html2canvas/jsPDF failure is meaningless to a user and to a host\n // author. Name the stage and the remedy here so no host writes a catch\n // block to reinterpret it (C22).\n throw new Error(\n `The PDF could not be rendered from this content: ${\n error instanceof Error ? error.message : String(error)\n }. Try again; if it repeats, the content likely contains an element the ` +\n \"renderer cannot rasterize (a cross-origin image without CORS headers, \" +\n \"or an embedded iframe/canvas).\",\n error instanceof Error ? { cause: error } : undefined,\n );\n } finally {\n host.remove();\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/directives.ts","../src/pdf.ts"],"sourcesContent":["/**\n * @ai-matrx/print/directives — the ONE definition of the print directives\n * markdown can carry, shared by every surface that reads markdown.\n *\n * Why a separate, zero-dependency entry: the directive syntax has three\n * readers — this package's two converters (print window / PDF / HTML page),\n * the chat renderer's remark plugin (the on-screen preview), and any editor\n * that inserts one. If each re-derived the regex they would drift the first\n * time someone added an alias. `/markdown` carries ~20KB of stylesheet, so the\n * chat bundle imports the syntax from here instead and pays nothing.\n *\n * ── Page break ────────────────────────────────────────────────────────────\n * A line that is exactly one of these (case-insensitive, surrounding\n * whitespace ignored) forces the next content onto a new printed page:\n *\n * <!-- pagebreak --> ← canonical; invisible in any other renderer\n * <!-- page-break --> <!-- page break --> <!-- newpage -->\n * \\pagebreak \\newpage ← LaTeX / pandoc habit\n * <div style=\"page-break-after: always\"></div> ← Typora / VS Code habit\n * (also `page-break-before`, `break-after: page`, `break-before: page`,\n * and `<div class=\"page-break\"></div>` / `class=\"pagebreak\"`)\n *\n * On screen every surface draws the same idea: a dashed rule labelled\n * \"Page break\" with generous padding. In print it is a zero-height element\n * with `break-after: page`.\n */\n\n/** The form editors insert and docs recommend. */\nexport const PAGE_BREAK_MARKDOWN = \"<!-- pagebreak -->\";\n\n/** The class every surface puts on a rendered page break. */\nexport const PAGE_BREAK_CLASS = \"matrx-page-break\";\n\n/** Screen label for a rendered page break. */\nexport const PAGE_BREAK_LABEL = \"Page break\";\n\nconst COMMENT_FORM = /^<!--\\s*(?:page[\\s_-]?break|new[\\s_-]?page)\\s*-->$/i;\nconst LATEX_FORM = /^\\\\(?:pagebreak|newpage|clearpage)\\s*(?:\\{\\s*\\})?$/i;\nconst DIV_FORM = /^<div\\b([^>]*)>\\s*<\\/div>$|^<div\\b([^>]*)\\/>$/i;\nconst DIV_BREAK_ATTR =\n /(?:page-break-(?:after|before)\\s*:\\s*always|break-(?:after|before)\\s*:\\s*page|class\\s*=\\s*[\"'](?:[^\"']*\\s)?page-?break(?:\\s[^\"']*)?[\"'])/i;\n\n/** Is this single line a page-break directive? */\nexport function isPageBreakLine(line: string): boolean {\n const t = line.trim();\n if (t.length < 8 || t.length > 200) return false;\n if (COMMENT_FORM.test(t) || LATEX_FORM.test(t)) return true;\n const div = DIV_FORM.exec(t);\n if (!div) return false;\n return DIV_BREAK_ATTR.test(div[1] ?? div[2] ?? \"\");\n}\n\n/**\n * Split markdown into the pieces that belong on separate pages. Fence-aware:\n * a directive inside a ``` / ~~~ code block is code, not a break. Always\n * returns at least one piece; empty pieces (two breaks in a row, a break at\n * the very start or end) are kept out so no blank page is produced.\n */\nexport function splitAtPageBreaks(markdown: string): string[] {\n const lines = markdown.split(/\\r?\\n/);\n const pieces: string[] = [];\n let current: string[] = [];\n let fence: string | null = null;\n\n for (const line of lines) {\n // Any indent — the converters accept list-nested fences, so must this.\n const fenceMatch = /^\\s*(`{3,}|~{3,})/.exec(line);\n if (fenceMatch) {\n const marker = fenceMatch[1] as string;\n if (fence === null) fence = marker;\n else if (marker[0] === fence[0] && marker.length >= fence.length) fence = null;\n } else if (fence === null && isPageBreakLine(line)) {\n if (current.join(\"\\n\").trim()) pieces.push(current.join(\"\\n\"));\n current = [];\n continue;\n }\n current.push(line);\n }\n if (current.join(\"\\n\").trim() || pieces.length === 0) pieces.push(current.join(\"\\n\"));\n return pieces;\n}\n","/**\n * DOM-Capture Print Utilities — Tier 2 Full-Message Print\n *\n * Captures a rendered DOM element using html2canvas, then pipes it into jsPDF.\n * This handles ALL block types because it screenshots the actual rendered output.\n *\n * Problem: Tailwind CSS 4 uses modern CSS color functions (oklch, lab, color(display-p3))\n * that html2canvas cannot parse. It calls console.error for each one, which gets\n * captured by AdminDebugContextCollector and shows up as false errors.\n *\n * Solution: Patch window.getComputedStyle during the capture so html2canvas always\n * receives safe hex fallbacks instead of unparseable color function strings.\n *\n * ---\n *\n * This module is the packaged union of three source files that always shipped\n * together:\n * • dom-capture-print-utils — the capture/PDF renderers (above),\n * • dom-capture-block-printer — the BlockPrinter factory + captureBlockElement,\n * • markdown-pdf — markdownToPdfBlob.\n *\n * Package-level seams (the only deliberate departures from the originals):\n * • `markdownToPdfBlob` takes its markdown→HTML converter and its stylesheet\n * loader INJECTED (`MarkdownPdfDeps`) instead of dynamically importing app\n * modules. The package cannot reach into an application's feature folders.\n * • `sanitizeForCapture` is a package-added export (it was module-private in\n * the original) so the sanitizer can be tested on its own.\n * • `captureBlockElement` calls `captureToPDF` directly rather than through a\n * dynamic import — html2canvas and jspdf are still lazily imported inside\n * the render functions, which is the laziness that actually matters.\n *\n * html2canvas and jspdf stay behind call-time dynamic imports: importing this\n * module pulls in neither, and the module touches `window`/`document` only when\n * one of its functions is called.\n */\n\nimport type { BlockPrinter } from \"./core\";\nimport type { MarkdownSkin } from \"./markdown\";\nimport { PAGE_BREAK_CLASS } from \"./directives\";\n\n/** Type-only view of the lazily imported defaults module. */\ntype MarkdownModule = typeof import(\"./markdown\");\n\nexport interface DomCaptureOptions {\n /** Paper size — 'a4' (default) or 'letter' */\n paperSize?: \"a4\" | \"letter\";\n /** Orientation */\n orientation?: \"portrait\" | \"landscape\";\n /** Scale factor for html2canvas (higher = sharper, slower). Default: 2 */\n scale?: number;\n /** Background colour for the capture. Default: '#ffffff' */\n background?: string;\n /** Filename for the downloaded PDF (without extension). Default: 'ai-response' */\n filename?: string;\n /** Image encoding inside the PDF. PNG is lossless; JPEG is much smaller for reports. */\n imageFormat?: \"png\" | \"jpeg\";\n /** JPEG quality from 0 to 1. Ignored for PNG. Default: 0.92. */\n imageQuality?: number;\n /** Called after each page is captured, with current page / total estimate */\n onProgress?: (page: number, total: number) => void;\n /**\n * Color-fallback theme for unparseable (oklch/lab) computed colors.\n * \"auto\" (default) follows the app's dark class — right for capturing\n * on-screen UI. Pass \"light\" for always-light offscreen document renders\n * so dark-mode fallbacks never bleed into a white-background PDF.\n */\n theme?: \"auto\" | \"light\";\n}\n\nconst PAGE_DIMS = {\n a4: { w: 210, h: 297 },\n letter: { w: 215.9, h: 279.4 },\n} as const;\n\n// Matches any modern CSS color function html2canvas cannot parse\nconst UNSAFE_COLOR_RE = /\\b(oklch|oklab|lab|lch|color)\\s*\\(/i;\n\nfunction isSafeColor(value: string): boolean {\n return !UNSAFE_COLOR_RE.test(value);\n}\n\n/**\n * Replaces modern CSS color functions in a single value string with a\n * context-appropriate hex fallback.\n */\nfunction safenColor(value: string, prop: string, isDark: boolean): string {\n if (isSafeColor(value)) return value;\n const lp = prop.toLowerCase();\n if (lp.includes(\"background\")) return isDark ? \"#1f2937\" : \"#ffffff\";\n if (lp === \"color\") return isDark ? \"#f3f4f6\" : \"#111827\";\n if (lp.includes(\"border\") || lp.includes(\"outline\"))\n return isDark ? \"#4b5563\" : \"#d1d5db\";\n if (lp === \"fill\") return isDark ? \"#9ca3af\" : \"#374151\";\n if (lp === \"stroke\") return isDark ? \"#6b7280\" : \"#6b7280\";\n return isDark ? \"#6b7280\" : \"#6b7280\";\n}\n\n/**\n * Patch window.getComputedStyle so that any call during the capture returns\n * safe hex values instead of oklch/lab/etc.\n *\n * html2canvas calls getComputedStyle extensively — this is the authoritative\n * intercept point. Returns a restore function.\n */\nfunction patchGetComputedStyle(isDark: boolean): () => void {\n // Two references on purpose. `original` is the UNBOUND function that was\n // installed before us and is what the restore puts back — restoring a bound\n // wrapper instead would leave one more `.bind` layer installed after every\n // capture, so the real function identity would never come back. `callOriginal`\n // is the bound form we invoke, because getComputedStyle needs `window` as its\n // receiver.\n const original = window.getComputedStyle;\n const callOriginal = original.bind(window);\n\n window.getComputedStyle = function patchedGetComputedStyle(\n elt: Element,\n pseudoElt?: string | null,\n ): CSSStyleDeclaration {\n const computed = callOriginal(elt, pseudoElt);\n\n return new Proxy(computed, {\n get(target, prop) {\n // CSSStyleDeclaration accessors require the native object as their\n // receiver. Using the Proxy as the receiver throws \"Illegal\n // invocation\" while html2canvas clones inline styles.\n const value: unknown = Reflect.get(target, prop, target);\n\n // Intercept string property accesses (color values)\n if (typeof prop === \"string\" && typeof value === \"string\" && value) {\n if (!isSafeColor(value)) {\n return safenColor(value, prop, isDark);\n }\n }\n\n // Intercept getPropertyValue\n if (prop === \"getPropertyValue\") {\n return (propName: string) => {\n const raw = target.getPropertyValue(propName);\n if (raw && !isSafeColor(raw)) {\n return safenColor(raw, propName, isDark);\n }\n return raw;\n };\n }\n\n if (typeof value === \"function\") {\n const fn = value as (...args: unknown[]) => unknown;\n return fn.bind(target);\n }\n\n return value;\n },\n });\n };\n\n return () => {\n window.getComputedStyle = original;\n };\n}\n\n/**\n * Capture a DOM element and export it as a multi-page PDF download.\n */\nexport async function captureToPDF(\n element: HTMLElement,\n options: DomCaptureOptions = {},\n): Promise<void> {\n const pdf = await renderElementToPdf(element, options);\n pdf.save(`${options.filename ?? \"ai-response\"}.pdf`);\n}\n\n/**\n * Capture a DOM element and return the multi-page PDF as a Blob — for\n * callers that upload/persist the PDF instead of downloading it (e.g. the\n * chat \"Save as → PDF Document\" action saving into the user's Files).\n */\nexport async function captureToPDFBlob(\n element: HTMLElement,\n options: DomCaptureOptions = {},\n): Promise<Blob> {\n const pdf = await renderElementToPdf(element, options);\n return pdf.output(\"blob\");\n}\n\n/**\n * Capture deliberately paginated DOM elements as one PDF page each.\n *\n * Use this for designed reports whose page boundaries are part of the layout.\n * The ordinary capture helpers intentionally slice one continuous canvas; this\n * variant preserves explicit report pages and therefore cannot cut a section\n * in half at an arbitrary paper boundary.\n */\nexport async function captureElementsToPDF(\n elements: HTMLElement[],\n options: DomCaptureOptions = {},\n): Promise<void> {\n const pdf = await renderElementsToPdf(elements, options);\n pdf.save(`${options.filename ?? \"ai-response\"}.pdf`);\n}\n\n/** Blob form of captureElementsToPDF for durable file-system uploads. */\nexport async function captureElementsToPDFBlob(\n elements: HTMLElement[],\n options: DomCaptureOptions = {},\n): Promise<Blob> {\n const pdf = await renderElementsToPdf(elements, options);\n return pdf.output(\"blob\");\n}\n\nasync function renderElementsToPdf(\n elements: HTMLElement[],\n options: DomCaptureOptions,\n) {\n if (elements.length === 0) {\n throw new Error(\"No report pages are available to export.\");\n }\n\n const {\n paperSize = \"a4\",\n orientation = \"portrait\",\n scale = 2,\n background = \"#ffffff\",\n imageFormat = \"png\",\n imageQuality = 0.92,\n onProgress,\n } = options;\n const [{ default: html2canvas }, { default: jsPDF }] = await Promise.all([\n import(\"html2canvas\"),\n import(\"jspdf\"),\n ]);\n const dims = PAGE_DIMS[paperSize];\n const pageW = orientation === \"portrait\" ? dims.w : dims.h;\n const pageH = orientation === \"portrait\" ? dims.h : dims.w;\n const pdf = new jsPDF({ orientation, unit: \"mm\", format: paperSize });\n const restoreGetComputedStyle = patchGetComputedStyle(false);\n\n try {\n for (const [index, element] of elements.entries()) {\n onProgress?.(index, elements.length);\n const canvas = await html2canvas(element, {\n scale,\n useCORS: true,\n backgroundColor: background,\n logging: false,\n windowWidth: element.scrollWidth,\n windowHeight: element.scrollHeight,\n });\n if (index > 0) pdf.addPage();\n const isJpeg = imageFormat === \"jpeg\";\n pdf.addImage(\n canvas.toDataURL(\n isJpeg ? \"image/jpeg\" : \"image/png\",\n isJpeg ? imageQuality : undefined,\n ),\n isJpeg ? \"JPEG\" : \"PNG\",\n 0,\n 0,\n pageW,\n pageH,\n );\n onProgress?.(index + 1, elements.length);\n }\n } finally {\n restoreGetComputedStyle();\n }\n\n return pdf;\n}\n\nasync function renderElementToPdf(\n element: HTMLElement,\n options: DomCaptureOptions = {},\n) {\n return renderSectionsToPdf([element], options);\n}\n\n/**\n * Capture one or more continuous sections into one PDF. Each section is\n * sliced across as many pages as it needs, and every section after the first\n * STARTS ON A NEW PAGE — that is how a markdown page break survives the\n * canvas lane, where CSS `break-after` means nothing.\n */\nasync function renderSectionsToPdf(\n elements: HTMLElement[],\n options: DomCaptureOptions = {},\n) {\n const {\n paperSize = \"a4\",\n orientation = \"portrait\",\n scale = 2,\n background = \"#ffffff\",\n imageFormat = \"png\",\n imageQuality = 0.92,\n onProgress,\n } = options;\n\n const [{ default: html2canvas }, { default: jsPDF }] = await Promise.all([\n import(\"html2canvas\"),\n import(\"jspdf\"),\n ]);\n\n const dims = PAGE_DIMS[paperSize];\n const pageW = orientation === \"portrait\" ? dims.w : dims.h;\n const pageH = orientation === \"portrait\" ? dims.h : dims.w;\n\n onProgress?.(0, 1);\n\n const isDark =\n options.theme === \"light\"\n ? false\n : document.documentElement.classList.contains(\"dark\") ||\n window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n\n const bgColor = background === \"#ffffff\" && isDark ? \"#111827\" : background;\n\n const restoreGetComputedStyle = patchGetComputedStyle(isDark);\n\n const canvases: HTMLCanvasElement[] = [];\n try {\n for (const element of elements) {\n canvases.push(\n await html2canvas(element, {\n scale,\n useCORS: true,\n backgroundColor: bgColor,\n logging: false,\n windowWidth: element.scrollWidth,\n windowHeight: element.scrollHeight,\n }),\n );\n }\n } finally {\n restoreGetComputedStyle();\n }\n\n // imageFormat/imageQuality are honoured here too. They are documented on\n // DomCaptureOptions with no \"elements-only\" caveat, and the paginated\n // renderer above already obeys them; this path used to hardcode PNG and\n // silently drop whatever the caller asked for. PNG remains the default, so\n // callers that never set the option are unaffected.\n const isJpeg = imageFormat === \"jpeg\";\n const imgFormat = isJpeg ? \"JPEG\" : \"PNG\";\n const pdf = new jsPDF({ orientation, unit: \"mm\", format: paperSize });\n\n // Half a millimetre of tolerance: an image that is an exact multiple of the\n // page height must not grow a blank trailing page from float rounding.\n const EPSILON_MM = 0.5;\n const sliceCounts = canvases.map((c) =>\n Math.max(1, Math.ceil(((c.height * pageW) / c.width - EPSILON_MM) / pageH)),\n );\n const totalPages = sliceCounts.reduce((a, b) => a + b, 0);\n let page = 0;\n\n canvases.forEach((canvas, sectionIndex) => {\n const imgData = canvas.toDataURL(\n isJpeg ? \"image/jpeg\" : \"image/png\",\n isJpeg ? imageQuality : undefined,\n );\n const imgW = pageW;\n const imgH = (canvas.height * pageW) / canvas.width;\n let yOffset = 0;\n let heightLeft = imgH;\n let first = true;\n while (first || heightLeft > EPSILON_MM) {\n if (page > 0) pdf.addPage();\n pdf.addImage(imgData, imgFormat, 0, yOffset, imgW, imgH);\n yOffset -= pageH;\n heightLeft -= pageH;\n page++;\n first = false;\n if (page > 1 || sectionIndex > 0) onProgress?.(page, totalPages);\n }\n });\n\n return pdf;\n}\n\n/**\n * Capture a DOM element and copy it to clipboard (or download) as PNG.\n */\nexport async function captureToClipboardImage(\n element: HTMLElement,\n): Promise<void> {\n const { default: html2canvas } = await import(\"html2canvas\");\n\n const isDark =\n document.documentElement.classList.contains(\"dark\") ||\n window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n\n const restoreGetComputedStyle = patchGetComputedStyle(isDark);\n\n let canvas: HTMLCanvasElement;\n try {\n canvas = await html2canvas(element, {\n scale: 2,\n useCORS: true,\n backgroundColor: isDark ? \"#111827\" : \"#ffffff\",\n logging: false,\n });\n } finally {\n restoreGetComputedStyle();\n }\n\n // The returned promise covers the copy/download, not just the capture. The\n // original resolved the moment toBlob was *scheduled*, so `await\n // captureToClipboardImage(el)` followed by a \"Copied!\" toast fired before\n // anything had been copied, and a throw inside the fallback path escaped as\n // an unhandled rejection.\n await new Promise<void>((resolve, reject) => {\n canvas.toBlob(async (blob) => {\n if (!blob) {\n resolve();\n return;\n }\n try {\n try {\n await navigator.clipboard.write([\n new ClipboardItem({ \"image/png\": blob }),\n ]);\n } catch {\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = \"capture.png\";\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n }\n resolve();\n } catch (error) {\n reject(error instanceof Error ? error : new Error(String(error)));\n }\n }, \"image/png\");\n });\n}\n\n/**\n * DOM-Capture BlockPrinter factory\n *\n * Creates a BlockPrinter that captures a provided DOM element and exports it as PDF.\n * Used by visual blocks (Timeline, ComparisonTable, DecisionTree, Diagram) where\n * the rendered DOM is the most faithful representation.\n *\n * Usage inside a block component:\n * const { captureRef, printAction } = useBlockDomCapture('Timeline');\n * // attach captureRef to the block's root element\n * // add printAction to customActions or controls\n */\n\n/**\n * Create a BlockPrinter backed by DOM capture.\n * The printer's `print(data)` call is a no-op — actual printing is triggered\n * imperatively via `captureElement(el)`.\n *\n * This exists so the block can register itself in the BlockPrinter contract\n * while keeping the DOM reference local to the component.\n */\nexport function createDomCapturePrinter(blockLabel: string): BlockPrinter {\n return {\n label: `Print ${blockLabel}`,\n variants: [],\n // No-op: DOM capture blocks use captureElement() directly\n print: () => {},\n };\n}\n\n/**\n * Capture a DOM element as PDF.\n * Called directly by block components when their print button is clicked.\n *\n * @param element - The DOM element to capture\n * @param filename - Output filename (without extension)\n * @param orientation - \"landscape\" (default) for wide visual blocks; \"portrait\" for tall/list content\n */\nexport async function captureBlockElement(\n element: HTMLElement,\n filename: string,\n orientation: \"landscape\" | \"portrait\" = \"landscape\",\n): Promise<void> {\n await captureToPDF(element, {\n filename,\n scale: 2,\n orientation,\n });\n}\n\n/**\n * markdownToPdfBlob — render a markdown string to a styled, multi-page PDF\n * Blob without any visible UI.\n *\n * Pipeline: markdown → WordPress-styled HTML (the same converter the\n * publish/copy-HTML flows use) → sanitized offscreen DOM host → html2canvas +\n * jsPDF via `captureToPDFBlob`. Everything heavy stays lazy-loaded behind\n * this module.\n *\n * Used by the chat \"Save as → PDF Document\" action, which uploads the blob\n * into the user's Files. Reuse this for any \"content → PDF file\" need — do\n * not fork another markdown-to-pdf path.\n */\n\n/**\n * OVERRIDES for markdownToPdfBlob — every field is optional.\n *\n * Both ports ship a working default in `@ai-matrx/print/markdown`\n * (`markdownToHtml` + `getMarkdownStylesheet`), so `markdownToPdfBlob(md)`\n * alone produces a styled PDF in a fresh app. Supplying a field replaces that\n * default; supplying neither is the normal case. Until 0.3.0 both fields were\n * REQUIRED, which forced every host to build the hard half itself.\n */\nexport interface MarkdownPdfDeps {\n /** markdown → HTML body string. Default: the package's `markdownToHtml`. */\n convertToHtml?: (markdown: string) => string;\n /** the stylesheet the rendered HTML expects. Default: the \"document\" skin. */\n loadCss?: () => Promise<string> | string;\n /** Which shipped look to use when `convertToHtml`/`loadCss` are not given. */\n skin?: MarkdownSkin;\n /** Host brand token overrides appended after the package's default values. */\n tokens?: string;\n /**\n * Where degraded conversion and unreachable images are announced. Nothing\n * fails silently: without a sink these still surface as `console.warn`.\n */\n onNotice?: (notice: MarkdownPdfNotice) => void;\n /**\n * How long to wait for images to decode before capturing, in ms.\n * Default 8000. html2canvas paints whatever has loaded, so an un-awaited\n * image is a blank rectangle in the PDF with no error anywhere.\n */\n imageTimeoutMs?: number;\n}\n\n/** A degradation the PDF lane detected, with the remedy. Never swallowed. */\nexport interface MarkdownPdfNotice {\n code:\n | \"flashcard-malformed\"\n | \"empty-output\"\n | \"html-stripped\"\n | \"image-unreachable\"\n | \"image-timeout\";\n message: string;\n remedy: string;\n}\n\n/**\n * Markdown can carry raw HTML; we are about to attach it to the live\n * document, so strip anything executable. html2canvas ignores scripts, but\n * inline handlers (`onerror`) and javascript: URLs would fire on attach.\n */\nexport function sanitizeForCapture(html: string): DocumentFragment {\n const template = document.createElement(\"template\");\n template.innerHTML = html;\n const fragment = template.content;\n\n fragment\n .querySelectorAll(\"script, iframe, object, embed, link, meta\")\n .forEach((el) => el.remove());\n fragment.querySelectorAll(\"*\").forEach((el) => {\n for (const attr of Array.from(el.attributes)) {\n const name = attr.name.toLowerCase();\n if (name.startsWith(\"on\")) el.removeAttribute(attr.name);\n if (\n (name === \"href\" || name === \"src\") &&\n attr.value.trim().toLowerCase().startsWith(\"javascript:\")\n ) {\n el.removeAttribute(attr.name);\n }\n }\n });\n return fragment;\n}\n\n/**\n * Wait for every `<img>` inside a host to finish decoding, and REPORT the ones\n * that never will.\n *\n * html2canvas captures whatever the browser has painted: an image still in\n * flight becomes a blank rectangle and an image that 404s becomes a blank\n * rectangle, both without a single error anywhere. This is the difference\n * between a PDF that is missing a figure and a PDF that is missing a figure\n * AND says so.\n */\nasync function settleImages(\n host: HTMLElement,\n timeoutMs: number,\n notify: (notice: MarkdownPdfNotice) => void,\n): Promise<void> {\n const images = Array.from(host.querySelectorAll(\"img\"));\n if (images.length === 0) return;\n\n const timedOut: string[] = [];\n await Promise.all(\n images.map(\n (img) =>\n new Promise<void>((resolve) => {\n if (img.complete) {\n if (img.naturalWidth === 0) {\n notify({\n code: \"image-unreachable\",\n message: `An image could not be loaded and will be blank in the PDF: ${img.src}`,\n remedy:\n \"Use a durable, publicly reachable https URL — a print render is an unauthenticated document and cannot use session-signed or expiring links.\",\n });\n }\n resolve();\n return;\n }\n const timer = setTimeout(() => {\n timedOut.push(img.src);\n resolve();\n }, timeoutMs);\n const done = (ok: boolean): void => {\n clearTimeout(timer);\n if (!ok) {\n notify({\n code: \"image-unreachable\",\n message: `An image could not be loaded and will be blank in the PDF: ${img.src}`,\n remedy:\n \"Use a durable, publicly reachable https URL — a print render is an unauthenticated document and cannot use session-signed or expiring links.\",\n });\n }\n resolve();\n };\n img.addEventListener(\"load\", () => done(true), { once: true });\n img.addEventListener(\"error\", () => done(false), { once: true });\n }),\n ),\n );\n\n if (timedOut.length > 0) {\n notify({\n code: \"image-timeout\",\n message: `${timedOut.length} image(s) did not finish loading in ${timeoutMs}ms and may be blank in the PDF: ${timedOut.join(\", \")}`,\n remedy:\n \"Raise `imageTimeoutMs`, or serve the images from a faster/CDN origin before exporting.\",\n });\n }\n}\n\nexport async function markdownToPdfBlob(\n markdown: string,\n deps: MarkdownPdfDeps = {},\n): Promise<Blob> {\n if (!markdown.trim()) {\n throw new Error(\"Nothing to convert — the content is empty.\");\n }\n\n // A caller with no sink still gets told. Silence is not an option the API\n // offers, because a silently degraded PDF looks exactly like a correct one.\n const notify = (notice: MarkdownPdfNotice): void => {\n if (deps.onNotice) {\n deps.onNotice(notice);\n return;\n }\n console.warn(`[@ai-matrx/print] ${notice.message} → ${notice.remedy}`);\n };\n\n // The defaults live behind a lazy import: a consumer that injects its own\n // converter and stylesheet never pulls the ~20KB of package CSS into its\n // bundle, and neither does one that imports /pdf for DOM capture only.\n //\n // The specifier is the package's OWN public name, not a relative path: tsup\n // runs with `splitting: false`, so `import(\"./markdown\")` gets inlined into\n // this chunk and the lazy edge silently disappears (measured — dist/pdf.js\n // grew by the full stylesheet). A self-reference is external to the bundle\n // and resolves through the export map at runtime. Same pattern as\n // @ai-matrx/associations `./react/lazy`.\n const needsDefaults = !deps.convertToHtml || !deps.loadCss;\n const markdownModule = needsDefaults\n ? ((await import(\"@ai-matrx/print/markdown\")) as MarkdownModule)\n : undefined;\n\n const bodyHtml = deps.convertToHtml\n ? deps.convertToHtml(markdown)\n : (markdownModule as MarkdownModule).markdownToHtmlForSkin(\n markdown,\n deps.skin ?? \"document\",\n { onNotice: notify },\n );\n\n const css = deps.loadCss\n ? await deps.loadCss()\n : (markdownModule as MarkdownModule).getMarkdownStylesheet({\n skin: deps.skin ?? \"document\",\n ...(deps.tokens === undefined ? {} : { tokens: deps.tokens }),\n });\n\n if (!bodyHtml.trim()) {\n throw new Error(\n \"The markdown converted to an empty document — nothing would be printed. \" +\n \"Check the content is more than whitespace or reasoning blocks.\",\n );\n }\n\n // Offscreen host — must be in the document for html2canvas to compute\n // styles, but never visible and never intercepting input.\n const host = document.createElement(\"div\");\n host.style.cssText =\n \"position:fixed;left:-10000px;top:0;width:800px;background:#ffffff;color:#111827;pointer-events:none;\";\n host.setAttribute(\"aria-hidden\", \"true\");\n\n const style = document.createElement(\"style\");\n style.textContent = css;\n host.appendChild(style);\n\n const contentEl = document.createElement(\"div\");\n // The document stylesheet is class-scoped (.matrx-*) and expects this\n // container; the article sheet styles bare tags under `.content`.\n contentEl.className =\n (deps.skin ?? \"document\") === \"article\" ? \"content\" : \"matrx-content-container\";\n contentEl.style.cssText = \"padding:32px;background:#ffffff;\";\n contentEl.appendChild(sanitizeForCapture(bodyHtml));\n host.appendChild(contentEl);\n\n const hosts = splitHostAtPageBreaks(host, contentEl);\n for (const h of hosts) document.body.appendChild(h);\n try {\n for (const h of hosts) await settleImages(h, deps.imageTimeoutMs ?? 8000, notify);\n // Always-light document render: \"#fff\" (not \"#ffffff\") bypasses the\n // dark-mode background flip, and theme:\"light\" keeps the oklch-fallback\n // patch from substituting dark text colors while the app is in dark mode.\n const pdf = await renderSectionsToPdf(hosts, { background: \"#fff\", theme: \"light\" });\n return pdf.output(\"blob\");\n } catch (error) {\n // The raw html2canvas/jsPDF failure is meaningless to a user and to a host\n // author. Name the stage and the remedy here so no host writes a catch\n // block to reinterpret it (C22).\n throw new Error(\n `The PDF could not be rendered from this content: ${\n error instanceof Error ? error.message : String(error)\n }. Try again; if it repeats, the content likely contains an element the ` +\n \"renderer cannot rasterize (a cross-origin image without CORS headers, \" +\n \"or an embedded iframe/canvas).\",\n error instanceof Error ? { cause: error } : undefined,\n );\n } finally {\n for (const h of hosts) h.remove();\n }\n}\n\n/**\n * Split an offscreen capture host at its top-level page breaks into one host\n * per printed section (same stylesheet, same container shells), so each\n * section starts on a fresh PDF page. No breaks → the host itself.\n */\nfunction splitHostAtPageBreaks(host: HTMLElement, contentEl: HTMLElement): HTMLElement[] {\n const inner = contentEl.firstElementChild;\n const flow =\n inner instanceof HTMLElement && inner.classList.contains(\"matrx-content-container\")\n ? inner\n : contentEl;\n const hasBreak = Array.from(flow.children).some((c) => c.classList.contains(PAGE_BREAK_CLASS));\n if (!hasBreak) return [host];\n\n const style = host.querySelector(\"style\");\n const shell = (): { h: HTMLElement; target: HTMLElement } => {\n const h = host.cloneNode(false) as HTMLElement;\n if (style) h.appendChild(style.cloneNode(true));\n const c = contentEl.cloneNode(false) as HTMLElement;\n h.appendChild(c);\n if (flow === contentEl) return { h, target: c };\n const f = flow.cloneNode(false) as HTMLElement;\n c.appendChild(f);\n return { h, target: f };\n };\n\n const sections: HTMLElement[] = [];\n let current = shell();\n let hasContent = false;\n for (const node of Array.from(flow.childNodes)) {\n if (node instanceof HTMLElement && node.classList.contains(PAGE_BREAK_CLASS)) {\n if (hasContent) {\n sections.push(current.h);\n current = shell();\n hasContent = false;\n }\n continue;\n }\n if (node.nodeType === 1 || (node.textContent ?? \"\").trim()) hasContent = true;\n current.target.appendChild(node);\n }\n if (hasContent || sections.length === 0) sections.push(current.h);\n return sections;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/print",
3
- "version": "0.5.10",
3
+ "version": "0.6.1",
4
4
  "description": "The AI Matrx print system: the BlockPrinter contract and print-window composition, the flashcards deck printer (duplex-mirrored cut sheets, Avery 5388, study sheets), QR generation with spec quiet zones and e-commerce payload builders, label-sheet printing on Avery and roll stock with custom label formats, and a complete markdown pipeline: a shipped markdown converter, two default print stylesheets, print windows, and markdown/DOM to PDF.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -186,6 +186,16 @@
186
186
  "types": "./dist/zpl.d.cts",
187
187
  "default": "./dist/zpl.cjs"
188
188
  }
189
+ },
190
+ "./directives": {
191
+ "import": {
192
+ "types": "./dist/directives.d.ts",
193
+ "default": "./dist/directives.js"
194
+ },
195
+ "require": {
196
+ "types": "./dist/directives.d.cts",
197
+ "default": "./dist/directives.cjs"
198
+ }
189
199
  }
190
200
  },
191
201
  "dependencies": {