Skip to main content

Images & fonts

Local asset and font roots, the validation boundary, and the opt-in remote allowlist with its security posture.

Images and fonts are resolved and embedded during the Nuxt build from explicit local roots. Remote fetching is off by default and, when enabled, is gated by an operator-owned allowlist. This is a fail-closed resource boundary, not a general filesystem or network sandbox.

Local images

Put PNG or JPEG files in pdfs/assets and reference the path relative to that directory:

vue
<PdfImage
  src="brand/logo.png"
  :style="{ height: 40, objectFit: 'contain', width: 120 }"
/>

Local fonts

Put TTF or OTF files in pdfs/fonts and register each face in nuxt.config.ts:

nuxt.config.ts
export default defineNuxtConfig({
  modules: ["@lupinum/nuxt-pdf"],
  pdf: {
    fonts: [{
      family: "Invoice Sans",
      src: "InvoiceSans-Regular.ttf",
      fontWeight: 400,
      fontStyle: "normal",
    }],
  },
});

Then use the family in a style object. Font inheritance flows through the tree, so setting it on the page covers the document:

vue
<PdfPage :style="{ fontFamily: 'Invoice Sans' }">
  <PdfText>Invoice</PdfText>
</PdfPage>

Register one entry per weight/style you use; fontWeight accepts a number or a named weight ("bold" or "medium"). See the module options reference for the full font declaration.

Completed render diagnostics expose registeredFontFaces (family, weight, and style only), and the development preview lists the same safe facts. This proves which faces were configured; it deliberately does not claim that a missing glyph fell back or that every character exists in a face.

Typography support boundary

Every claim assumes the application embeds a font containing the required glyphs. The calibration fixture checks shaping, extraction, geometry, and a reviewed raster, not only whether rendering avoided an exception.

Text behaviorStatusEvidence and boundary
Latin Extended, punctuation, currencySupportedRender, extraction, wrapping, and raster evidence with Roboto
Greek and CyrillicSupportedRepresentative sentences render and extract exactly with Roboto
Custom hyphenationSupportedPaired layout/raster conformance; application supplies the callback
CJKExperimentalRepresentative Chinese/Japanese text renders and extracts with a supplied Noto subset; broad font coverage and line-breaking policy remain application evidence
Combining marksExperimentalMarks render correctly, but PDF extraction can detach or omit mark association (Ångström extracts as A ngstrom)
Arabic and mixed bidirectional textExperimentalRepresentative Arabic shapes visually and reports an RTL run; extracted mixed-order text follows bidi ordering rather than the authored string
Variable fontsExperimentalA Source Code variable TTF renders/extracts at its default instance; axis selection is not a public API
EmojiUnsupportedMonochrome face emoji do not serialize faithfully through the pinned engine; use an admitted PNG or SVG asset instead
Font-family fallback chainsUnsupportedfontFamily names one registered family; Nuxt PDF does not invent missing-glyph or fallback detection

Use static faces for production documents unless your own semantic and raster suite proves an experimental script/font combination.

The validation boundary

Every configured resource is checked at build time. Resources are:

  • structurally checked for PNG/JPEG chunks, dimensions, and decoded pixels; TTF/OTF signature, matching extension, SFNT directory bounds, and required outline tables;
  • size-checked with limits of 10 MB for images and 5 MB for fonts;
  • realpath-contained, so resolved paths must stay inside the declared root; and
  • embedded into the server build, so the bytes travel in the build and there is no runtime filesystem read.

Absolute paths, .. traversal, symlink escapes, missing files, ambiguous sources, and unsupported URL schemes are all rejected. There is no runtime filesystem fallback.

Opt-in remote images

Remote images are unsupported until you configure an allowlist. Remote fonts are always unsupported; keep fonts in pdfs/fonts so builds are reproducible. With pdf.remote absent the module performs zero network I/O and every URL source fails closed.

When an operator sets pdf.remote.allow, the module fetches allowlisted images and converts them to validated bytes before layout:

nuxt.config.ts
export default defineNuxtConfig({
  modules: ["@lupinum/nuxt-pdf"],
  pdf: {
    remote: {
      allow: [
        "https://cdn.example.com/brand/",
        "https://images.example.com/logos/",
      ],
      // Optional per-hop timeout (default shown):
      // timeoutMs: 10_000,
    },
  },
});

The security posture

The allowlist is the operator's trust decision, and the fetch path is deliberately narrow:

  • Exact HTTPS prefixes only. Each entry must be an https://host/path/ prefix with a trailing slash. Prefix entries reject queries too; requested image URLs may carry a query, which is never shown in errors. http://, wildcards, embedded credentials, fragments, and non-matching hosts, ports, or paths are blocked.
  • Redirects re-checked per hop. Redirects are followed manually (bounded to three) and the allowlist is re-checked on every hop, so an allowlisted host cannot redirect out of the allowlist.
  • One render budget. pdf.limits owns per-image and aggregate source bytes, decoded pixels, request count, concurrency, output bytes, and the whole-render deadline. A fatal resource error aborts sibling requests.
  • Signature is authoritative. A deceptive Content-Type cannot make non-image bytes validate; PNG/JPEG structure and dimensions are inspected before engine admission.
  • No credentials, no headers. Fetches are GET only, send no request headers, and carry no cookies or credentials, under a per-hop timeout.

Remote images resolve at render time (deduplicated per render, no cross-render cache). Local fonts are validated and embedded at build time.

Not claimed: authenticated fetches, request headers or bodies, proxies, and private-IP or DNS-rebinding protection beyond the allowlist. The allowlist controls which hosts and paths the module can fetch. It is not an SSRF sandbox.