@ai-matrx/print
v0.8.13
Published
The AI Matrx print system: the BlockPrinter contract and print-window composition, the flashcards deck printer (duplex-mirrored cut sheets, Avery 5388, study sheets), QR generation with spec quiet zones and e-commerce payload builders, label-sheet printin
Maintainers
Readme
@ai-matrx/print
The AI Matrx print system — everything a browser app needs to turn structured content into paper: a tiny print contract, print-window composition, QR generation with e-commerce payload builders, label-sheet printing on Avery and roll stock with custom label formats, a full flashcard deck printer, a shipped markdown converter with two default print stylesheets, and DOM-capture/markdown → PDF.
Every entry is import-inert and safe to import during SSR — browser APIs are
touched only when you call something. /react is the only React entry (a
client component entry); everything else is framework-free.
Install
npm install @ai-matrx/printReact ≥18 is an optional peer, needed only for @ai-matrx/print/react.
Entries
@ai-matrx/print — types only
BlockPrinter, PrintVariant, PrintSetting, PrintSettings,
LabelTemplate, LabelFormat, QrEcLevel, Flashcard, … — the shared
vocabulary, no runtime.
@ai-matrx/print/core — the print contract & window composition
import { buildPrintDocument, openPrintWindow, printHtmlContent, escapeHtml, buildFitTextScript } from "@ai-matrx/print/core";
printHtmlContent("<h1>Report</h1><table>…</table>", "Q3 Report");BlockPrinter is the one interface a host needs: label, variants,
optional settings (boolean / range / number / select), and
print(data, variantId, settings). openPrintWindow opens a styled window
with Print / Close actions and falls back to an .html download when the
popup is blocked. buildFitTextScript(selectors) returns the in-window
auto-shrink script (0.5pt steps, 6pt floor) used by the flashcards engine.
@ai-matrx/print/qr — QR generation (not decoding)
import { generateQrDataUrl, generateQrSvg, minQrVersion, buildShortResolverUrl, buildGs1DigitalLink } from "@ai-matrx/print/qr";
const url = buildShortResolverUrl({ origin: "https://yourbrand.com", code: "a1B2c3" }); // https://yourbrand.com/l/a1B2c3
const png = await generateQrDataUrl(url); // ECC "M", ≥4-module quiet zone — both enforced defaults
const gs1 = buildGs1DigitalLink({ origin: "https://id.yourbrand.com", gtin: "4006381333931", lot: "L42", serial: "S1001", expiry: "271231" });Error correction defaults to M (never L — scuffed labels need margin) and
the quiet zone is clamped to the ISO minimum of 4 modules. The code value in
buildShortResolverUrl is an opaque input — mint identities server-side.
minQrVersion(byteLen, ecc) / qrModuleCount(version) /
qrByteCapacity(version, ecc) expose the symbol math (byte-mode capacities
verified against the reference encoder).
@ai-matrx/print/labels — label sheets, roll stock, custom formats
import { qrLabelsPrinter, printQrLabelSheet, downloadLabelsPdf, printCalibrationSheet, applyLabelFormat, LABEL_FORMAT_PRESETS, assertScannable, getLabelTemplate } from "@ai-matrx/print/labels";
// One data shape, three lanes: print window, PDF download, calibration page.
const data = applyLabelFormat(
LABEL_FORMAT_PRESETS.find((f) => f.id === "garment")!,
[{ qrValue: "https://yourbrand.com/l/a1B2c3", size: "XL", sku: "TS-001", color: "Black", price: "$29" }],
);
await printQrLabelSheet({ ...data, templateId: "avery-5160" });Built-in templates: Avery 5160/8160, 5163/8163, 5164/8164, 22806 (2″ square),
22807 (2″ round), plus roll stock — Brother DK-1202 / DK-11201, DYMO 30252,
and generic 4×6″ thermal (kind: "roll"). Custom stock is just data via
customLabelTemplate(geometry); a dev-time assertion proves every template
tiles its sheet. Settings include startAtLabel (reuse a partially-used
sheet), copiesPerCode, label ranges, and EC level (default M).
LabelFormat describes a label as ordered elements — qr, text with a
title / detail / badge role, image (durable http(s) URLs only) — so
clients can define their own layouts as data. Presets: qr-only,
qr-caption, garment, asset-spec.
assertScannable(template, payloadByteLen, ecc) warns before anyone prints a
payload too dense for the stock (module size below the ~0.4 mm thermal floor);
the print lane surfaces it as an on-screen banner automatically.
Print fidelity: QRs are inlined data URIs rasterized at 300 dpi (a print
window is a fresh, unauthenticated document — nothing is fetched),
print-color-adjust: exact, inch-exact @page geometry, and a
print-on-plain-paper calibration sheet per template. Print at 100% scale with
no margins.
@ai-matrx/print/flashcards — the deck printer
import { flashcardsPrinter } from "@ai-matrx/print/flashcards";
flashcardsPrinter.print({ title: "Biology 101", cards }, "portrait-6up-duplex", { showCutLines: true });Ten variants: portrait 6-up and landscape 4-up (each duplex-mirrored — backs column-swapped so long-edge duplex aligns perfectly — or stacked all-fronts-then-all-backs), Avery 5388 index cards, cut-out cards, both-sides, study-sheet table, questions-only self-test, answers-only. Settings: card ranges, cut tick marks / full cut lines, side labels, card numbers, and show-through countermeasures (gray back text, mirrored back text). Face images print from durable URLs only.
@ai-matrx/print/markdown — the shipped converter & print stylesheets
import { markdownToHtml, getMarkdownStylesheet, renderMarkdownDocument, printMarkdown, removeThinkingContent } from "@ai-matrx/print/markdown";
printMarkdown(markdown, { title: "Report" }); // markdown → print window, one call
const html = markdownToHtml(markdown); // → .matrx-* classed body HTML
const css = getMarkdownStylesheet({ skin: "article" }); // → the matching stylesheet
const page = renderMarkdownDocument(markdown, { title: "Report" }); // → a complete <!DOCTYPE html> pageTwo skins off one vocabulary. "document" (default) emits .matrx-*
classed HTML — headings, nested lists, markdown tables, images, links, code,
FAQ promotion, <flashcards> blocks — inside .matrx-content-container, and
its stylesheet is what pastes into a WordPress theme. "article" emits
bare semantic tags with the serif AI-response look for print windows (code
fences are HTML-escaped there, because a print window does not re-escape).
removeThinkingContent strips <thinking> / <think> / <reasoning> — all
three, which is why a model switching tag names cannot leak reasoning into a
printed page.
Colours are var(--matrx-print-<role>, <default>) throughout (C26): the inline
fallbacks make a fresh app render correctly with zero setup, and a host brands
it by passing tokens: ":root{--matrx-print-accent:#0f766e}" — appended last,
so the host wins. Both sheets carry the print hard parts (@media print,
page-break avoidance, orphans/widows, print-color-adjust).
@ai-matrx/print/directives — page breaks, one grammar everywhere
import { isPageBreakLine, splitAtPageBreaks, PAGE_BREAK_MARKDOWN } from "@ai-matrx/print/directives";Put <!-- pagebreak --> on its own line to start a new printed page
(\pagebreak, \newpage, <!-- newpage --> and
<div style="page-break-after: always"></div> are accepted too). Both
converters render it as <div class="matrx-page-break"> — a dashed "Page
break" rule on screen, a real page break on paper — and markdownToPdfBlob
starts a new PDF page there. The entry has no dependencies, so a chat or editor
renderer can recognise the same syntax without loading the stylesheets.
@ai-matrx/print/pdf — DOM capture & markdown → PDF
import { captureToPDF, captureElementsToPDF, markdownToPdfBlob } from "@ai-matrx/print/pdf";
await captureToPDF(document.getElementById("report")!, { filename: "report", orientation: "landscape" });
const blob = await markdownToPdfBlob(markdown); // converter + stylesheet included
const branded = await markdownToPdfBlob(markdown, { skin: "article", tokens }); // tune without replacing
const custom = await markdownToPdfBlob(markdown, { convertToHtml, loadCss }); // override either portmarkdownToPdfBlob needs nothing from you. Both ports ship a working default
(they were REQUIRED injections until 0.3.0); supplying one replaces that
default and leaves the other alone. The defaults load through a lazy
import("@ai-matrx/print/markdown"), so a consumer that only does DOM capture
never pulls in the stylesheets.
Nothing degrades silently: a flashcard missing its Front:/Back:, markdown
that converts to an empty document, an image that 404s or never finishes
loading (html2canvas would paint a blank rectangle and say nothing), and a
failed rasterization all report through onNotice — or console.warn with the
remedy when no sink is given. imageTimeoutMs (default 8000) bounds the wait.
html2canvas + jsPDF, lazy-loaded on use. Handles the Tailwind v4 problem
(oklch/lab colors html2canvas can't parse) via a scoped getComputedStyle
patch that is always restored. captureElementsToPDF renders deliberately
paginated report pages one-per-PDF-page; the single-element lane slices one
tall canvas across pages.
@ai-matrx/print/react — the options dialog & sheet preview
import { PrintOptionsDialog, usePrintOptions, LabelSheetPreview } from "@ai-matrx/print/react";PrintOptionsDialog renders any BlockPrinter's variants + settings
(desktop dialog, mobile bottom drawer) and runs the print.
LabelSheetPreview shows a scaled, exact-proportion preview of a label sheet,
with a calibration outline mode. Tailwind CSS classes; Radix dialog + vaul
drawer are bundled dependencies.
License
MIT
@ai-matrx/print/document — print-grade documents (PDF, Word, EPUB, HTML)
import { exportDocument, downloadDocumentExport } from "@ai-matrx/print/document";
const exp = await exportDocument(markdown, "docx"); // "pdf" | "docx" | "epub" | "html" | "markdown"
downloadDocumentExport(exp); // browser: saves "<title>.docx"Settings ride in the markdown's frontmatter:
---
title: Quarterly Review
subtitle: Operations
author: Jane Park
date: today
cover: true
toc: true # or { depth: 2, title: Contents }
page: { size: a4, orientation: portrait, margins: 1in }
header: "{title} | | {date}" # left | center | right
footer: "Page {page} of {pages}"
columns: 1
numberSections: true
csl: apa # apa | mla | chicago
references:
- { id: smith2020, title: Paper Cuts, author: "Smith, Jane", issued: 2020, publisher: Acme }
---In the body: <!-- pagebreak -->, <!-- section landscape columns=2 -->,
[[toc]], {#fig:chart}, Table: Caption {#tbl:sales},
## Methods {#sec:methods}, see @fig:chart, [@smith2020, p. 4].
All four formats come from one parsed tree (parseDocument), so they agree.
Citations are formatted by citeproc-js over the official CSL styles:
csl: accepts any style name from the CSL repository (zotero.org/styles) or a
.csl URL; apa, mla, chicago, chicago-notes, ieee and harvard are
bundled (offline). References come from the frontmatter and from your app via
exportDocument(md, fmt, { resolveReferences: async (keys) => cslItems }).
PDFs are hyphenated with Knuth–Liang patterns for the document's lang, and
emoji / symbols print through Noto Emoji and Noto Sans Math.
CSL styles and locales © the Citation Style Language project, CC BY-SA 3.0.
Noto Emoji and Noto Sans Math are SIL Open Font License fonts (loaded at
export time from jsDelivr).
DocumentPrintPreview (in /react) shows the real PDF pages with Print and
every download.
