PDFPipe

2D matrix / QR Code

QR code in a PDF

The 2D code everyone recognises: read by any phone, generous error correction, and a quiet zone four modules wide that almost nobody leaves.

Where the symbol comes from

This API renders HTML to PDF. It does not generate barcodes, so the QR Code symbol itself is produced by an encoder in your own stack and placed in the markup, either as inline SVG or as a data URI. That split matters more than it sounds: the encoders are mature and correct, and almost every barcode that will not scan failed for a layout reason rather than an encoding one. Everything below is about the layout half.

What it can encode

Up to 7,089 digits, 4,296 alphanumeric characters or 2,953 bytes at the largest of the forty versions. In practice the size that matters is the physical one: more data means more modules in the same space, which means smaller modules and a harder scan.

The check digit, and who computes it

Reed-Solomon error correction at four levels: L, M, Q and H, recovering roughly 7, 15, 25 and 30 percent of the symbol. H is not automatically the right choice, because the extra correction codewords make the symbol denser at the same physical size, and a denser symbol is harder to scan in the first place.

Size and quiet zone

The two measurements that decide whether a scanner accepts the symbol, and the two most often lost when a design is adjusted to fit.

  • X-dimension: The module size. About 0.4mm is a sound floor for a printed symbol read by a phone at arm's length. The often-quoted rule of thumb is that the symbol should be at least a tenth of the intended reading distance, so a code read from two metres away wants to be 20cm across.
  • Quiet zone: 4 modules on all four sides. This is the most commonly violated rule in the whole cluster: a QR code dropped into a design flush against a coloured panel has no quiet zone, and it fails for a reason nobody looking at it can see.

Where it is used

  • tickets, passes and anything scanned by a member of the public with their own phone
  • payment and menu links in hospitality
  • asset and equipment labels where a phone is the scanner
  • document provenance, where the code links back to a verification page

Placing it in the markup

The quiet zone is padding on the container rather than part of the encoder's output, so it survives a change of encoder. Dimensions are in millimetres, because a bar width is a physical measurement and a percentage makes it depend on whatever the container happens to be.

html
<figure class="qr">
  <img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0i..." alt="Ticket TKT-2026-0442">
  <figcaption>TKT-2026-0442</figcaption>
</figure>

<style>
.qr {
  margin: 0;
  /* The quiet zone is four modules. With a 34mm symbol of 33 modules
     that is about 4mm, and it must be the same white as the symbol
     background rather than the page colour behind a tinted panel. */
  padding: 4mm;
  background: #fff;
  width: max-content;
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}
.qr img {
  /* Square, in absolute units. A percentage width with an auto height
     produces a symbol a fraction wider than it is tall as soon as the
     caption wraps, and a non-square QR code does not decode. */
  width: 34mm;
  height: 34mm;
  display: block;
}
.qr figcaption {
  text-align: center;
  font: 8pt/1.2 "IBM Plex Mono", monospace;
  margin-top: 2mm;
}
</style>

What goes wrong on paper

Placing the code on a tinted or photographic background. A scanner needs contrast between the dark modules and the light ones, and the quiet zone is part of that: it has to be the same light colour as the symbol's own background, not the design behind it. A white symbol on a brand-coloured panel with no white margin is the most common non-scanning QR code in existence.

First thing to check when it will not scan

Add the four module quiet zone and try again before touching anything else. If it still fails, check that the symbol is exactly square and that the light modules are genuinely light: an inverted code, dark on light reversed to light on dark, is not readable by most consumer scanners.

Vector, not a bitmap

Encode to SVG wherever the encoder offers it. A rasterised barcode is resampled when the PDF is printed, and at 203 or 300 dots per inch the bar edges land between printer dots, so the ratio of bar to space drifts by a fraction of a millimetre. That fraction is the whole tolerance. An SVG stays vector inside the PDF and is rendered at the printer's own resolution, which removes the entire failure mode. Where a bitmap is unavoidable, generate it at the final physical size and set image-rendering so nothing smooths the edges.

Frequently asked

Does this API generate QR Code symbols?

No. It renders HTML to PDF. The symbol is produced by a barcode library in your own application and embedded in the markup as SVG or as a data URI, which is the same way you would put any other generated graphic into a document. What this API is responsible for is that the symbol reaches the page at the size and position you specified.

Why does it scan on screen and fail on paper?

Three reasons, in order of frequency. The quiet zone is present on screen because the browser window is white and absent on paper because the label is not. The symbol was sized as a percentage, so it came out at a different physical width than the one that was tested. Or it is a bitmap, and printing resampled it so the bar-to-space ratio moved outside tolerance.

Can I scale the symbol to fit the space I have?

Uniformly, within the range the symbology allows, and never on one axis. The module size. About 0.4mm is a sound floor for a printed symbol read by a phone at arm's length. The often-quoted rule of thumb is that the symbol should be at least a tenth of the intended reading distance, so a code read from two metres away wants to be 20cm across. is the constraint on the way down. Scaling one axis changes the bar-to-space ratio on a linear symbology and makes a 2D symbol non-square, and neither survives a scanner.

Other symbologies

Codes of the same kind, and one from each of the other kinds.

Paste the markup into the playground with your own encoder's output and see what comes out at the real page size.