Skip to content

PDF: legacy CJK CMaps — code → CID → Unicode for composite fonts#593

Merged
andiwand merged 2 commits into
mainfrom
pdf-cjk-legacy-cmaps
Jul 5, 2026
Merged

PDF: legacy CJK CMaps — code → CID → Unicode for composite fonts#593
andiwand merged 2 commits into
mainfrom
pdf-cjk-legacy-cmaps

Conversation

@andiwand

@andiwand andiwand commented Jul 5, 2026

Copy link
Copy Markdown
Member

🤖 Generated with Claude Code

Stacked on #591. Closes the deferred legacy CJK CMap selectability gap for the in-house PDF engine.

Problem

A composite (Type0) font with no /ToUnicode whose /Encoding is a legacy CJK CMap (90ms-RKSJ-H, GBK-EUC-H, B5pc-H, KSC-EUC-H, …) — or Identity-H/V with a real /CIDSystemInfo — previously yielded "no Unicode". The glyphs rendered correctly (PUA re-encode), but the text was not selectable or searchable. This was the last open item in CMap coverage.

What lands

  • Generated tables (pdf_cid_data.{hpp,cpp}, from generate_cid_data.py). 130 legacy CMaps across Adobe-Japan1/-GB1/-CNS1/-Korea1/-KR. Packed as the "S3" tier from the storage analysis (0.58 MB compiled, directly indexable, no new dependency):
    • interned code → CID ranges (73% cross-CMap dedup) + per-CMap uint16 index lists;
    • per-collection CID → Unicode as a presence bitmap + rank index + uint16 value array (astral escape for the ~1% > U+FFFF).
    • Data is Adobe's cmap-resources (BSD-3-Clause) — fetched by the script, not vendored; already attributed in THIRD_PARTY_LICENSES.md.
  • Runtime (pdf_cid): translate_predefined_cmap now resolves named legacy CMaps (codespace split → code → CID binary search over the interned ranges → CID → Unicode bitmap rank); new cid_to_unicode(registry, ordering, cid) covers the Identity-H/V + embedded-CMap-stream case (code == CID, collection from /CIDSystemInfo).
  • Wiring (Font::to_unicode): tries the named CMap, then the /CIDSystemInfo collection — guarded so a named CMap we lack tables for is never misread as identity CIDs. Falls through to the embedded reverse map as before.
  • Generated arrays are std::array, exported definitions qualified (cid_data::…) in the parent namespace so a header rename fails to compile — matching pdf_encoding_data.

Display was never affected (glyphs already render via the font); this is purely a selectability/search improvement.

Tests

pdf_cid.cpp (assertion-based, vectors derived from the Adobe data and frozen inline): Shift-JIS + EUC code → CID → Unicode, a mixed 1-/2-byte codespace, cid_to_unicode by collection incl. an astral escape, and nullopt for Identity-H / an unshipped name. Full suite green (425 unit + 49 PDF HTML reference-output); no corpus fixture hits the path, so no snapshot regen.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2285c936d0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/odr/internal/pdf/pdf_cid.cpp Outdated
@andiwand andiwand force-pushed the pdf-link-annotations branch from 5346eef to 803c25d Compare July 5, 2026 09:41
@andiwand andiwand force-pushed the pdf-cjk-legacy-cmaps branch 3 times, most recently from 636da54 to dabc445 Compare July 5, 2026 11:03
@andiwand andiwand changed the base branch from pdf-link-annotations to main July 5, 2026 11:05
A composite (Type0) font with no /ToUnicode whose /Encoding is a legacy
CJK CMap (90ms-RKSJ-H, GBK-EUC-H, B5pc-H, KSC-EUC-H, …) previously
yielded "no Unicode" — the glyphs rendered (PUA re-encode) but the text
was not selectable. This closes that gap.

generate_cid_data.py now emits pdf_cid_data.{hpp,cpp} (the deferred
tables): interned code → CID ranges with per-CMap uint16 index lists, and
a per-collection CID → Unicode presence bitmap + rank index + uint16 value
array (astral escape). This is the "S3" packing from the storage analysis
— ~0.58 MB compiled, directly indexable, no new dependency. Data is
Adobe's cmap-resources (BSD-3-Clause), fetched by the script, not vendored.

pdf_cid gains the lookup: translate_predefined_cmap handles the named
legacy CMaps (split by codespace → code → CID by binary search over the
interned ranges → CID → Unicode by bitmap rank), and cid_to_unicode maps a
CID through the /CIDSystemInfo collection for the Identity-H/V and
embedded-CMap-stream cases (code == CID, collection from the descendant
CIDFont). Font::to_unicode wires both in, guarding the identity path so a
named CMap we lack tables for is never misread as identity CIDs.

Codespace splitting matches per byte (ISO 32000-1 9.7.6.2), not just the
leading byte, so GB18030's overlapping 2- and 4-byte ranges (both leading
0x81-0xFE) pick the right width — e.g. GBK2K-H `81 30 84 36` → CID 22354
→ U+00A5 rather than two mis-split 2-byte codes.

Generated arrays are std::array, and the exported definitions are
qualified (cid_data::…) in the parent namespace so a header rename fails to
compile rather than defining a stray symbol — matching pdf_encoding_data.

Tests (assertion-based, vectors derived from the Adobe data and frozen
inline): Shift-JIS and EUC code → CID → Unicode, a mixed 1-/2-byte
codespace, the GB18030 four-byte case, cid_to_unicode by collection incl.
an astral escape, and the nullopt for Identity-H / an unshipped name.

A visual walkthrough of the path and tables is added at
docs/design/pdf-cjk-code-cid-unicode.html.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@andiwand andiwand force-pushed the pdf-cjk-legacy-cmaps branch from dabc445 to a9f6723 Compare July 5, 2026 11:07
Both collections ship a Uni* CMap, so they invert to CID -> Unicode tables
like the existing five; add them to generate_cid_data.py's _COLLECTIONS and
regenerate (7 collections, 136 predefined CMaps, ~+54 KB payload). Hojo also
brings legacy code -> CID CMaps (Hojo-H/V, Hojo-EUC-H/V). Adobe-Identity-0
stays out by necessity: it has no Uni* CMap to invert.

No runtime changes: lookups are by name / (registry, ordering) against the
generated tables. Adds pdf_cid tests for the Hojo legacy path and the
Manga1/Japan2 collection lookups. Drops the now-resolved "CMap coverage" gap
from AGENTS.md.

Also tidies the generator (drop the always-true std_array flag) and
Font::to_unicode (use !result.empty() instead of a separate mapped flag).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@andiwand andiwand enabled auto-merge (squash) July 5, 2026 11:44
@andiwand andiwand disabled auto-merge July 5, 2026 11:44
@andiwand andiwand merged commit f4674de into main Jul 5, 2026
9 checks passed
@andiwand andiwand deleted the pdf-cjk-legacy-cmaps branch July 5, 2026 11:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant