Skip to content

Build1 publisher3 min readPublished

Your .ai viewer is a pdf.js problem, and its worst bugs never throw

Illustrator files saved with PDF compatibility are PDF containers, so the browser side is signature detection plus pdf.js. The failures that cost time produce no error at all.

The Engineer · Build desk

Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

What happened

  • An .ai file saved with Create PDF Compatible File turned on, the default since Illustrator 9, is a PDF container on disk, and every artboard is a page.
  • Because the bytes are a PDF container, a browser-side .ai viewer does not require reverse-engineering a proprietary format: pointing pdf.js at the file opens it.
  • The author built ArtboardLab, a free browser-based tool set where nothing gets uploaded, and the lessons described come from its .ai viewer/converter; the failures after initial setup produced no error message at all.
  • Detection is a byte-signature check on the head of the file rather than the extension: the first 1024 bytes are decoded as latin1 and checked for '%PDF-' (modern, PDF-compatible) or '%!PS-Adobe' (Illustrator 8 and older), otherwise 'unknown'.
  • '%PDF-' is searched for within the head rather than matched at offset 0 because the PDF specification tolerates leading bytes before the header.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

An Adobe Illustrator file saved with Create PDF Compatible File turned on, the default since Illustrator 9, is a PDF container on disk, with every artboard as a page [1]. That is the whole basis for opening .ai in a browser without reverse-engineering anything: point pdf.js at the bytes and it opens [2]. The rest of the work, according to a write-up from the developer of ArtboardLab, a browser-side tool set that uploads nothing, is a sequence of failures that emit no error [3].

Detection is not the file extension. The check decodes the first 1024 bytes as latin1 and looks for `%PDF-` or `%!PS-Adobe` in that window [4]. `%PDF-` is searched for rather than matched at offset 0, because the PDF specification tolerates leading bytes before the header [5]. Files from Illustrator 8 and older are PostScript, which no browser will read, so that branch returns an honest error instead of a spinner [6]. This is the cheap part, and it is the only part that fails loudly.

The expensive part: the first Korean-language file the developer tested rendered perfectly except that every Hangul glyph was missing, with no exception, no console warning and no fallback box, while vector shapes and Latin text were fine [7]. CID-keyed fonts, which cover Korean, Japanese, Chinese and other multi-byte encodings, need Adobe's CMap tables to map character codes to glyph IDs [8]. pdf.js ships those tables but does not bundle them into the library; it fetches them at runtime from `cMapUrl`, and if the option is unset there is nothing to fetch and nothing to report [9]. Two sibling options have the same shape: `standardFontDataUrl` supplies substitutes for standard fonts a document references but does not embed, and `wasmUrl` points at the WASM used to decode JPX (JPEG 2000) images, which appear in print-oriented artwork more often than you would expect [10].

In ArtboardLab those three directories are served from the same origin as static assets, no CDN: 198 files, mostly `.bcmap` tables, about 3.3 MB total [11], which averages roughly 17 KB per file [12]. The operational point is that a missing CMap directory does not break the page. It quietly deletes other people's languages [13], so a build that stops copying those directories will look green in every smoke test written by someone who reads Latin script.

Transparent PNG export is the other silent one. pdf.js `render()` accepts a `background` option, and per the write-up `rgba(0,0,0,0)`, the string `transparent` and even a transparent `CanvasPattern` all paint opaque black in pdf.js v6, each variant verified before the conclusion was accepted [14]. The workaround is compositing: render the page on white and on black, derive alpha from the per-channel difference, then divide the black composite back out because it is already premultiplied [15]. An opaque pixel is identical on both backgrounds, a fully transparent one differs by the full range, and partial coverage lands proportionally between [16]. The cost is one extra full rasterisation per output, a 100 per cent increase in render work [17], and it extends to thumbnails and the preview, because the UI checkerboard is only honest if the alpha behind it is real [18].

Worth watching: whether the v6 background behaviour changes in a later pdf.js release, since it is currently a single developer's finding across several variants [14]; and whether your own asset pipeline can prove the CMap, standard-font and WASM directories actually shipped [13]. Both are cheap to assert in CI and impossible to notice by eye.

Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories