Zero-width characters inside a figure

unicode.zero-width-in-number

The same invisible codepoints placed between the digits of a number, where they have no typographic purpose and can change what a figure reads as by an order of magnitude.

How the text is hidden

A zero-width character is inserted between two digits, or across a thousands separator, so the figure in the text layer is not the figure on the page. In PDF the carrier is a /ToUnicode map: a code that paints a nought claims to be U+200B, so a page drawing a seven-digit figure extracts as a five-digit one interleaved with invisible characters. Once those characters are stripped — as this pack strips them — the canonical text reads a clean, smaller number, which is what makes the forgery tidy rather than obviously broken. The engine's own comment names this: the sanitisation is what completes the attack, so the finding has to be raised at the strip rather than left to a downstream comparison.

Why a model still reads it

The invisible characters are in the extracted string, so the model reads the shortened figure with nothing to indicate anything was removed. The reader signs the number painted on the page; the model reads the number left in the text.

What we do about it

A separate pack class, zero-width-in-number, matches the same five codepoints only when a digit sits on each side, allowing an optional . , no-break-space or space between — a thousands separator is part of a number. The pattern uses lookbehind and lookahead so the STRIP removes only the format characters and the surrounding digits survive; ` pins that the digits either side are still there afterwards. This is the only unicode class that carries its own severity field, graded high, overriding the confidence-band mapping every other class uses — an integrator's banner keyed on severity would otherwise see a magnitude error at medium. It reads extracted text only, with no dependence on resolving a font, which is why it covers the subsetted-font case where pdf.cmap-divergence` has no glyph name to compare and stays silent.

How often it fires

0% of CUAD — 510 real commercial contracts (EDGAR EX-10 exhibits), bulk-converted from HTML by Aspose/EVO, measured 2026-08-17.

This is an alert-volume number and nothing else. It says how often the alarm sounds on documents as found — not how often it is right, and not whether what it found was harmless. Documents as found may themselves carry concealment. Read it against the population named above rather than as a property of documents in general.

next

Check your own file

Three commands: a key, credit, a verdict.

Start with the API