{
  "markdown": "# @boxpdf/html-reader\n\nReadable HTML-to-PDF rendering built on [`@boxpdf/writer`](https://github.com/earonesty/boxpdf). It is for invoices, receipts, reports, emails, and other authored document HTML where a useful static PDF matters more than browser pixel emulation.\n\n```sh\nnpm install @boxpdf/html-reader @boxpdf/writer pdf-lib\n```\n\nThe original `boxpdf-html` package and `boxpdf-html` command remain supported. They are published\nfrom the same build and at the same version as `@boxpdf/html-reader`. New projects should use the\nscoped package; existing projects do not need to change.\n\n## CLI\n\nRender an HTML file directly:\n\n```sh\nnpx @boxpdf/html-reader invoice.html invoice.pdf\n```\n\nThe scoped package also provides `html-reader` as a shorter command name.\n\nWith generated Tailwind CSS:\n\n```sh\nnpx tailwindcss -i ./tailwind.css -o ./dist/tailwind.css --minify\nnpx @boxpdf/html-reader invoice.html invoice.pdf --css ./dist/tailwind.css\n```\n\nFor very large inputs, add `--stream`:\n\n```sh\nnpx @boxpdf/html-reader archive.html archive.pdf --stream\ntype archive.html | npx @boxpdf/html-reader - archive.pdf --stream\n```\n\nStreaming makes two bounded passes over the HTML: one for CSS, fonts, and\nimages, then one for incremental layout and PDF output. Stdin is spooled to a\ntemporary file so it can be reopened. The output replaces its destination only\nafter a successful conversion. This path requires `boxpdf` 1.12.0 or newer.\n\nWith PDF 2.0 AES-256 password encryption:\n\n```sh\nBOXPDF_PASSWORD='open me' \\\n  npx @boxpdf/html-reader invoice.html invoice.pdf --password-env BOXPDF_PASSWORD\n```\n\n`--password-env` accepts an environment-variable name. The variable must be\nset to a non-empty password. The password value is never accepted as a command\nargument, which keeps it out of the command line and process listing.\n\nWith custom fonts and local images:\n\n```sh\nnpx @boxpdf/html-reader invoice.html invoice.pdf \\\n  --font ./Inter-Regular.ttf \\\n  --bold-font ./Inter-Bold.ttf \\\n  --font-family 'Inter=normal:Inter-Regular.ttf,bold:Inter-Bold.ttf'\n```\n\nUseful flags:\n\n```sh\nhtml-reader <input.html> <output.pdf>\nhtml-reader - <output.pdf>                  # read HTML from stdin\nhtml-reader input.html output.pdf --css app.css\nhtml-reader input.html output.pdf --base-url ./public\nhtml-reader input.html output.pdf --password-env BOXPDF_PASSWORD\nhtml-reader input.html output.pdf --debug\nhtml-reader input.html output.pdf --unsupported-css\nhtml-reader input.html output.pdf --profile\nhtml-reader input.html output.pdf --stream\n```\n\nThe CLI defaults to pdf-lib's built-in Helvetica family. Use real embedded fonts for production output when brand matching, unicode coverage, or exact metrics matter.\n\n## MCP server\n\n`boxpdf-html mcp` is a stdio [MCP](https://modelcontextprotocol.io) server for AI agents. It's batteries-included: an `html_to_pdf` tool plus the full boxpdf library docs, so an agent never has to add a second server.\n\n```sh\nclaude mcp add boxpdf-html -- npx -y @boxpdf/html-reader mcp\n```\n\n**Tools**\n\n- `html_to_pdf` — render an HTML string (and optional `css`) to a PDF. Writes to `outputPath`, or returns the PDF inline as a base64 resource. Always returns `warnings` and `unsupportedCss` diagnostics so the agent can fix its input.\n  - Args: `html` (required), `css`, `outputPath`, `size` (`Letter`/`A4`/`Legal`/`Tabloid`, default `Letter`), `margin` (default 40), `baseUrl`, `fonts: { regular, bold, italic, boldItalic }` (TTF/OTF paths), `allowRemote` (default `false` — http(s) image fetches are blocked unless enabled), `debug`.\n- `boxpdf_docs` — focused guidance for building PDFs with the libraries directly. `topic`: `quickstart` (default), `fonts`, `themes`, `tables`, `pagination`, `streaming`, `html-api`, `cloudflare`.\n\n**Resources**: `boxpdf-html://guide`, `boxpdf-html://readme`, `boxpdf://readme`, and the five `boxpdf://templates/<name>` sources (receipt, boarding-pass, resume, order-confirmation, certificate).\n\nThe server runs no JavaScript and (by default) makes no network requests. `outputPath` writes with the agent's filesystem permissions; without it, PDFs over 1 MB are summarized rather than inlined.\n\n## API\n\n### `htmlToPdf` — one call to bytes\n\n`htmlToPdf(html, options?)` is the simplest path: it creates the document, embeds fonts, renders, and returns the PDF bytes. Fonts default to the built-in Helvetica family, so the minimal call needs no setup.\n\n```ts\nimport { htmlToPdf } from \"@boxpdf/html-reader\";\n\nconst bytes = await htmlToPdf(\"<h1>Invoice</h1><p>Thanks for your order.</p>\");\n```\n\nPass embedded fonts (via `loadFont`) and a `resolveImage` callback for production output:\n\n```ts\nimport { readFile } from \"node:fs/promises\";\nimport { PDFDocument } from \"pdf-lib\";\nimport { loadFont, loadImage } from \"@boxpdf/writer\";\nimport { htmlToPdf } from \"@boxpdf/html-reader\";\n\nconst pdf = await PDFDocument.create();\nconst inter = await loadFont(pdf, await readFile(\"Inter-Regular.ttf\"));\nconst interBold = await loadFont(pdf, await readFile(\"Inter-Bold.ttf\"));\nconst logo = await loadImage(pdf, await readFile(\"logo.png\"));\n\nconst bytes = await htmlToPdf(await readFile(\"invoice.html\", \"utf8\"), {\n  pdf,                       // reuse the document you embedded into\n  font: inter,\n  boldFont: interBold,\n  resolveImage: ({ url }) => (url === \"logo.png\" ? logo : undefined),\n  margin: 40\n});\n```\n\nOptions: `font` / `boldFont` / `italicFont` / `boldItalicFont` (default to Helvetica), `pdf` (render into an existing document), `margin` (default 40), `size` (default US Letter), `width` (CSS containing-block width; defaults to the page's content width), `debug`, plus everything `htmlToBoxpdf` accepts (`resolveFont`, `resolveImage`, `baseUrl`, `defaultFontSize`, `defaultColor`, `diagnostics`, `profile`).\n\n### `htmlToBoxpdf` — the nodes, for full control\n\n`htmlToBoxpdf` turns HTML into normal boxpdf nodes without rendering. Reach for it when you need the nodes themselves, the `warnings`/`diagnostics`, multiple render passes, or `renderFlow` headers/footers.\n\n```ts\nimport { readFile } from \"node:fs/promises\";\nimport { PDFDocument } from \"pdf-lib\";\nimport { loadFont, loadImage, renderFlow } from \"@boxpdf/writer\";\nimport { fontFamily, htmlToBoxpdf } from \"@boxpdf/html-reader\";\n\nconst html = await readFile(\"invoice.html\", \"utf8\");\nconst pdf = await PDFDocument.create();\n\nconst inter = await loadFont(pdf, await readFile(\"Inter-Regular.ttf\"));\nconst interBold = await loadFont(pdf, await readFile(\"Inter-Bold.ttf\"));\nconst logo = await loadImage(pdf, await readFile(\"logo.png\"));\n\nconst result = htmlToBoxpdf(html, {\n  font: inter,\n  boldFont: interBold,\n  resolveFont: fontFamily({\n    Inter: { normal: inter, bold: interBold },\n    \"sans-serif\": { normal: inter, bold: interBold }\n  }),\n  resolveImage: ({ url }) => (url === \"logo.png\" ? logo : undefined),\n  baseUrl: process.cwd(),\n  width: 532\n});\n\nconsole.log(result.warnings);\nawait renderFlow(pdf, result.nodes, { margin: 40 });\nconst bytes = await pdf.save();\n```\n\n`width` is the CSS containing block width in PDF points. A US Letter page with 40pt margins has a 532pt content width, so `width: 532` is a good default.\n\n### `streamHtmlToPdf` — bounded large-document conversion\n\n`streamHtmlToPdf` accepts a function that reopens the HTML for each of its two\npasses and writes PDF bytes incrementally. Embed fonts before calling it; use\n`prepare` to embed images found by the resource preflight before output begins.\n\n```ts\nimport { createReadStream, createWriteStream } from \"node:fs\";\nimport { PDFDocument, StandardFonts } from \"pdf-lib\";\nimport { nodeAdapter } from \"@boxpdf/writer\";\nimport { streamHtmlToPdf } from \"@boxpdf/html-reader\";\n\nconst pdf = await PDFDocument.create();\nconst font = await pdf.embedFont(StandardFonts.Helvetica);\n\nconst result = await streamHtmlToPdf(\n  () => createReadStream(\"archive.html\"),\n  nodeAdapter(createWriteStream(\"archive.pdf\")),\n  { pdf, font, width: 532, margin: 40 }\n);\n\nconsole.log(result.pageCount, result.dom.maxBufferedNodes);\n```\n\nOrdinary block wrappers and tables are released in bounded continuation\nfragments. Atomic layouts such as flex/grid, positioned or transformed\ncontainers, and single uninterrupted text nodes have explicit safety caps and\nfail with a useful error when they cannot be streamed safely. Selectors whose\nmeaning depends on sibling position conservatively disable wrapper\nfragmentation.\n\n## Fonts\n\nFonts are explicit. `boxpdf-html` does not discover system fonts and does not ship a browser font stack. This keeps rendering deterministic and works in serverless runtimes.\n\nAt minimum, pass `font`. Pass `boldFont` and `italicFont` if your HTML uses bold or italic text:\n\n```ts\nconst result = htmlToBoxpdf(html, {\n  font,\n  boldFont,\n  italicFont,\n  width: 532\n});\n```\n\nFor CSS `font-family`, use `fontFamily()`:\n\n```ts\nconst resolveFont = fontFamily({\n  Inter: {\n    normal: interRegular,\n    bold: interBold,\n    italic: interItalic,\n    boldItalic: interBoldItalic\n  },\n  Helvetica: {\n    normal: fallback,\n    bold: fallbackBold\n  },\n  \"sans-serif\": {\n    normal: fallback,\n    bold: fallbackBold\n  }\n});\n```\n\nThe resolver receives `{ families, weight, style }` and returns a pdf-lib `PDFFont`. You can provide your own resolver when you need looser mapping, font aliases, language-specific fallbacks, or weight synthesis.\n\nGotchas:\n\n- `font-family: system-ui` only works if your resolver maps `system-ui`.\n- Standard pdf-lib fonts are convenient but limited; use embedded TTF/OTF fonts for real documents.\n- Complex shaping depends on pdf-lib/fontkit behavior. Western-language invoice/report text is the target.\n- Font metrics affect layout. Use the same embedded fonts in tests and production when visual stability matters.\n\n## Tailwind CSS\n\nTailwind works when you render its generated CSS, not raw class names alone. The usual flow is:\n\n1. Write document HTML with Tailwind classes.\n2. Run Tailwind against that HTML.\n3. Inline or pass the generated CSS to `boxpdf-html`.\n4. Render with a containing width that matches your intended PDF content area.\n\nExample source:\n\n```html\n<div class=\"p-6 bg-[#f8fafc] text-gray-900\">\n  <div class=\"max-w-[520px] rounded-[10px] border bg-white p-5 shadow-sm\">\n    <div class=\"grid grid-cols-[1fr_2fr] gap-x-4 gap-y-3\">\n      <div class=\"rounded-md border border-blue-200 bg-blue-50 p-3\">\n        <p class=\"text-xs font-semibold uppercase tracking-wide text-blue-700\">Status</p>\n        <p class=\"mt-1 text-sm font-bold\">Paid</p>\n      </div>\n      <div class=\"rounded-md border border-gray-200 p-3\">\n        <p class=\"text-xs font-semibold uppercase tracking-wide text-gray-600\">Notes</p>\n        <p class=\"mt-1 text-sm leading-5\">Two fraction column wraps later.</p>\n      </div>\n    </div>\n  </div>\n</div>\n```\n\nBuild CSS:\n\n```css\n@import \"tailwindcss\";\n@source \"./invoice.html\";\n```\n\n```sh\nnpx tailwindcss -i ./tailwind-input.css -o ./tailwind-output.css --minify\nnpx @boxpdf/html-reader invoice.html invoice.pdf --css ./tailwind-output.css\n```\n\nSupported Tailwind patterns include common spacing, color, text, border, radius, width/height, flex, grid, table, image, and arbitrary-value utilities. Unsupported utility declarations can be reported with `--unsupported-css` or `diagnostics: { unsupportedCss: true }`.\n\nTailwind gotchas:\n\n- Responsive/state variants are parsed as CSS; there is no viewport interaction. Choose a single generated CSS target for the PDF you want.\n- Two-dimensional transforms support ordered `translate`, `scale`, `rotate`, `skew`, and `matrix` function lists, their X/Y variants, `transform-origin`, and the standalone `translate`, `rotate`, and `scale` properties. Length/percentage translations and `deg`, `grad`, `rad`, and `turn` angles are supported. Three-dimensional transforms, shadows, filters, transitions, and browser-only effects are ignored or reported as unsupported.\n- Tailwind preflight resets are mostly harmless. Diagnostics intentionally focus on utility selectors instead of noisy base selectors.\n- If text layout matters, use the same fonts in Tailwind design review and PDF rendering.\n\n## Images\n\nThe API uses `resolveImage` because pdf-lib images must be embedded before rendering:\n\n```ts\nconst images = new Map([\n  [\"logo.png\", await loadImage(pdf, await readFile(\"logo.png\"))]\n]);\n\nhtmlToBoxpdf(html, {\n  font,\n  resolveImage: ({ url }) => images.get(url),\n  baseUrl: process.cwd()\n});\n```\n\nThe CLI preloads local, `http(s)`, and `data:` image URLs referenced by `<img src>` and CSS `url(...)`. Missing images preserve their layout box when width/height can be inferred.\n\n## CSS And HTML Surface\n\nSupported:\n\n- HTML fragments and full documents via `parse5`.\n- Stylesheets and inline styles via `css-tree`.\n- Selectors: tag, class, id, attributes, descendants, child/sibling combinators, common structural pseudos, and escaped Tailwind selectors.\n- Cascade basics: stylesheet rules, inline style, `!important`, inheritance, custom properties, `var()`, and common `calc()`.\n- Layout: block, inline, inline-block, inline-flex, inline-grid, flex, grid fallback, tables, floats, absolute/relative positioning, z-index, overflow hidden, and replaced images.\n- Text: rich inline runs, hard breaks, normal/no-wrap/pre-like whitespace, transforms, decoration, alignment, vertical-align, list hanging indents, and wrapping.\n- Sizing/styling: CSS px to points, pt, em/rem, vw/vh, percentages in common places, min/max widths, box-sizing, margin/padding/gap, backgrounds, background images, borders, per-side borders, border collapse, radius, object-fit.\n\nNot a browser:\n\n- No JavaScript execution.\n- No interactive or dynamic layout.\n- No full browser paint model.\n- No system font discovery.\n- CSS support is intentionally expanded around static document output. Use diagnostics to find unsupported declarations in real templates.\n\n## Diagnostics\n\n```ts\nconst result = htmlToBoxpdf(html, {\n  font,\n  width: 532,\n  diagnostics: { unsupportedCss: true, sampleLimit: 3 },\n  profile: (event) => console.log(event.phase, event.elapsedMs)\n});\n\nconsole.log(result.diagnostics?.unsupportedCss);\n```\n\nUnsupported CSS diagnostics are aggregated by property/value pair and include selector samples. Profile events cover parsing, CSS, style computation, render-tree construction, and output node counts.\n\nUseful commands:\n\n```sh\npnpm run typecheck\npnpm run test\npnpm run build\npnpm run tailwind:fixture\npnpm run visual:check\npnpm run pack:release\nBOXPDF_DEP_VERSION=^1.7.0 pnpm run publish:release\n```\n",
  "bytes": 14472,
  "sha": "a4808c120f6ce74bc5e20108f6edd4f5bc387b437c607b1b6849f1d09cde6c28",
  "repo_slug": "earonesty/boxpdf-html",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_earonesty_boxpdf_html_acc5d2da/readme"
}