Takumi

Invoices

Paged invoices, and the PDF/A-3 container an EU e-invoice needs.

An invoice PDF serves two readers. A person reads the layout. A tax authority's software reads structured data attached inside the same file. takumi-pdf writes both halves.

A plain invoice

Line items flow across pages. A footer carries the page counter. measure returns the band's height, so the bottom margin always clears it:

import {  } from "@takumi-rs/helpers";
import { ,  } from "takumi-pdf";

const  = (
  < ="flex w-full justify-between px-12 text-[10px] text-[#6b7280]">
    <>Invoice {.}</>
    < ="flex">
      Page < ="pageNumber" /> of < ="totalPages" />
    </>
  </>
);

const  = await (["Inter"]);
const {  } = await (, { : "a4",  });

const  = await (< ={} />, {
  : "a4",
  : { : 48, :  + 32, : 48, : 48 },
  ,
  ,
});

Mark each line row break-inside: avoid so a row never straddles a page. The Tailwind class is break-inside-avoid:

import "takumi-pdf";

< ="flex break-inside-avoid gap-3 pt-3 text-xs">
  < ="flex-1">{.}</>
  < ="w-[100px] text-right">{.}</>
</>;

Wrap the totals block the same way. It is the part a reader most expects to see whole.

Electronic invoices

Factur-X and ZUGFeRD are the same file twice: a readable invoice, plus the invoice as XML attached inside it. The standards pin how that file is packaged.

The standard requiresThe option
A PDF/A-3 containerpdfa: "3b"
The XML attached by nameattachments
An fx: XMP blockmetadata.xmp
A modification date per filemodificationDate
Embedded subset fontsalways on

Each attachment's modificationDate falls back to metadata.creationDate. Setting the document date once covers both.

import {  } from "takumi-pdf";

const  = await (, {
  : "en",
  : "3b",
  : "ua1",
  : {
    : "Invoice INV-2026-0042",
    : "2026-08-06",
    : [],
  },
  : [
    {
      : "factur-x.xml",
      : ,
      : "text/xml",
      : "Factur-X 1.0 MINIMUM invoice data",
      : "data",
    },
  ],
});

The renderer validates while it writes. A document that cannot conform fails with the violated rule, instead of producing a file a tax portal rejects.

Takumi does not build the invoice XML. Use a library for the profile, or emit the elements the profile lists. The e-invoice example writes a MINIMUM profile by hand.

Validating the result

Two validators cover the two halves. Both need Java.

veraPDF checks the container:

verapdf --flavour 3b --format text invoice.pdf
verapdf --flavour ua1 --format text invoice.pdf

Mustang checks the Factur-X half, both the XML and the packaging:

java -jar Mustang-CLI.jar --action validate --source invoice.pdf

Mustang finds the profile through the fx: XMP block. Takumi writes the values and the schema description PDF/A demands, so the two cannot drift. It does not check the values: a wrong profile name surfaces in Mustang, not at render time.

Accessible invoices

Public-sector buyers often require an accessible invoice. tagged: "ua1" validates the structure tree against PDF/UA-1 during the render. See PDF/A for what it needs.

Reproducible bytes

A fixed metadata.creationDate makes the output byte-identical across runs. The same invoice hashes to the same value, which suits archival storage and golden tests.

Last updated on

On this page