@ai-matrx/print 0.2.1 → 0.3.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/CHANGELOG.md +76 -0
- package/README.md +44 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/markdown.cjs +1260 -0
- package/dist/markdown.cjs.map +1 -0
- package/dist/markdown.d.cts +177 -0
- package/dist/markdown.d.ts +177 -0
- package/dist/markdown.js +1237 -0
- package/dist/markdown.js.map +1 -0
- package/dist/pdf.cjs +77 -4
- package/dist/pdf.cjs.map +1 -1
- package/dist/pdf.d.cts +35 -9
- package/dist/pdf.d.ts +35 -9
- package/dist/pdf.js +77 -4
- package/dist/pdf.js.map +1 -1
- package/package.json +12 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core.ts","../src/markdown-css.ts","../src/markdown.ts"],"sourcesContent":["/**\n * @ai-matrx/print/core — the Block Print contract & print-window composition.\n *\n * Ported VERBATIM from matrx-frontend `lib/block-print/block-print-utils.ts`\n * (2026-08-29 state, including the `number` and `select` PrintSetting types the\n * commerce label lane added). Zero dependencies; touches `window` only at call\n * time, so the module stays import-inert and RSC-safe.\n *\n * BlockPrinter is the only interface a host system needs to know about.\n * Each printable unit exports its own BlockPrinter alongside its render\n * component; host code only ever calls print(data, variantId, settings).\n *\n * `buildFitTextScript` is the ONE auto-shrink script (the flashcards\n * FIT_TEXT pattern, parameterized by selector so the label lane shares it).\n */\n\n// ─── Contract ────────────────────────────────────────────────────────────────\n\nexport interface PrintVariant {\n id: string;\n label: string;\n description?: string;\n}\n\n/**\n * A single user-configurable setting shown in the print dialog.\n * Extend the discriminated union for new control types\n * without breaking existing printers.\n */\nexport type PrintSetting =\n | {\n type: \"boolean\";\n id: string;\n label: string;\n description?: string;\n /** Default value shown when the dialog first opens */\n defaultValue: boolean;\n /** Which variant IDs this setting applies to. Omit = applies to all. */\n appliesTo?: string[];\n }\n | {\n /**\n * A numeric \"from → to\" range control, rendered as two small number inputs.\n * Writes two entries into the resolved PrintSettings map — one under fromId,\n * one under toId. Printers interpret 0 (or missing) as \"no bound\" by convention.\n */\n type: \"range\";\n /** Stable id used as React key — not written to the settings map */\n id: string;\n label: string;\n description?: string;\n /** Settings map key for the lower bound value */\n fromId: string;\n /** Settings map key for the upper bound value */\n toId: string;\n defaultFrom: number;\n defaultTo: number;\n /** Minimum accepted value for each input (inclusive). Default 0. */\n min?: number;\n /** Placeholder shown when the input is empty (0). Default \"start\" / \"end\". */\n fromPlaceholder?: string;\n toPlaceholder?: string;\n /** Which variant IDs this setting applies to. Omit = applies to all. */\n appliesTo?: string[];\n }\n | {\n /** A single numeric input (e.g. \"start at label #\", \"copies\"). */\n type: \"number\";\n id: string;\n label: string;\n description?: string;\n defaultValue: number;\n /** Minimum accepted value (inclusive). Default 0. */\n min?: number;\n /** Maximum accepted value (inclusive). Omit = unbounded. */\n max?: number;\n /** Which variant IDs this setting applies to. Omit = applies to all. */\n appliesTo?: string[];\n }\n | {\n /** A one-of-N choice rendered as a compact segmented row. */\n type: \"select\";\n id: string;\n label: string;\n description?: string;\n options: { value: string; label: string }[];\n defaultValue: string;\n /** Which variant IDs this setting applies to. Omit = applies to all. */\n appliesTo?: string[];\n };\n\n/** A resolved map of setting id → current value, passed to print(). */\nexport type PrintSettings = Record<string, boolean | string | number>;\n\n/**\n * The self-contained print capability that each block can export.\n * Host code only ever calls print(data, variantId, settings) — it never inspects internals.\n */\nexport interface BlockPrinter {\n /** Tooltip / aria-label for the print button in the block header */\n label: string;\n\n /**\n * Variants offered to the user before printing.\n * Empty array = no options dialog shown; prints immediately with defaults.\n */\n variants: PrintVariant[];\n\n /**\n * Optional user-configurable settings shown below the variant picker.\n * Omit entirely if the block has no configurable settings.\n */\n settings?: PrintSetting[];\n\n /**\n * Execute the print.\n * data — the block's structured data (serverData or parsed content)\n * variantId — which variant was chosen (undefined = default / no variants)\n * settings — resolved setting values chosen by the user\n *\n * MAY resolve to a PrintOutcome (\"opened\" | \"downloaded\") so the host can\n * tell the user when the popup-blocked download fallback fired; `void`\n * stays legal for printers that don't go through a print window.\n */\n print: (\n data: unknown,\n variantId?: string,\n settings?: PrintSettings,\n ) => void | PrintOutcome | Promise<void | PrintOutcome>;\n}\n\n// ─── Shared Utilities ─────────────────────────────────────────────────────────\n\nconst PRINT_STYLES = `\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;\n font-size: 11pt;\n line-height: 1.6;\n color: #1a1a1a;\n background: #fff;\n max-width: 860px;\n margin: 0 auto;\n padding: 28px 36px;\n }\n\n .print-actions {\n display: flex;\n gap: 10px;\n margin-bottom: 24px;\n padding-bottom: 18px;\n border-bottom: 2px solid #e5e7eb;\n }\n\n .print-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 20px;\n border-radius: 8px;\n font-size: 13px;\n font-family: inherit;\n cursor: pointer;\n border: none;\n font-weight: 600;\n }\n\n .print-btn-primary { background: #2563eb; color: #fff; }\n .print-btn-secondary { background: #f3f4f6; color: #374151; border: 1px solid #d1d5db; }\n\n h1 { font-size: 22pt; font-weight: 700; margin-bottom: 0.5em; }\n h2 { font-size: 16pt; font-weight: 700; margin: 1.2em 0 0.4em; border-bottom: 1px solid #e5e7eb; padding-bottom: 0.2em; }\n h3 { font-size: 13pt; font-weight: 600; margin: 1em 0 0.3em; }\n p { margin-bottom: 0.7em; }\n\n table {\n width: 100%;\n border-collapse: collapse;\n margin: 1em 0;\n font-size: 10pt;\n }\n th {\n background: #1e293b;\n color: #f8fafc;\n font-weight: 600;\n text-align: left;\n padding: 7px 12px;\n border: 1px solid #334155;\n }\n td {\n padding: 6px 12px;\n border: 1px solid #e2e8f0;\n vertical-align: top;\n }\n tr:nth-child(even) td { background: #f8fafc; }\n\n @media print {\n body { max-width: 100%; padding: 0; }\n .print-actions { display: none !important; }\n th { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n }\n`;\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\n/**\n * Wrap body HTML in a complete, styled print document.\n */\nexport function buildPrintDocument(\n bodyHtml: string,\n title = \"AI Response\",\n extraStyles = \"\",\n): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${escapeHtml(title)}</title>\n <style>\n${PRINT_STYLES}\n${extraStyles}\n </style>\n</head>\n<body>\n <div class=\"print-actions\">\n <button class=\"print-btn print-btn-primary\" onclick=\"window.print()\">Print / Save as PDF</button>\n <button class=\"print-btn print-btn-secondary\" onclick=\"window.close()\">Close</button>\n </div>\n <div class=\"content\">\n${bodyHtml}\n </div>\n</body>\n</html>`;\n}\n\n/**\n * How a print request actually reached the user.\n *\n * \"opened\" — a print window/tab was opened and the document written into it.\n * \"downloaded\" — the popup was blocked, so the document was downloaded as an\n * .html file instead (the contract's fallback). Hosts SHOULD\n * surface this to the user (e.g. a toast saying the print file\n * was downloaded) — a blocked popup plus a silent download is\n * indistinguishable from \"nothing happened\", which is exactly\n * how QA filed it (2026-08-30, F5/F6/F8).\n */\nexport type PrintOutcome = \"opened\" | \"downloaded\";\n\n/**\n * Open a new browser window and write a complete HTML document into it.\n * Falls back to downloading an .html file if the popup is blocked.\n *\n * Returns which path was taken so callers can tell the user when the\n * fallback fired. `windowFeatures` customizes the popup geometry (the\n * flashcards fixed-geometry variants pass a wider window).\n */\nexport function openPrintWindow(\n htmlDocument: string,\n filename = \"print\",\n windowFeatures = \"width=920,height=720,scrollbars=yes\",\n): PrintOutcome {\n const win = window.open(\"\", \"_blank\", windowFeatures);\n if (!win) {\n // Popup blocked — fall back to file download\n const blob = new Blob([htmlDocument], { type: \"text/html;charset=utf-8\" });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `${filename.replace(/\\s+/g, \"-\").toLowerCase()}.html`;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n // Revoke on a macrotask so the browser has started the blob fetch —\n // a synchronous revoke races the download in some engines.\n setTimeout(() => URL.revokeObjectURL(url), 0);\n return \"downloaded\";\n }\n win.document.open();\n win.document.write(htmlDocument);\n win.document.close();\n return \"opened\";\n}\n\n/**\n * Shorthand: build the document and open it in one call.\n * Returns the same PrintOutcome as openPrintWindow.\n */\nexport function printHtmlContent(\n bodyHtml: string,\n title = \"AI Response\",\n extraStyles = \"\",\n): PrintOutcome {\n const doc = buildPrintDocument(bodyHtml, title, extraStyles);\n return openPrintWindow(doc, title);\n}\n\n/**\n * Build the auto-shrink script that runs INSIDE a printed window: for every\n * element matching each selector, its font-size is reduced (0.5pt steps, 6pt\n * floor) until it fits its parent element's box. This is the flashcards\n * FIT_TEXT behavior verbatim, parameterized by selector so every lane\n * (flashcards card text, Avery text, label captions) shares the ONE loop.\n *\n * Pass the same selectors the original used to get byte-identical behavior:\n * flashcards passes `[\".card-text .card-text-inner\", \".avery-text-wrap .avery-text\"]`.\n */\nexport function buildFitTextScript(selectors: readonly string[]): string {\n const selectorsJson = JSON.stringify([...selectors]);\n return `\n<script>\n(function() {\n function fitText(el, container) {\n // container is the bounding box we must fit inside\n var maxW = container.clientWidth;\n var maxH = container.clientHeight;\n var size = parseFloat(window.getComputedStyle(el).fontSize);\n var minSize = 6;\n // Give the element a chance to reflow at current size before measuring\n el.style.fontSize = size + \"pt\";\n while (size > minSize && (el.scrollWidth > maxW + 1 || el.scrollHeight > maxH + 1)) {\n size -= 0.5;\n el.style.fontSize = size + \"pt\";\n }\n }\n function runAll() {\n var selectors = ${selectorsJson};\n for (var s = 0; s < selectors.length; s++) {\n var els = document.querySelectorAll(selectors[s]);\n for (var i = 0; i < els.length; i++) {\n fitText(els[i], els[i].parentElement);\n }\n }\n }\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", runAll);\n } else {\n // document.write pages may already be loaded\n setTimeout(runAll, 0);\n }\n})();\n</script>`;\n}\n\nexport { escapeHtml };\n","/**\n * Default print stylesheets for `@ai-matrx/print/markdown`.\n *\n * Two skins, ONE semantic-token vocabulary (C26 — structural CSS ships in the\n * package, the token CONTRACT is enforced, token VALUES are host-overridable):\n *\n * \"document\" — the `.matrx-*` content sheet. What `markdownToHtml` emits,\n * what the markdown→PDF lane renders, and what pastes straight\n * into a WordPress theme.\n * \"article\" — the serif AI-response sheet used by the print-window lane.\n *\n * Every colour is `var(--matrx-print-<role>, <default>)`. The inline fallback\n * is what makes the drop-in test pass in a fresh, unthemed app; the `:root`\n * block is the shipped default token-values sheet a host overrides by\n * appending its own `:root` rule (`getMarkdownStylesheet({ tokens })`).\n *\n * Both sheets carry the print hard parts — `@media print`, page-break\n * avoidance, orphans/widows, `print-color-adjust` — so no host re-derives them.\n *\n * THIS FILE IS THE SOURCE OF TRUTH. It was seeded once from the two host\n * stylesheets it replaced (matrx-frontend's WordPress sheet and its serif\n * print sheet, both deleted in the same session); there is no generator to\n * re-run and no upstream to diff against. Edit it here.\n *\n * DO NOT hand-edit colour literals back in: a hardcoded colour in package CSS\n * is a C26 violation. Add a role to the vocabulary instead.\n */\n\n/** Default token VALUES for the document skin. */\nexport const MARKDOWN_DOCUMENT_TOKENS_CSS = `:root {\n --matrx-print-accent: #667eea;\n --matrx-print-accent-2: #764ba2;\n --matrx-print-bg: #ffffff;\n --matrx-print-border: #e1e1e1;\n --matrx-print-border-muted: #d1d5db;\n --matrx-print-border-strong: #e5e5e5;\n --matrx-print-code-bg: #f5f5f5;\n --matrx-print-code-block-bg: #f8f8f8;\n --matrx-print-code-block-fg: #333;\n --matrx-print-code-fg: #d73a49;\n --matrx-print-fg: #1a1a1a;\n --matrx-print-fg-muted: #4a4a4a;\n --matrx-print-fg-subtle: #6b7280;\n --matrx-print-heading: #2a2a2a;\n --matrx-print-heading-sub: #3a3a3a;\n --matrx-print-link-fg: #374151;\n --matrx-print-link-hover-fg: #1f2937;\n --matrx-print-link-underline: #9ca3af;\n --matrx-print-quote-fg: #666;\n --matrx-print-shadow: rgba(0, 0, 0, 0.1);\n --matrx-print-shadow-strong: rgba(0, 0, 0, 0.2);\n --matrx-print-success: #2d5016;\n --matrx-print-success-2: #1e3a0f;\n --matrx-print-surface: #f8f9fa;\n --matrx-print-table-fg: #333333;\n}`;\n\n/** Default token VALUES for the article skin. */\nexport const MARKDOWN_ARTICLE_TOKENS_CSS = `:root {\n --matrx-print-accent: #2563eb;\n --matrx-print-bg: #fff;\n --matrx-print-border: #e5e7eb;\n --matrx-print-border-muted: #d1d5db;\n --matrx-print-code-bg: #f1f5f9;\n --matrx-print-fg: #1a1a1a;\n --matrx-print-fg-muted: #374151;\n --matrx-print-fg-subtle: #64748b;\n --matrx-print-heading: #111827;\n --matrx-print-ink: #0f172a;\n --matrx-print-line: #e2e8f0;\n --matrx-print-quote-bg: #f5f3ff;\n --matrx-print-quote-border: #6366f1;\n --matrx-print-surface: #f8fafc;\n --matrx-print-surface-sunken: #f3f4f6;\n --matrx-print-table-head-bg: #1e293b;\n --matrx-print-table-head-border: #334155;\n}`;\n\n/** Structural CSS for the document skin (`.matrx-*` classes). */\nexport const MARKDOWN_DOCUMENT_CSS = `/* \n * MATRX WordPress CSS - Production Ready\n * Use this CSS in your WordPress theme to style MATRX content\n * All styles are scoped to .matrx-content-container to avoid conflicts\n */\n\n/* Content Container */\n.matrx-content-container {\n max-width: 800px;\n margin: 0 auto;\n padding: 2rem;\n line-height: 1.6;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n}\n\n/* Typography */\n.matrx-h1 {\n font-size: 2.5rem;\n font-weight: 700;\n margin: 0 0 1.5rem 0;\n color: var(--matrx-print-fg, #1a1a1a);\n line-height: 1.2;\n}\n\n.matrx-h2 {\n font-size: 1.8rem;\n font-weight: 600;\n margin: 3rem 0 1rem 0 !important;\n color: var(--matrx-print-heading, #2a2a2a);\n line-height: 1.3 !important;\n border-bottom: 2px solid var(--matrx-print-border-strong, #e5e5e5);\n padding-bottom: 0.75rem !important;\n padding-top: 0 !important;\n}\n\n.matrx-h3 {\n font-size: 1.3rem;\n font-weight: 600;\n margin: 2rem 0 1rem 0;\n color: var(--matrx-print-heading-sub, #3a3a3a);\n line-height: 1.4;\n}\n\n.matrx-h4 {\n font-size: 1.1rem;\n font-weight: 600;\n margin: 1.5rem 0 0.75rem 0;\n color: var(--matrx-print-heading-sub, #3a3a3a);\n line-height: 1.4;\n}\n\n.matrx-h5 {\n font-size: 1rem;\n font-weight: 600;\n margin: 1.25rem 0 0.5rem 0;\n color: var(--matrx-print-heading-sub, #3a3a3a);\n line-height: 1.4;\n}\n\n.matrx-h6 {\n font-size: 0.9rem;\n font-weight: 600;\n margin: 1rem 0 0.5rem 0;\n color: var(--matrx-print-heading-sub, #3a3a3a);\n line-height: 1.4;\n}\n\n/* Paragraphs */\n.matrx-intro {\n font-size: 1.1rem;\n color: var(--matrx-print-fg-muted, #4a4a4a);\n margin-bottom: 2rem;\n padding: 1.5rem;\n background: var(--matrx-print-surface, #f8f9fa);\n border-left: 4px solid var(--matrx-print-border-muted, #d1d5db);\n border-radius: 0 8px 8px 0;\n}\n\n.matrx-paragraph {\n font-size: 1rem;\n color: var(--matrx-print-fg-muted, #4a4a4a);\n margin-bottom: 1.5rem;\n text-align: justify;\n}\n\n/* Text Formatting */\n.matrx-em {\n font-style: italic;\n color: var(--matrx-print-heading, #2a2a2a);\n}\n\n.matrx-strong {\n font-weight: 600;\n color: var(--matrx-print-heading, #2a2a2a);\n}\n\n/* Links */\n.matrx-link {\n color: var(--matrx-print-link-fg, #374151);\n text-decoration: underline;\n text-decoration-color: var(--matrx-print-border-muted, #d1d5db);\n text-underline-offset: 3px;\n transition: all 0.2s ease;\n}\n\n.matrx-link:hover {\n color: var(--matrx-print-link-hover-fg, #1f2937);\n text-decoration-color: var(--matrx-print-link-underline, #9ca3af);\n}\n\n/* Images */\n.matrx-image {\n max-width: 100%;\n height: auto;\n display: block;\n margin: 1.5rem auto;\n border-radius: 8px;\n box-shadow: 0 2px 8px var(--matrx-print-shadow, rgba(0, 0, 0, 0.1));\n}\n\n/* Lists */\n.matrx-list {\n margin: 1.5rem 0;\n padding-left: 0;\n}\n\n.matrx-bullet-list {\n list-style: none;\n}\n\n.matrx-list-item {\n margin-bottom: 1rem;\n padding-left: 1.5rem;\n position: relative;\n color: var(--matrx-print-fg-muted, #4a4a4a);\n}\n\n.matrx-list-item::before {\n content: \"•\";\n color: var(--matrx-print-fg-subtle, #6b7280);\n font-weight: bold;\n position: absolute;\n left: 0;\n top: 0;\n font-size: 1.2rem;\n}\n\n/* Numbered Lists */\n.matrx-numbered-list {\n list-style: decimal;\n padding-left: 2rem;\n}\n\n.matrx-numbered-list > .matrx-list-item {\n padding-left: 0.5rem;\n}\n\n.matrx-numbered-list > .matrx-list-item::before {\n content: none;\n /* Remove the bullet */\n}\n\n/* Nested list indentation */\n.matrx-nested-list {\n margin-top: 0.5rem;\n margin-bottom: 0.5rem;\n padding-left: 1.5rem !important;\n}\n\n/* Ensure nested lists within list items are properly indented */\n.matrx-list-item > .matrx-list {\n margin-top: 0.5rem;\n margin-bottom: 0.5rem;\n padding-left: 1.5rem;\n}\n\n/* For multiple levels of nesting, increase indentation */\n.matrx-nested-list .matrx-nested-list {\n padding-left: 1.5rem !important;\n}\n\n/* Code */\n.matrx-content-container .matrx-inline-code {\n background-color: var(--matrx-print-code-bg, #f5f5f5);\n padding: 0.125rem 0.25rem;\n border-radius: 0.25rem;\n font-family: \"Monaco\", \"Menlo\", \"Ubuntu Mono\", monospace;\n font-size: 0.875rem;\n color: var(--matrx-print-code-fg, #d73a49);\n}\n\n.matrx-content-container .matrx-code-block {\n background-color: var(--matrx-print-code-block-bg, #f8f8f8);\n border: 1px solid var(--matrx-print-border, #e1e1e1);\n border-radius: 0.375rem;\n padding: 1rem;\n margin: 1.5rem 0;\n overflow-x: auto;\n}\n\n.matrx-content-container .matrx-code {\n font-family: \"Monaco\", \"Menlo\", \"Ubuntu Mono\", monospace;\n font-size: 0.875rem;\n line-height: 1.5;\n color: var(--matrx-print-code-block-fg, #333);\n}\n\n/* Blockquotes */\n.matrx-content-container .matrx-blockquote {\n border-left: 4px solid var(--matrx-print-border, #e1e1e1);\n padding-left: 1rem;\n margin: 1.5rem 0;\n font-style: italic;\n color: var(--matrx-print-quote-fg, #666);\n}\n\n/* Tables */\n.matrx-content-container .matrx-table {\n width: 100%;\n border-collapse: collapse;\n margin: 1.5rem 0;\n border: 1px solid var(--matrx-print-border, #e1e1e1);\n}\n\n.matrx-content-container .matrx-table-header {\n background-color: var(--matrx-print-surface, #f8f9fa);\n font-weight: 600;\n padding: 0.75rem;\n border: 1px solid var(--matrx-print-border, #e1e1e1);\n text-align: left;\n}\n\n.matrx-content-container .matrx-table-cell {\n padding: 0.75rem;\n border: 1px solid var(--matrx-print-border, #e1e1e1);\n}\n\n.matrx-content-container .matrx-table-row:nth-child(even) {\n background-color: var(--matrx-print-surface, #f8f9fa);\n}\n\n/* Images */\n.matrx-content-container .matrx-image {\n max-width: 100%;\n height: auto;\n border-radius: 0.375rem;\n margin: 1rem 0;\n}\n\n/* Horizontal Rules */\n.matrx-content-container .matrx-hr {\n border: none;\n border-top: 1px solid var(--matrx-print-border, #e1e1e1);\n margin: 2rem 0;\n}\n\n/* FAQ Styles - Clean Design */\n.matrx-faq-item {\n margin-bottom: 2rem;\n padding: 1.5rem;\n background: var(--matrx-print-surface, #f8f9fa);\n border-radius: 8px;\n border-left: 4px solid var(--matrx-print-border-muted, #d1d5db);\n}\n\n.matrx-faq-question {\n font-size: 1.2rem;\n font-weight: 600;\n margin: 0 0 0.75rem 0;\n color: var(--matrx-print-heading, #2a2a2a);\n}\n\n.matrx-faq-answer {\n margin: 0;\n color: var(--matrx-print-fg-muted, #4a4a4a);\n line-height: 1.6;\n}\n\n/* Code Blocks */\n.matrx-inline-code {\n background-color: var(--matrx-print-code-bg, #f5f5f5);\n padding: 0.125rem 0.25rem;\n border-radius: 0.25rem;\n font-family: \"Monaco\", \"Menlo\", \"Ubuntu Mono\", monospace;\n font-size: 0.875rem;\n color: var(--matrx-print-code-fg, #d73a49);\n}\n\n.matrx-code-block {\n background-color: var(--matrx-print-code-block-bg, #f8f8f8);\n border: 1px solid var(--matrx-print-border, #e1e1e1);\n border-radius: 0.375rem;\n padding: 1rem;\n margin: 1.5rem 0;\n overflow-x: auto;\n}\n\n.matrx-code {\n font-family: \"Monaco\", \"Menlo\", \"Ubuntu Mono\", monospace;\n font-size: 0.875rem;\n line-height: 1.5;\n color: var(--matrx-print-code-block-fg, #333);\n}\n\n/* Blockquotes */\n.matrx-blockquote {\n border-left: 4px solid var(--matrx-print-border, #e1e1e1);\n padding-left: 1rem;\n margin: 1.5rem 0;\n font-style: italic;\n color: var(--matrx-print-quote-fg, #666);\n}\n\n/* Tables */\n.matrx-table {\n width: 100%;\n border-collapse: collapse;\n margin: 1.5rem 0;\n border: 1px solid var(--matrx-print-border, #e1e1e1);\n color: var(--matrx-print-table-fg, #333333);\n}\n\n.matrx-table-head {\n background-color: var(--matrx-print-surface, #f8f9fa);\n}\n\n.matrx-table-body {\n background-color: var(--matrx-print-bg, #ffffff);\n}\n\n.matrx-table-header {\n background-color: var(--matrx-print-surface, #f8f9fa);\n font-weight: 600;\n padding: 0.75rem;\n border: 1px solid var(--matrx-print-border, #e1e1e1);\n text-align: left;\n color: var(--matrx-print-table-fg, #333333);\n}\n\n.matrx-table-cell {\n padding: 0.75rem;\n border: 1px solid var(--matrx-print-border, #e1e1e1);\n color: var(--matrx-print-table-fg, #333333);\n}\n\n.matrx-table-row {\n color: var(--matrx-print-table-fg, #333333);\n}\n\n.matrx-table-row:nth-child(even) {\n background-color: var(--matrx-print-surface, #f8f9fa);\n}\n\n/* Ensure table content doesn't inherit external colors */\n.matrx-table * {\n color: inherit;\n}\n\n/* Images */\n.matrx-image {\n max-width: 100%;\n height: auto;\n border-radius: 0.375rem;\n margin: 1rem 0;\n}\n\n/* Horizontal Rules */\n.matrx-hr {\n border: none;\n border-top: 1px solid var(--matrx-print-border, #e1e1e1);\n margin: 2rem 0;\n}\n\n/* Utility Classes */\n.matrx-div {\n margin: 0.5rem 0;\n}\n\n.matrx-span {\n /* Inherit parent styling by default */\n}\n\n/* Flashcard Styles */\n.matrx-flashcards-container {\n margin: 2rem 0;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.matrx-flashcard {\n perspective: 1000px;\n perspective-origin: center center;\n height: 200px;\n cursor: pointer;\n position: relative;\n margin-bottom: 0.5rem;\n}\n\n.matrx-flashcard-input {\n display: none;\n}\n\n.matrx-flashcard-inner {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n transition: transform 0.6s;\n transform-style: preserve-3d;\n transform-origin: center center;\n}\n\n.matrx-flashcard-input:checked ~ .matrx-flashcard-inner {\n transform: rotateY(180deg);\n}\n\n.matrx-flashcard-front,\n.matrx-flashcard-back {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n backface-visibility: hidden;\n border-radius: 12px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1.5rem;\n box-shadow: 0 4px 6px var(--matrx-print-shadow, rgba(0, 0, 0, 0.1));\n transition: box-shadow 0.3s ease;\n box-sizing: border-box;\n}\n\n.matrx-flashcard-front {\n background: linear-gradient(135deg, var(--matrx-print-accent, #667eea) 0%, var(--matrx-print-accent-2, #764ba2) 100%);\n color: white;\n}\n\n.matrx-flashcard-back {\n background: linear-gradient(135deg, var(--matrx-print-success, #2d5016) 0%, var(--matrx-print-success-2, #1e3a0f) 100%);\n color: white;\n transform: rotateY(180deg);\n}\n\n.matrx-flashcard:hover .matrx-flashcard-front,\n.matrx-flashcard:hover .matrx-flashcard-back {\n box-shadow: 0 8px 16px var(--matrx-print-shadow-strong, rgba(0, 0, 0, 0.2));\n}\n\n.matrx-flashcard-content {\n text-align: center;\n font-size: 1.3rem;\n line-height: 1.8;\n max-height: 100%;\n width: 100%;\n overflow: hidden;\n}\n\n.matrx-flashcard-label {\n cursor: pointer;\n user-select: none;\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: 10;\n}\n\n.matrx-flashcard-indicator {\n position: absolute;\n bottom: 12px;\n right: 16px;\n font-size: 0.75rem;\n opacity: 0.8;\n font-weight: 500;\n letter-spacing: 0.5px;\n}\n\n/* Responsive adjustments */\n@media (max-width: 768px) {\n .matrx-flashcard {\n height: 180px;\n }\n\n .matrx-flashcard-content {\n font-size: 1rem;\n }\n\n .matrx-flashcard-front,\n .matrx-flashcard-back {\n padding: 1.25rem;\n }\n}\n\n@media (max-width: 480px) {\n .matrx-flashcard {\n height: 160px;\n }\n\n .matrx-flashcard-content {\n font-size: 0.95rem;\n }\n\n .matrx-flashcard-front,\n .matrx-flashcard-back {\n padding: 1rem;\n }\n}\n\n/* Responsive Design */\n@media (max-width: 768px) {\n .matrx-content-container {\n padding: 1rem;\n }\n\n .matrx-h1 {\n font-size: 2rem;\n }\n\n .matrx-h2 {\n font-size: 1.5rem;\n margin: 2rem 0 1rem 0 !important;\n }\n\n .matrx-h3 {\n font-size: 1.2rem;\n }\n\n .matrx-h4 {\n font-size: 1.05rem;\n }\n\n .matrx-h5 {\n font-size: 0.95rem;\n }\n\n .matrx-h6 {\n font-size: 0.9rem;\n }\n\n .matrx-intro {\n padding: 1rem;\n font-size: 1rem;\n }\n\n .matrx-faq-question {\n font-size: 1.1rem;\n margin: 1.5rem 0 0.75rem 0;\n }\n\n .matrx-paragraph {\n text-align: left;\n }\n\n .matrx-table {\n font-size: 0.875rem;\n }\n\n .matrx-table-header,\n .matrx-table-cell {\n padding: 0.5rem;\n }\n\n .matrx-code-block {\n padding: 0.75rem;\n margin: 1rem 0;\n }\n}\n\n@media (max-width: 480px) {\n .matrx-content-container {\n padding: 0.75rem;\n }\n\n .matrx-h1 {\n font-size: 1.75rem;\n margin-bottom: 1rem;\n }\n\n .matrx-h2 {\n font-size: 1.3rem;\n margin: 1.5rem 0 0.75rem 0 !important;\n }\n\n .matrx-h3 {\n font-size: 1.1rem;\n margin: 1.5rem 0 0.75rem 0;\n }\n\n .matrx-h4 {\n font-size: 1rem;\n margin: 1.25rem 0 0.5rem 0;\n }\n\n .matrx-h5 {\n font-size: 0.9rem;\n margin: 1rem 0 0.5rem 0;\n }\n\n .matrx-h6 {\n font-size: 0.85rem;\n margin: 1rem 0 0.5rem 0;\n }\n\n .matrx-intro {\n padding: 0.75rem;\n margin-bottom: 1.5rem;\n }\n\n .matrx-paragraph {\n margin-bottom: 1.25rem;\n }\n\n .matrx-list-item {\n margin-bottom: 0.75rem;\n }\n\n .matrx-table-header,\n .matrx-table-cell {\n padding: 0.375rem;\n }\n\n .matrx-code-block {\n padding: 0.5rem;\n font-size: 0.8rem;\n }\n\n .matrx-inline-code {\n font-size: 0.8rem;\n }\n}\n/* Print / PDF hard parts — added by @ai-matrx/print 0.3.0. */\n@media print {\n .matrx-content-container {\n max-width: 100%;\n padding: 0;\n }\n\n .matrx-h1,\n .matrx-h2,\n .matrx-h3,\n .matrx-h4,\n .matrx-h5,\n .matrx-h6 {\n page-break-after: avoid;\n break-after: avoid;\n }\n\n .matrx-paragraph,\n .matrx-intro {\n orphans: 3;\n widows: 3;\n }\n\n .matrx-table,\n .matrx-code-block,\n .matrx-blockquote,\n .matrx-list-item,\n .matrx-image,\n .matrx-faq-item,\n .matrx-flashcard {\n page-break-inside: avoid;\n break-inside: avoid;\n }\n\n .matrx-table-header,\n .matrx-code-block,\n .matrx-blockquote,\n .matrx-flashcard {\n -webkit-print-color-adjust: exact;\n print-color-adjust: exact;\n }\n\n .matrx-link {\n text-decoration: underline;\n }\n}\n`;\n\n/** Structural CSS for the article skin (bare tags + `.print-*` classes). */\nexport const MARKDOWN_ARTICLE_CSS = `*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}body{font-family:Georgia,'Times New Roman',serif;font-size:11pt;line-height:1.7;color:var(--matrx-print-fg, #1a1a1a);background:var(--matrx-print-bg, #fff);max-width:780px;margin:0 auto;padding:32px 40px}.print-actions{display:flex;gap:10px;margin-bottom:28px;padding-bottom:20px;border-bottom:2px solid var(--matrx-print-border, #e5e7eb)}.print-btn{display:inline-flex;align-items:center;gap:6px;padding:8px 20px;border-radius:8px;font-size:13px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;cursor:pointer;border:none;font-weight:600;transition:opacity .15s}.print-btn:hover{opacity:.85}.print-btn-primary{background:var(--matrx-print-accent, #2563eb);color:var(--matrx-print-bg, #fff)}.print-btn-secondary{background:var(--matrx-print-surface-sunken, #f3f4f6);color:var(--matrx-print-fg-muted, #374151);border:1px solid var(--matrx-print-border-muted, #d1d5db)}h1,h2,h3,h4,h5,h6{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Helvetica Neue',sans-serif;font-weight:700;line-height:1.3;color:var(--matrx-print-heading, #111827);margin-top:1.6em;margin-bottom:.5em;page-break-after:avoid}h1{font-size:24pt;border-bottom:2px solid var(--matrx-print-border, #e5e7eb);padding-bottom:.3em;margin-top:0}h2{font-size:18pt;border-bottom:1px solid var(--matrx-print-border, #e5e7eb);padding-bottom:.2em}h3{font-size:14pt}p{margin-bottom:.9em;orphans:3;widows:3}a{color:var(--matrx-print-accent, #2563eb);text-decoration:underline;word-break:break-word}ul,ol{margin:.6em 0 .9em 0;padding-left:1.8em}li{margin-bottom:.3em;page-break-inside:avoid}blockquote{margin:1em 0;padding:.7em 1em .7em 1.2em;border-left:4px solid var(--matrx-print-quote-border, #6366f1);background:var(--matrx-print-quote-bg, #f5f3ff);border-radius:0 6px 6px 0;color:var(--matrx-print-fg-muted, #374151);font-style:italic;page-break-inside:avoid}.inline-code{font-family:'JetBrains Mono','Fira Code',monospace;font-size:9.5pt;background:var(--matrx-print-code-bg, #f1f5f9);color:var(--matrx-print-ink, #0f172a);padding:1px 5px;border-radius:4px;border:1px solid var(--matrx-print-line, #e2e8f0)}.code-block-wrapper{position:relative;margin:1em 0 1.2em;border-radius:8px;overflow:hidden;border:1px solid var(--matrx-print-line, #e2e8f0);page-break-inside:avoid}.code-lang{display:block;font-family:-apple-system,sans-serif;font-size:9pt;font-weight:600;color:var(--matrx-print-fg-subtle, #64748b);background:var(--matrx-print-surface, #f8fafc);padding:4px 12px;border-bottom:1px solid var(--matrx-print-line, #e2e8f0);text-transform:uppercase;letter-spacing:.05em}.code-block{margin:0;padding:14px 16px;background:var(--matrx-print-ink, #0f172a);color:var(--matrx-print-line, #e2e8f0);font-family:'JetBrains Mono',monospace;font-size:9pt;line-height:1.6;overflow-x:auto;white-space:pre;tab-size:2}.print-table{width:100%;border-collapse:collapse;margin:1em 0 1.2em;font-size:10pt;page-break-inside:avoid}.print-table th{background:var(--matrx-print-table-head-bg, #1e293b);color:var(--matrx-print-surface, #f8fafc);font-family:-apple-system,sans-serif;font-weight:600;font-size:9.5pt;text-align:left;padding:8px 12px;border:1px solid var(--matrx-print-table-head-border, #334155)}.print-table td{padding:7px 12px;border:1px solid var(--matrx-print-line, #e2e8f0);vertical-align:top}.print-table tr:nth-child(even) td{background:var(--matrx-print-surface, #f8fafc)}hr{border:none;border-top:2px solid var(--matrx-print-border, #e5e7eb);margin:1.5em 0}@media print{body{max-width:100%;padding:0;font-size:10.5pt}.print-actions{display:none!important}.code-block,.print-table th,blockquote{color-adjust:exact;-webkit-print-color-adjust:exact;print-color-adjust:exact}a[href]::after{content:\" (\"attr(href)\")\";font-size:9pt;color:var(--matrx-print-fg-subtle, #64748b)}a[href^=\"#\"]::after,a[href^=\"javascript:\"]::after{content:\"\"}h1,h2,h3{page-break-after:avoid}pre,blockquote,table{page-break-inside:avoid}}`;\n","/**\n * @ai-matrx/print/markdown — the SHIPPED DEFAULT markdown→HTML converter and\n * print stylesheet.\n *\n * Why this entry exists: `markdownToPdfBlob` used to declare its converter and\n * its stylesheet as REQUIRED injected ports (\"bring your own converter +\n * stylesheet\"). That is the exact failure THE ALL-INCLUSIVE LAW names — every\n * host had to build the hard half itself, and both hosts that tried ended up\n * with a different converter, a different stylesheet, and a different set of\n * bugs. The package now ships a complete, working default for both ports; the\n * injection seam survives as the OVERRIDE path, never as a prerequisite.\n *\n * Two skins, one vocabulary:\n * • \"document\" — `markdownToHtml` emits `.matrx-*`-classed HTML (tables,\n * nested lists, images, links, code, FAQ blocks, flashcards) and\n * `MARKDOWN_DOCUMENT_CSS` styles it. This is the PDF/publish/WordPress\n * lane.\n * • \"article\" — `markdownToArticleHtml` emits bare semantic tags and\n * `MARKDOWN_ARTICLE_CSS` gives them the serif AI-response look. This is\n * the print-window lane.\n *\n * Nothing here touches `window`/`document` at import time, and nothing here is\n * heavy — but it IS ~20KB of CSS, so `markdownToPdfBlob` reaches it through a\n * lazy `import(\"./markdown\")` and a consumer that never prints markdown pays\n * nothing.\n *\n * NOTHING FAILS SILENTLY: every case where the converter drops input it cannot\n * represent (a malformed flashcard, an empty document) is reported through the\n * optional `onNotice` sink with a concrete remedy, instead of vanishing.\n */\n\nimport { escapeHtml, openPrintWindow, type PrintOutcome } from \"./core\";\nimport {\n MARKDOWN_ARTICLE_CSS,\n MARKDOWN_ARTICLE_TOKENS_CSS,\n MARKDOWN_DOCUMENT_CSS,\n MARKDOWN_DOCUMENT_TOKENS_CSS,\n} from \"./markdown-css\";\n\nexport {\n MARKDOWN_ARTICLE_CSS,\n MARKDOWN_ARTICLE_TOKENS_CSS,\n MARKDOWN_DOCUMENT_CSS,\n MARKDOWN_DOCUMENT_TOKENS_CSS,\n};\n\n/** Which shipped look to render markdown in. */\nexport type MarkdownSkin = \"document\" | \"article\";\n\n/**\n * A thing the converter could not represent, with the remedy. Announced, never\n * swallowed — a dropped card that says nothing is indistinguishable from a card\n * the author never wrote.\n */\nexport interface MarkdownNotice {\n code: \"flashcard-malformed\" | \"empty-output\" | \"html-stripped\";\n message: string;\n /** What the caller (or the author of the markdown) should do about it. */\n remedy: string;\n}\n\nexport interface MarkdownToHtmlOptions {\n /** Keep `<thinking>` / `<think>` / `<reasoning>` blocks. Default: false. */\n includeThinking?: boolean;\n /** Where dropped/degraded content is announced. */\n onNotice?: (notice: MarkdownNotice) => void;\n}\n\n// ─── thinking-block stripping ────────────────────────────────────────────────\n\n/**\n * Strip `<thinking>` / `<think>` / `<reasoning>` blocks from model output.\n *\n * All three tags are treated as equivalent — a host that handled only\n * `<thinking>` leaked raw reasoning into printed documents the moment a model\n * switched tag names, which is exactly the bug this package exists to stop\n * being re-solved per app.\n */\nexport function removeThinkingContent(content: string): string {\n if (!content) return \"\";\n\n let result = content\n .replace(/\\n*<thinking>[\\s\\S]*?<\\/thinking>\\n*/gi, \"\\n\")\n .replace(/\\n*<think>[\\s\\S]*?<\\/think>\\n*/gi, \"\\n\")\n .replace(/\\n*<reasoning>[\\s\\S]*?<\\/reasoning>\\n*/gi, \"\\n\");\n\n // 3+ newlines collapse to 2 — intentional paragraph breaks survive.\n result = result.replace(/\\n{3,}/g, \"\\n\\n\");\n return result.trim();\n}\n\n/** Does this content carry any reasoning block? */\nexport function hasThinkingContent(content: string): boolean {\n if (!content) return false;\n return /<thinking>|<think>|<reasoning>/i.test(content);\n}\n\n// ─── document skin: markdown → `.matrx-*` HTML ───────────────────────────────\n\nfunction convertMarkdownTables(html: string): string {\n const tableRegex = /^(\\|.*\\|)\\s*\\n(\\|[-\\s:|]*\\|)\\s*\\n((?:\\|.*\\|\\s*\\n?)*)/gm;\n\n return html.replace(\n tableRegex,\n (_match: string, headerRow: string, _separatorRow: string, bodyRows: string) => {\n const headers = headerRow\n .split(\"|\")\n .map((cell) => cell.trim())\n .filter((cell) => cell !== \"\");\n\n const rows = bodyRows\n .trim()\n .split(\"\\n\")\n .filter((row) => row.trim())\n .map((row) =>\n row\n .split(\"|\")\n .map((cell) => cell.trim())\n .filter((cell) => cell !== \"\"),\n );\n\n let tableHtml = '<table class=\"matrx-table\">\\n';\n\n if (headers.length > 0) {\n tableHtml += '<thead class=\"matrx-table-head\">\\n<tr class=\"matrx-table-row\">\\n';\n for (const header of headers) {\n tableHtml += `<th class=\"matrx-table-header\">${header}</th>\\n`;\n }\n tableHtml += \"</tr>\\n</thead>\\n\";\n }\n\n if (rows.length > 0) {\n tableHtml += '<tbody class=\"matrx-table-body\">\\n';\n for (const row of rows) {\n if (row.length === 0) continue;\n tableHtml += '<tr class=\"matrx-table-row\">\\n';\n for (const cell of row) {\n tableHtml += `<td class=\"matrx-table-cell\">${cell}</td>\\n`;\n }\n tableHtml += \"</tr>\\n\";\n }\n tableHtml += \"</tbody>\\n\";\n }\n\n return `${tableHtml}</table>\\n`;\n },\n );\n}\n\ninterface OpenList {\n type: \"ul\" | \"ol\";\n indent: number;\n}\n\n/**\n * markdown → `.matrx-*`-classed HTML wrapped in `.matrx-content-container`.\n *\n * The class vocabulary is deliberate and non-configurable: it is what\n * `MARKDOWN_DOCUMENT_CSS` styles, what the PDF lane renders, and what a\n * WordPress theme targets. Handles (in this order, because the order is the\n * whole trick): flashcard blocks → horizontal rules → images → links →\n * headings → emphasis → nested lists → blockquotes → code → tables → block\n * elements → paragraphs → FAQ promotion → placeholder restoration.\n */\nexport function markdownToHtml(\n markdown: string,\n options: MarkdownToHtmlOptions = {},\n): string {\n const notice = options.onNotice;\n if (!markdown) {\n notice?.({\n code: \"empty-output\",\n message: \"markdownToHtml received an empty string.\",\n remedy: \"Check the content is loaded before converting; nothing was rendered.\",\n });\n return \"\";\n }\n\n const startWrapper = '<div class=\"matrx-content-container\">';\n const endWrapper = \"</div>\";\n\n let html = options.includeThinking ? markdown : removeThinkingContent(markdown);\n\n // Flashcards FIRST, behind placeholders — their HTML must not be re-processed\n // by the markdown passes below.\n const flashcardPlaceholders: Array<{ placeholder: string; html: string }> = [];\n let flashcardIndex = 0;\n let globalCardIndex = 0;\n\n html = html.replace(\n /<flashcards>([\\s\\S]*?)<\\/flashcards>/gi,\n (_match: string, content: string) => {\n const cards = content\n .split(/\\n*---\\n*/g)\n .map((card) => card.trim())\n .filter((card) => card.length > 0);\n\n if (cards.length === 0) return \"\";\n\n let flashcardsHtml = '<div class=\"matrx-flashcards-container\">\\n';\n\n for (const card of cards) {\n const frontMatch = card.match(/Front:\\s*(.+?)(?=\\n|$)/i);\n const backMatch = card.match(/Back:\\s*([\\s\\S]+?)$/i);\n const front = frontMatch?.[1]?.trim();\n const back = backMatch?.[1]?.trim();\n\n if (!front || !back) {\n // The original dropped these without a word. A card that silently\n // disappears from a printed deck is a defect the printer can see and\n // the author cannot.\n notice?.({\n code: \"flashcard-malformed\",\n message: `A flashcard was skipped: it is missing a ${front ? \"Back:\" : \"Front:\"} line.`,\n remedy:\n 'Every card inside <flashcards> needs both \"Front: …\" and \"Back: …\", separated from the next card by ---.',\n });\n continue;\n }\n\n globalCardIndex++;\n const cardId = `card${globalCardIndex}`;\n\n flashcardsHtml += ` <div class=\"matrx-flashcard\">\n <input type=\"checkbox\" class=\"matrx-flashcard-input\" id=\"${cardId}\">\n <label for=\"${cardId}\" class=\"matrx-flashcard-label\"></label>\n <div class=\"matrx-flashcard-inner\">\n <div class=\"matrx-flashcard-front\">\n <div class=\"matrx-flashcard-content\">\n <strong>${front}</strong>\n </div>\n <span class=\"matrx-flashcard-indicator\">CLICK TO FLIP</span>\n </div>\n <div class=\"matrx-flashcard-back\">\n <div class=\"matrx-flashcard-content\">\n ${back}\n </div>\n <span class=\"matrx-flashcard-indicator\">CLICK TO FLIP</span>\n </div>\n </div>\n </div>\n\n`;\n }\n\n flashcardsHtml += \"</div>\\n\";\n\n const placeholder = `ΩFLASHCARDΩ${flashcardIndex}ΩFLASHCARDΩ`;\n flashcardPlaceholders.push({ placeholder, html: flashcardsHtml });\n flashcardIndex++;\n return placeholder;\n },\n );\n\n // Horizontal rules before headings/lists so `---` is never eaten as a list.\n html = html.replace(/^[-]{3,}$/gm, '<hr class=\"matrx-hr\">');\n html = html.replace(/^[*]{3,}$/gm, '<hr class=\"matrx-hr\">');\n\n // Images BEFORE links — `` is a superset of `[a](u)`.\n const imagePlaceholders: Array<{ placeholder: string; html: string }> = [];\n let imageIndex = 0;\n html = html.replace(\n /!\\[([^\\]]*)\\]\\(([^)]+)\\)/g,\n (_match: string, altText: string, url: string) => {\n const placeholder = `ΩIMAGEΩ${imageIndex}ΩIMAGEΩ`;\n imagePlaceholders.push({\n placeholder,\n html: `<img class=\"matrx-image\" src=\"${url}\" alt=\"${altText}\" />`,\n });\n imageIndex++;\n return placeholder;\n },\n );\n\n const linkPlaceholders: Array<{ placeholder: string; html: string }> = [];\n let linkIndex = 0;\n html = html.replace(\n /\\[([^\\]]+)\\]\\(([^)]+)\\)/g,\n (_match: string, linkText: string, url: string) => {\n const placeholder = `ΩLINKΩ${linkIndex}ΩLINKΩ`;\n linkPlaceholders.push({\n placeholder,\n html: `<a class=\"matrx-link\" href=\"${url}\">${linkText}</a>`,\n });\n linkIndex++;\n return placeholder;\n },\n );\n\n html = html\n .replace(/^# (.+)$/gm, '<h1 class=\"matrx-h1\">$1</h1>')\n .replace(/^## (.+)$/gm, '<h2 class=\"matrx-h2\">$1</h2>')\n .replace(/^### (.+)$/gm, '<h3 class=\"matrx-h3\">$1</h3>')\n .replace(/^#### (.+)$/gm, '<h4 class=\"matrx-h4\">$1</h4>')\n .replace(/^##### (.+)$/gm, '<h5 class=\"matrx-h5\">$1</h5>')\n .replace(/^###### (.+)$/gm, '<h6 class=\"matrx-h6\">$1</h6>');\n\n html = html\n .replace(/\\*\\*(.+?)\\*\\*/g, '<strong class=\"matrx-strong\">$1</strong>')\n .replace(/\\*(.+?)\\*/g, '<em class=\"matrx-em\">$1</em>')\n .replace(/__(.+?)__/g, '<strong class=\"matrx-strong\">$1</strong>')\n .replace(/_(.+?)_/g, '<em class=\"matrx-em\">$1</em>');\n\n // Nested lists — indentation-driven open/close bookkeeping.\n const lines = html.split(\"\\n\");\n let processedHtml = \"\";\n const listStack: OpenList[] = [];\n let lastListItem: \"\" | \"ul\" | \"ol\" = \"\";\n\n const top = (): OpenList | undefined => listStack[listStack.length - 1];\n\n const closeDeeperThan = (indent: number): void => {\n while (listStack.length > 0 && (top() as OpenList).indent > indent) {\n if (lastListItem) {\n processedHtml += \"</li>\\n\";\n lastListItem = \"\";\n }\n const closing = listStack.pop();\n if (!closing) break;\n processedHtml += closing.type === \"ol\" ? \"</ol>\\n\" : \"</ul>\\n\";\n if (listStack.length > 0) processedHtml += \"</li>\\n\";\n }\n };\n\n for (const line of lines) {\n const numberedMatch = line.match(/^(\\s*)(\\d+)\\.\\s+(.+)$/);\n if (numberedMatch) {\n const indent = (numberedMatch[1] ?? \"\").length;\n const content = numberedMatch[3] ?? \"\";\n const current = top();\n\n if (current && indent > current.indent) {\n processedHtml += '\\n<ol class=\"matrx-list matrx-numbered-list matrx-nested-list\">\\n';\n listStack.push({ type: \"ol\", indent });\n processedHtml += `<li class=\"matrx-list-item\">${content}`;\n lastListItem = \"ol\";\n } else {\n closeDeeperThan(indent);\n if (lastListItem && top()?.indent === indent) processedHtml += \"</li>\\n\";\n const now = top();\n if (!now || now.indent !== indent || now.type !== \"ol\") {\n processedHtml += '<ol class=\"matrx-list matrx-numbered-list\">\\n';\n listStack.push({ type: \"ol\", indent });\n }\n processedHtml += `<li class=\"matrx-list-item\">${content}`;\n lastListItem = \"ol\";\n }\n continue;\n }\n\n const bulletMatch = line.match(/^(\\s*)[*\\-+]\\s+(.+)$/);\n if (bulletMatch) {\n const indent = (bulletMatch[1] ?? \"\").length;\n const content = bulletMatch[2] ?? \"\";\n const current = top();\n\n if (current && indent > current.indent) {\n processedHtml += '\\n<ul class=\"matrx-list matrx-bullet-list matrx-nested-list\">\\n';\n listStack.push({ type: \"ul\", indent });\n processedHtml += `<li class=\"matrx-list-item\">${content}`;\n lastListItem = \"ul\";\n } else {\n closeDeeperThan(indent);\n if (lastListItem && top()?.indent === indent) processedHtml += \"</li>\\n\";\n const now = top();\n if (!now || now.indent !== indent || now.type !== \"ul\") {\n processedHtml += '<ul class=\"matrx-list matrx-bullet-list\">\\n';\n listStack.push({ type: \"ul\", indent });\n }\n processedHtml += `<li class=\"matrx-list-item\">${content}`;\n lastListItem = \"ul\";\n }\n continue;\n }\n\n if (line.trim() !== \"\") {\n while (listStack.length > 0) {\n if (lastListItem) {\n processedHtml += \"</li>\\n\";\n lastListItem = \"\";\n }\n const closing = listStack.pop();\n if (!closing) break;\n processedHtml += closing.type === \"ol\" ? \"</ol>\\n\" : \"</ul>\\n\";\n if (listStack.length > 0) processedHtml += \"</li>\\n\";\n }\n }\n\n processedHtml += `${line}\\n`;\n }\n\n while (listStack.length > 0) {\n if (lastListItem) {\n processedHtml += \"</li>\\n\";\n lastListItem = \"\";\n }\n const closing = listStack.pop();\n if (!closing) break;\n processedHtml += closing.type === \"ol\" ? \"</ol>\\n\" : \"</ul>\\n\";\n if (listStack.length > 0) processedHtml += \"</li>\\n\";\n }\n\n html = processedHtml;\n\n html = html.replace(/^> (.+)$/gm, '<blockquote class=\"matrx-blockquote\">$1</blockquote>');\n html = html.replace(\n /```([^`]+)```/gs,\n '<pre class=\"matrx-code-block\"><code class=\"matrx-code\">$1</code></pre>',\n );\n html = html.replace(/`([^`]+)`/g, '<code class=\"matrx-inline-code\">$1</code>');\n\n html = convertMarkdownTables(html);\n\n html = html\n .replace(/<table>/g, '<table class=\"matrx-table\">')\n .replace(/<thead>/g, '<thead class=\"matrx-table-head\">')\n .replace(/<tbody>/g, '<tbody class=\"matrx-table-body\">')\n .replace(/<tr>/g, '<tr class=\"matrx-table-row\">')\n .replace(/<th>/g, '<th class=\"matrx-table-header\">')\n .replace(/<td>/g, '<td class=\"matrx-table-cell\">');\n\n html = html.replace(/<img([^>]*)>/g, '<img class=\"matrx-image\"$1>');\n html = html.replace(/<div(?!\\s+class=\"matrx-)([^>]*)>/g, '<div class=\"matrx-div\"$1>');\n html = html.replace(/<span([^>]*)>/g, '<span class=\"matrx-span\"$1>');\n\n html = html.replace(/^([^<\\n].+)$/gm, '<p class=\"matrx-paragraph\">$1</p>');\n html = html.replace(\n /<\\/p>\\s*<p class=\"matrx-paragraph\">/g,\n '</p><p class=\"matrx-paragraph\">',\n );\n\n // Intro paragraph (first <p> after the h1) — before FAQ promotion.\n html = html.replace(\n /(<h1 class=\"matrx-h1\">[\\s\\S]*?<\\/h1>\\s*)<p class=\"matrx-paragraph\">([\\s\\S]*?)<\\/p>/g,\n '$1<p class=\"matrx-intro\">$2</p>',\n );\n\n // FAQ promotion: bold questions and question headings become FAQ items.\n html = html.replace(\n /^<strong class=\"matrx-strong\">([^<]*\\?[^<]*)<\\/strong>$/gm,\n '<h3 class=\"matrx-h3\">$1</h3>',\n );\n html = html.replace(\n /<p class=\"matrx-paragraph\"><strong class=\"matrx-strong\">([^<]*\\?[^<]*)<\\/strong><\\/p>/g,\n '<h3 class=\"matrx-h3\">$1</h3>',\n );\n html = html.replace(\n /<h3 class=\"matrx-h3\">([^<]*\\?[^<]*)<\\/h3>/g,\n '<div class=\"matrx-faq-question\">$1</div>',\n );\n html = html.replace(\n /(<div class=\"matrx-faq-question\">[^<]*<\\/div>)\\s*(<p class=\"matrx-paragraph\">[\\s\\S]*?<\\/p>)/g,\n '<div class=\"matrx-faq-item\">$1<div class=\"matrx-faq-answer\">$2</div></div>',\n );\n html = html.replace(\n /(<div class=\"matrx-faq-question\">[^<]*<\\/div>)(?!\\s*<div class=\"matrx-faq-answer\">)/g,\n '<div class=\"matrx-faq-item\">$1<div class=\"matrx-faq-answer\"></div></div>',\n );\n html = html.replace(\n /(<div class=\"matrx-faq-answer\">)<p class=\"matrx-paragraph\">([\\s\\S]*?)<\\/p>(<\\/div>)/g,\n \"$1$2$3\",\n );\n\n for (const { placeholder, html: imageHtml } of imagePlaceholders) {\n if (html.includes(placeholder)) html = html.replaceAll(placeholder, imageHtml);\n }\n // Images are block-level; a paragraph wrapper around a lone image breaks flow.\n html = html.replace(/<p class=\"matrx-paragraph\">\\s*(<img[^>]*>)\\s*<\\/p>/g, \"$1\");\n\n for (const { placeholder, html: linkHtml } of linkPlaceholders) {\n if (html.includes(placeholder)) html = html.replaceAll(placeholder, linkHtml);\n }\n for (const { placeholder, html: cardHtml } of flashcardPlaceholders) {\n if (html.includes(placeholder)) html = html.replaceAll(placeholder, cardHtml);\n }\n\n return startWrapper + html + endWrapper;\n}\n\n// ─── article skin: markdown → bare semantic HTML ─────────────────────────────\n\nfunction convertArticleTables(html: string): string {\n const tableRegex = /^(\\|.*\\|)\\s*\\n(\\|[-\\s:|]*\\|)\\s*\\n((?:\\|.*\\|\\s*\\n?)*)/gm;\n return html.replace(\n tableRegex,\n (_match: string, headerRow: string, _sep: string, bodyRows: string) => {\n const headers = headerRow\n .split(\"|\")\n .map((c) => c.trim())\n .filter((c) => c !== \"\");\n const rows = bodyRows\n .trim()\n .split(\"\\n\")\n .filter((r) => r.trim())\n .map((r) =>\n r\n .split(\"|\")\n .map((c) => c.trim())\n .filter((c) => c !== \"\"),\n );\n\n let t = '<table class=\"print-table\">\\n';\n if (headers.length) {\n t += `<thead><tr>${headers.map((h) => `<th>${h}</th>`).join(\"\")}</tr></thead>\\n`;\n }\n if (rows.length) {\n t += `<tbody>${rows\n .map((r) =>\n r.length ? `<tr>${r.map((c) => `<td>${c}</td>`).join(\"\")}</tr>` : \"\",\n )\n .join(\"\\n\")}</tbody>\\n`;\n }\n return `${t}</table>\\n`;\n },\n );\n}\n\n/**\n * markdown → bare semantic HTML (`h1`…`h6`, `p`, `ul`, `blockquote`,\n * `.code-block-wrapper`, `.print-table`) for the article skin.\n *\n * Code fences are HTML-escaped here — the document skin leaves them raw\n * because it targets a CMS that re-escapes; a print window does not, so\n * unescaped code would render as markup.\n */\nexport function markdownToArticleHtml(\n markdown: string,\n options: MarkdownToHtmlOptions = {},\n): string {\n if (!markdown) {\n options.onNotice?.({\n code: \"empty-output\",\n message: \"markdownToArticleHtml received an empty string.\",\n remedy: \"Check the content is loaded before printing; nothing was rendered.\",\n });\n return \"\";\n }\n\n let html = options.includeThinking ? markdown : removeThinkingContent(markdown);\n html = convertArticleTables(html);\n\n const codeBlockPlaceholders: Array<{ placeholder: string; html: string }> = [];\n let codeIdx = 0;\n html = html.replace(\n /```(\\w*)\\n?([\\s\\S]*?)```/g,\n (_m: string, lang: string, code: string) => {\n const ph = `ΩCODEBLOCKΩ${codeIdx}ΩCODEBLOCKΩ`;\n const langLabel = lang ? `<span class=\"code-lang\">${lang}</span>` : \"\";\n codeBlockPlaceholders.push({\n placeholder: ph,\n html: `<div class=\"code-block-wrapper\">${langLabel}<pre class=\"code-block\"><code>${escapeHtml(\n code.trim(),\n )}</code></pre></div>`,\n });\n codeIdx++;\n return ph;\n },\n );\n\n const linkPlaceholders: Array<{ placeholder: string; html: string }> = [];\n let linkIdx = 0;\n html = html.replace(\n /\\[([^\\]]+)\\]\\(([^)]+)\\)/g,\n (_m: string, text: string, url: string) => {\n const ph = `ΩLINKΩ${linkIdx}ΩLINKΩ`;\n linkPlaceholders.push({ placeholder: ph, html: `<a href=\"${url}\">${text}</a>` });\n linkIdx++;\n return ph;\n },\n );\n\n html = html\n .replace(/^#{6}\\s+(.+)$/gm, \"<h6>$1</h6>\")\n .replace(/^#{5}\\s+(.+)$/gm, \"<h5>$1</h5>\")\n .replace(/^#{4}\\s+(.+)$/gm, \"<h4>$1</h4>\")\n .replace(/^#{3}\\s+(.+)$/gm, \"<h3>$1</h3>\")\n .replace(/^#{2}\\s+(.+)$/gm, \"<h2>$1</h2>\")\n .replace(/^#{1}\\s+(.+)$/gm, \"<h1>$1</h1>\");\n\n html = html.replace(/^> (.+)$/gm, \"<blockquote>$1</blockquote>\");\n html = html.replace(/^[-*_]{3,}$/gm, \"<hr>\");\n html = html\n .replace(/\\*\\*\\*(.+?)\\*\\*\\*/g, \"<strong><em>$1</em></strong>\")\n .replace(/\\*\\*(.+?)\\*\\*/g, \"<strong>$1</strong>\")\n .replace(/\\*(.+?)\\*/g, \"<em>$1</em>\")\n .replace(/___(.+?)___/g, \"<strong><em>$1</em></strong>\")\n .replace(/__(.+?)__/g, \"<strong>$1</strong>\")\n .replace(/_([^_\\s][^_]*)_/g, \"<em>$1</em>\");\n html = html.replace(/`([^`]+)`/g, '<code class=\"inline-code\">$1</code>');\n\n const lines = html.split(\"\\n\");\n const result: string[] = [];\n let inUl = false;\n let inOl = false;\n for (const line of lines) {\n const ulMatch = line.match(/^(\\s*)[-*+]\\s+(.+)$/);\n const olMatch = line.match(/^(\\s*)\\d+\\.\\s+(.+)$/);\n if (ulMatch) {\n if (inOl) {\n result.push(\"</ol>\");\n inOl = false;\n }\n if (!inUl) {\n result.push(\"<ul>\");\n inUl = true;\n }\n result.push(`<li>${ulMatch[2] ?? \"\"}</li>`);\n } else if (olMatch) {\n if (inUl) {\n result.push(\"</ul>\");\n inUl = false;\n }\n if (!inOl) {\n result.push(\"<ol>\");\n inOl = true;\n }\n result.push(`<li>${olMatch[2] ?? \"\"}</li>`);\n } else {\n if (inUl) {\n result.push(\"</ul>\");\n inUl = false;\n }\n if (inOl) {\n result.push(\"</ol>\");\n inOl = false;\n }\n result.push(line);\n }\n }\n if (inUl) result.push(\"</ul>\");\n if (inOl) result.push(\"</ol>\");\n html = result.join(\"\\n\");\n\n html = html\n .split(\"\\n\")\n .map((line) => {\n const t = line.trim();\n if (!t) return \"\";\n if (/^<(h[1-6]|ul|ol|li|blockquote|pre|hr|table|thead|tbody|tr|th|td|div|ΩCODE)/.test(t)) {\n return line;\n }\n return `<p>${t}</p>`;\n })\n .join(\"\\n\");\n\n for (const { placeholder, html: ph } of codeBlockPlaceholders) {\n html = html.replace(`<p>${placeholder}</p>`, ph);\n html = html.replace(placeholder, ph);\n }\n for (const { placeholder, html: ph } of linkPlaceholders) {\n html = html.replaceAll(placeholder, ph);\n }\n\n return html;\n}\n\n// ─── stylesheet + document composition ───────────────────────────────────────\n\n/**\n * The Print / Close action bar. The article sheet styles it already; the\n * document sheet is a content stylesheet with no chrome, so this ships beside\n * it — otherwise every host printing document-skin markdown gets two unstyled\n * browser buttons at the top of the page.\n */\nexport const PRINT_ACTIONS_CSS = `\n.print-actions { display: flex; gap: 10px; margin: 0 auto 28px; max-width: 800px; padding: 0 2rem 20px; border-bottom: 2px solid var(--matrx-print-border, #e1e1e1); }\n.print-btn { display: inline-flex; align-items: center; gap: 6px; padding: 8px 20px; border-radius: 8px; font-size: 13px; font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; cursor: pointer; border: none; font-weight: 600; }\n.print-btn-primary { background: var(--matrx-print-accent, #667eea); color: var(--matrx-print-bg, #ffffff); }\n.print-btn-secondary { background: var(--matrx-print-surface, #f8f9fa); color: var(--matrx-print-fg-strong, #374151); border: 1px solid var(--matrx-print-border-muted, #d1d5db); }\n@media print { .print-actions { display: none !important; } }\n`;\n\nexport interface MarkdownStylesheetOptions {\n /** Which shipped look. Default: `\"document\"`. */\n skin?: MarkdownSkin;\n /**\n * Host brand overrides — a CSS string appended AFTER the package's default\n * token values, so `:root { --matrx-print-accent: #0f766e; }` wins. Token\n * VALUES are host-owned; the token CONTRACT and the structural CSS are not.\n */\n tokens?: string;\n /** Extra rules appended after everything else. */\n extraCss?: string;\n}\n\n/**\n * The complete default stylesheet for a skin: default token values, then the\n * structural sheet, then the host's overrides. Never returns an empty string.\n */\nexport function getMarkdownStylesheet(\n options: MarkdownStylesheetOptions = {},\n): string {\n const skin: MarkdownSkin = options.skin ?? \"document\";\n const base =\n skin === \"article\"\n ? `${MARKDOWN_ARTICLE_TOKENS_CSS}\\n${MARKDOWN_ARTICLE_CSS}`\n : `${MARKDOWN_DOCUMENT_TOKENS_CSS}\\n${MARKDOWN_DOCUMENT_CSS}`;\n return [base, options.tokens ?? \"\", options.extraCss ?? \"\"]\n .filter((part) => part.trim().length > 0)\n .join(\"\\n\");\n}\n\n/** Convert markdown with the converter that matches a skin. */\nexport function markdownToHtmlForSkin(\n markdown: string,\n skin: MarkdownSkin,\n options: MarkdownToHtmlOptions = {},\n): string {\n return skin === \"article\"\n ? markdownToArticleHtml(markdown, options)\n : markdownToHtml(markdown, options);\n}\n\nexport interface RenderMarkdownDocumentOptions extends MarkdownStylesheetOptions {\n title?: string;\n /** Override the shipped converter. The seam survives; it is no longer required. */\n convertToHtml?: (markdown: string) => string;\n /** Replace the whole stylesheet (not merely the tokens). */\n css?: string;\n onNotice?: (notice: MarkdownNotice) => void;\n /** Include the Print / Close action bar (hidden by `@media print`). */\n withPrintActions?: boolean;\n}\n\n/**\n * markdown → a complete, standalone, styled HTML document string.\n *\n * This is the one composition every lane uses — the print window, the PDF\n * capture host, and \"copy a full HTML page\". A host that builds its own\n * `<!DOCTYPE html>` wrapper around package CSS is re-implementing this.\n */\nexport function renderMarkdownDocument(\n markdown: string,\n options: RenderMarkdownDocumentOptions = {},\n): string {\n const skin: MarkdownSkin = options.skin ?? \"document\";\n const title = options.title ?? \"AI Response\";\n const convert =\n options.convertToHtml ??\n ((md: string) =>\n markdownToHtmlForSkin(\n md,\n skin,\n options.onNotice ? { onNotice: options.onNotice } : {},\n ));\n const bodyHtml = convert(markdown);\n const css =\n options.css ??\n getMarkdownStylesheet({\n skin,\n ...(options.tokens === undefined ? {} : { tokens: options.tokens }),\n ...(options.extraCss === undefined ? {} : { extraCss: options.extraCss }),\n });\n\n // The article sheet already styles the action bar; the document sheet is a\n // pure content stylesheet, so it gets the shared chrome appended.\n const chrome =\n options.withPrintActions && skin !== \"article\" ? PRINT_ACTIONS_CSS : \"\";\n\n const actions = options.withPrintActions\n ? '<div class=\"print-actions\"><button class=\"print-btn print-btn-primary\" onclick=\"window.print()\">Print / Save as PDF</button><button class=\"print-btn print-btn-secondary\" onclick=\"window.close()\">Close</button></div>'\n : \"\";\n\n return `<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\"><title>${escapeHtml(\n title,\n )}</title>\n<style>${css}${chrome}</style></head>\n<body>${actions}<div class=\"content\">${bodyHtml}</div></body></html>`;\n}\n\nexport interface PrintMarkdownOptions extends RenderMarkdownDocumentOptions {\n /** Window features for the print window. */\n windowFeatures?: string;\n}\n\n/**\n * Open a print window for a markdown string, in one call.\n *\n * Returns the `PrintOutcome` so the host can tell the user when the popup was\n * blocked and the `.html` download fallback fired instead — a silent fallback\n * is indistinguishable from \"print did nothing\".\n */\nexport function printMarkdown(\n markdown: string,\n options: PrintMarkdownOptions = {},\n): PrintOutcome {\n const title = options.title ?? \"AI Response\";\n const doc = renderMarkdownDocument(markdown, {\n ...options,\n title,\n withPrintActions: options.withPrintActions ?? true,\n });\n return options.windowFeatures === undefined\n ? openPrintWindow(doc, title)\n : openPrintWindow(doc, title, options.windowFeatures);\n}\n"],"mappings":";AA4MA,SAAS,WAAW,KAAqB;AACvC,SAAO,IACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,OAAO;AAC1B;AAsDO,SAAS,gBACd,cACA,WAAW,SACX,iBAAiB,uCACH;AACd,QAAM,MAAM,OAAO,KAAK,IAAI,UAAU,cAAc;AACpD,MAAI,CAAC,KAAK;AAER,UAAM,OAAO,IAAI,KAAK,CAAC,YAAY,GAAG,EAAE,MAAM,0BAA0B,CAAC;AACzE,UAAM,MAAM,IAAI,gBAAgB,IAAI;AACpC,UAAM,IAAI,SAAS,cAAc,GAAG;AACpC,MAAE,OAAO;AACT,MAAE,WAAW,GAAG,SAAS,QAAQ,QAAQ,GAAG,EAAE,YAAY,CAAC;AAC3D,aAAS,KAAK,YAAY,CAAC;AAC3B,MAAE,MAAM;AACR,aAAS,KAAK,YAAY,CAAC;AAG3B,eAAW,MAAM,IAAI,gBAAgB,GAAG,GAAG,CAAC;AAC5C,WAAO;AAAA,EACT;AACA,MAAI,SAAS,KAAK;AAClB,MAAI,SAAS,MAAM,YAAY;AAC/B,MAAI,SAAS,MAAM;AACnB,SAAO;AACT;;;ACrQO,IAAM,+BAA+B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6BrC,IAAM,8BAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBpC,IAAM,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":[]}
|
package/dist/pdf.cjs
CHANGED
|
@@ -298,12 +298,79 @@ function sanitizeForCapture(html) {
|
|
|
298
298
|
});
|
|
299
299
|
return fragment;
|
|
300
300
|
}
|
|
301
|
-
async function
|
|
301
|
+
async function settleImages(host, timeoutMs, notify) {
|
|
302
|
+
const images = Array.from(host.querySelectorAll("img"));
|
|
303
|
+
if (images.length === 0) return;
|
|
304
|
+
const timedOut = [];
|
|
305
|
+
await Promise.all(
|
|
306
|
+
images.map(
|
|
307
|
+
(img) => new Promise((resolve) => {
|
|
308
|
+
if (img.complete) {
|
|
309
|
+
if (img.naturalWidth === 0) {
|
|
310
|
+
notify({
|
|
311
|
+
code: "image-unreachable",
|
|
312
|
+
message: `An image could not be loaded and will be blank in the PDF: ${img.src}`,
|
|
313
|
+
remedy: "Use a durable, publicly reachable https URL \u2014 a print render is an unauthenticated document and cannot use session-signed or expiring links."
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
resolve();
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const timer = setTimeout(() => {
|
|
320
|
+
timedOut.push(img.src);
|
|
321
|
+
resolve();
|
|
322
|
+
}, timeoutMs);
|
|
323
|
+
const done = (ok) => {
|
|
324
|
+
clearTimeout(timer);
|
|
325
|
+
if (!ok) {
|
|
326
|
+
notify({
|
|
327
|
+
code: "image-unreachable",
|
|
328
|
+
message: `An image could not be loaded and will be blank in the PDF: ${img.src}`,
|
|
329
|
+
remedy: "Use a durable, publicly reachable https URL \u2014 a print render is an unauthenticated document and cannot use session-signed or expiring links."
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
resolve();
|
|
333
|
+
};
|
|
334
|
+
img.addEventListener("load", () => done(true), { once: true });
|
|
335
|
+
img.addEventListener("error", () => done(false), { once: true });
|
|
336
|
+
})
|
|
337
|
+
)
|
|
338
|
+
);
|
|
339
|
+
if (timedOut.length > 0) {
|
|
340
|
+
notify({
|
|
341
|
+
code: "image-timeout",
|
|
342
|
+
message: `${timedOut.length} image(s) did not finish loading in ${timeoutMs}ms and may be blank in the PDF: ${timedOut.join(", ")}`,
|
|
343
|
+
remedy: "Raise `imageTimeoutMs`, or serve the images from a faster/CDN origin before exporting."
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async function markdownToPdfBlob(markdown, deps = {}) {
|
|
302
348
|
if (!markdown.trim()) {
|
|
303
349
|
throw new Error("Nothing to convert \u2014 the content is empty.");
|
|
304
350
|
}
|
|
305
|
-
const
|
|
306
|
-
|
|
351
|
+
const notify = (notice) => {
|
|
352
|
+
if (deps.onNotice) {
|
|
353
|
+
deps.onNotice(notice);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
console.warn(`[@ai-matrx/print] ${notice.message} \u2192 ${notice.remedy}`);
|
|
357
|
+
};
|
|
358
|
+
const needsDefaults = !deps.convertToHtml || !deps.loadCss;
|
|
359
|
+
const markdownModule = needsDefaults ? await import("@ai-matrx/print/markdown") : void 0;
|
|
360
|
+
const bodyHtml = deps.convertToHtml ? deps.convertToHtml(markdown) : markdownModule.markdownToHtmlForSkin(
|
|
361
|
+
markdown,
|
|
362
|
+
deps.skin ?? "document",
|
|
363
|
+
{ onNotice: notify }
|
|
364
|
+
);
|
|
365
|
+
const css = deps.loadCss ? await deps.loadCss() : markdownModule.getMarkdownStylesheet({
|
|
366
|
+
skin: deps.skin ?? "document",
|
|
367
|
+
...deps.tokens === void 0 ? {} : { tokens: deps.tokens }
|
|
368
|
+
});
|
|
369
|
+
if (!bodyHtml.trim()) {
|
|
370
|
+
throw new Error(
|
|
371
|
+
"The markdown converted to an empty document \u2014 nothing would be printed. Check the content is more than whitespace or reasoning blocks."
|
|
372
|
+
);
|
|
373
|
+
}
|
|
307
374
|
const host = document.createElement("div");
|
|
308
375
|
host.style.cssText = "position:fixed;left:-10000px;top:0;width:800px;background:#ffffff;color:#111827;pointer-events:none;";
|
|
309
376
|
host.setAttribute("aria-hidden", "true");
|
|
@@ -311,13 +378,19 @@ async function markdownToPdfBlob(markdown, deps) {
|
|
|
311
378
|
style.textContent = css;
|
|
312
379
|
host.appendChild(style);
|
|
313
380
|
const contentEl = document.createElement("div");
|
|
314
|
-
contentEl.className = "matrx-content-container";
|
|
381
|
+
contentEl.className = (deps.skin ?? "document") === "article" ? "content" : "matrx-content-container";
|
|
315
382
|
contentEl.style.cssText = "padding:32px;background:#ffffff;";
|
|
316
383
|
contentEl.appendChild(sanitizeForCapture(bodyHtml));
|
|
317
384
|
host.appendChild(contentEl);
|
|
318
385
|
document.body.appendChild(host);
|
|
319
386
|
try {
|
|
387
|
+
await settleImages(host, deps.imageTimeoutMs ?? 8e3, notify);
|
|
320
388
|
return await captureToPDFBlob(host, { background: "#fff", theme: "light" });
|
|
389
|
+
} catch (error) {
|
|
390
|
+
throw new Error(
|
|
391
|
+
`The PDF could not be rendered from this content: ${error instanceof Error ? error.message : String(error)}. Try again; if it repeats, the content likely contains an element the renderer cannot rasterize (a cross-origin image without CORS headers, or an embedded iframe/canvas).`,
|
|
392
|
+
error instanceof Error ? { cause: error } : void 0
|
|
393
|
+
);
|
|
321
394
|
} finally {
|
|
322
395
|
host.remove();
|
|
323
396
|
}
|
package/dist/pdf.cjs.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\";\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 * The two application-owned pieces markdownToPdfBlob needs. The original\n * reached for them with dynamic imports of app feature modules; a package\n * takes them from its caller instead.\n */\nexport interface MarkdownPdfDeps {\n /** markdown → HTML body string (the same converter the publish/copy-HTML flows use) */\n convertToHtml: (markdown: string) => string;\n /** the class-scoped stylesheet text the rendered HTML expects */\n loadCss: () => Promise<string> | 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\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 const bodyHtml = deps.convertToHtml(markdown);\n const css = await deps.loadCss();\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 WP stylesheet is class-scoped (.matrx-*) and expects this container.\n contentEl.className = \"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 // 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 } finally {\n host.remove();\n }\n}\n"],"mappings":"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","names":[]}
|
|
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":[]}
|
package/dist/pdf.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BlockPrinter } from './core.cjs';
|
|
2
|
+
import { MarkdownSkin } from './markdown.cjs';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* DOM-Capture Print Utilities — Tier 2 Full-Message Print
|
|
@@ -130,15 +131,40 @@ declare function captureBlockElement(element: HTMLElement, filename: string, ori
|
|
|
130
131
|
* not fork another markdown-to-pdf path.
|
|
131
132
|
*/
|
|
132
133
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
134
|
+
* OVERRIDES for markdownToPdfBlob — every field is optional.
|
|
135
|
+
*
|
|
136
|
+
* Both ports ship a working default in `@ai-matrx/print/markdown`
|
|
137
|
+
* (`markdownToHtml` + `getMarkdownStylesheet`), so `markdownToPdfBlob(md)`
|
|
138
|
+
* alone produces a styled PDF in a fresh app. Supplying a field replaces that
|
|
139
|
+
* default; supplying neither is the normal case. Until 0.3.0 both fields were
|
|
140
|
+
* REQUIRED, which forced every host to build the hard half itself.
|
|
136
141
|
*/
|
|
137
142
|
interface MarkdownPdfDeps {
|
|
138
|
-
/** markdown → HTML body string
|
|
139
|
-
convertToHtml
|
|
140
|
-
/** the
|
|
141
|
-
loadCss
|
|
143
|
+
/** markdown → HTML body string. Default: the package's `markdownToHtml`. */
|
|
144
|
+
convertToHtml?: (markdown: string) => string;
|
|
145
|
+
/** the stylesheet the rendered HTML expects. Default: the "document" skin. */
|
|
146
|
+
loadCss?: () => Promise<string> | string;
|
|
147
|
+
/** Which shipped look to use when `convertToHtml`/`loadCss` are not given. */
|
|
148
|
+
skin?: MarkdownSkin;
|
|
149
|
+
/** Host brand token overrides appended after the package's default values. */
|
|
150
|
+
tokens?: string;
|
|
151
|
+
/**
|
|
152
|
+
* Where degraded conversion and unreachable images are announced. Nothing
|
|
153
|
+
* fails silently: without a sink these still surface as `console.warn`.
|
|
154
|
+
*/
|
|
155
|
+
onNotice?: (notice: MarkdownPdfNotice) => void;
|
|
156
|
+
/**
|
|
157
|
+
* How long to wait for images to decode before capturing, in ms.
|
|
158
|
+
* Default 8000. html2canvas paints whatever has loaded, so an un-awaited
|
|
159
|
+
* image is a blank rectangle in the PDF with no error anywhere.
|
|
160
|
+
*/
|
|
161
|
+
imageTimeoutMs?: number;
|
|
162
|
+
}
|
|
163
|
+
/** A degradation the PDF lane detected, with the remedy. Never swallowed. */
|
|
164
|
+
interface MarkdownPdfNotice {
|
|
165
|
+
code: "flashcard-malformed" | "empty-output" | "html-stripped" | "image-unreachable" | "image-timeout";
|
|
166
|
+
message: string;
|
|
167
|
+
remedy: string;
|
|
142
168
|
}
|
|
143
169
|
/**
|
|
144
170
|
* Markdown can carry raw HTML; we are about to attach it to the live
|
|
@@ -146,6 +172,6 @@ interface MarkdownPdfDeps {
|
|
|
146
172
|
* inline handlers (`onerror`) and javascript: URLs would fire on attach.
|
|
147
173
|
*/
|
|
148
174
|
declare function sanitizeForCapture(html: string): DocumentFragment;
|
|
149
|
-
declare function markdownToPdfBlob(markdown: string, deps
|
|
175
|
+
declare function markdownToPdfBlob(markdown: string, deps?: MarkdownPdfDeps): Promise<Blob>;
|
|
150
176
|
|
|
151
|
-
export { type DomCaptureOptions, type MarkdownPdfDeps, captureBlockElement, captureElementsToPDF, captureElementsToPDFBlob, captureToClipboardImage, captureToPDF, captureToPDFBlob, createDomCapturePrinter, markdownToPdfBlob, sanitizeForCapture };
|
|
177
|
+
export { type DomCaptureOptions, type MarkdownPdfDeps, type MarkdownPdfNotice, captureBlockElement, captureElementsToPDF, captureElementsToPDFBlob, captureToClipboardImage, captureToPDF, captureToPDFBlob, createDomCapturePrinter, markdownToPdfBlob, sanitizeForCapture };
|
package/dist/pdf.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BlockPrinter } from './core.js';
|
|
2
|
+
import { MarkdownSkin } from './markdown.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* DOM-Capture Print Utilities — Tier 2 Full-Message Print
|
|
@@ -130,15 +131,40 @@ declare function captureBlockElement(element: HTMLElement, filename: string, ori
|
|
|
130
131
|
* not fork another markdown-to-pdf path.
|
|
131
132
|
*/
|
|
132
133
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
134
|
+
* OVERRIDES for markdownToPdfBlob — every field is optional.
|
|
135
|
+
*
|
|
136
|
+
* Both ports ship a working default in `@ai-matrx/print/markdown`
|
|
137
|
+
* (`markdownToHtml` + `getMarkdownStylesheet`), so `markdownToPdfBlob(md)`
|
|
138
|
+
* alone produces a styled PDF in a fresh app. Supplying a field replaces that
|
|
139
|
+
* default; supplying neither is the normal case. Until 0.3.0 both fields were
|
|
140
|
+
* REQUIRED, which forced every host to build the hard half itself.
|
|
136
141
|
*/
|
|
137
142
|
interface MarkdownPdfDeps {
|
|
138
|
-
/** markdown → HTML body string
|
|
139
|
-
convertToHtml
|
|
140
|
-
/** the
|
|
141
|
-
loadCss
|
|
143
|
+
/** markdown → HTML body string. Default: the package's `markdownToHtml`. */
|
|
144
|
+
convertToHtml?: (markdown: string) => string;
|
|
145
|
+
/** the stylesheet the rendered HTML expects. Default: the "document" skin. */
|
|
146
|
+
loadCss?: () => Promise<string> | string;
|
|
147
|
+
/** Which shipped look to use when `convertToHtml`/`loadCss` are not given. */
|
|
148
|
+
skin?: MarkdownSkin;
|
|
149
|
+
/** Host brand token overrides appended after the package's default values. */
|
|
150
|
+
tokens?: string;
|
|
151
|
+
/**
|
|
152
|
+
* Where degraded conversion and unreachable images are announced. Nothing
|
|
153
|
+
* fails silently: without a sink these still surface as `console.warn`.
|
|
154
|
+
*/
|
|
155
|
+
onNotice?: (notice: MarkdownPdfNotice) => void;
|
|
156
|
+
/**
|
|
157
|
+
* How long to wait for images to decode before capturing, in ms.
|
|
158
|
+
* Default 8000. html2canvas paints whatever has loaded, so an un-awaited
|
|
159
|
+
* image is a blank rectangle in the PDF with no error anywhere.
|
|
160
|
+
*/
|
|
161
|
+
imageTimeoutMs?: number;
|
|
162
|
+
}
|
|
163
|
+
/** A degradation the PDF lane detected, with the remedy. Never swallowed. */
|
|
164
|
+
interface MarkdownPdfNotice {
|
|
165
|
+
code: "flashcard-malformed" | "empty-output" | "html-stripped" | "image-unreachable" | "image-timeout";
|
|
166
|
+
message: string;
|
|
167
|
+
remedy: string;
|
|
142
168
|
}
|
|
143
169
|
/**
|
|
144
170
|
* Markdown can carry raw HTML; we are about to attach it to the live
|
|
@@ -146,6 +172,6 @@ interface MarkdownPdfDeps {
|
|
|
146
172
|
* inline handlers (`onerror`) and javascript: URLs would fire on attach.
|
|
147
173
|
*/
|
|
148
174
|
declare function sanitizeForCapture(html: string): DocumentFragment;
|
|
149
|
-
declare function markdownToPdfBlob(markdown: string, deps
|
|
175
|
+
declare function markdownToPdfBlob(markdown: string, deps?: MarkdownPdfDeps): Promise<Blob>;
|
|
150
176
|
|
|
151
|
-
export { type DomCaptureOptions, type MarkdownPdfDeps, captureBlockElement, captureElementsToPDF, captureElementsToPDFBlob, captureToClipboardImage, captureToPDF, captureToPDFBlob, createDomCapturePrinter, markdownToPdfBlob, sanitizeForCapture };
|
|
177
|
+
export { type DomCaptureOptions, type MarkdownPdfDeps, type MarkdownPdfNotice, captureBlockElement, captureElementsToPDF, captureElementsToPDFBlob, captureToClipboardImage, captureToPDF, captureToPDFBlob, createDomCapturePrinter, markdownToPdfBlob, sanitizeForCapture };
|