- Created comprehensive documentation at notes/bf-e4uvb-child-3-cmap-flow.md - Documented 4-level encoding resolution chain (ToUnicode → AGL → Fingerprint → Shape) - Identified key data structures: ToUnicodeMap, ResolvedGlyph, Font, ResolverCache - Documented CMAP entry creation mechanism at cmap.rs:82-88 (MARKER at line 85) - Explained glyph name processing: CMAP processes byte sequences, AGL processes glyph names - Documented UTF-16BE decoding and ligature expansion support - Listed all code locations with line numbers for easy reference - Covered confidence hierarchy: 1.0 (ToUnicode) → 0.9 (AGL) → 0.85 (Fingerprint) → 0.7 (Shape) Acceptance criteria met: ✓ Data flow documented: which glyph names are processed ✓ Entry addition mechanism documented ✓ Key data structures identified ✓ Documentation saved to notes/bf-e4uvb-child-3-cmap-flow.md Closes bf-3ztvp
330 lines
12 KiB
Markdown
330 lines
12 KiB
Markdown
# CMAP Data Flow and Glyph Processing Documentation
|
||
|
||
## Overview
|
||
|
||
This document describes how CMAP (Character Map) processes glyph names and creates encoding entries in the pdftract codebase. The CMAP system is a critical component of the 4-level encoding resolution chain for text extraction from PDFs.
|
||
|
||
## Data Flow Architecture
|
||
|
||
### Level 1: ToUnicode CMap (Confidence 1.0)
|
||
|
||
The primary and most reliable source for glyph-to-Unicode mapping is the `/ToUnicode` CMap stream embedded in PDF fonts.
|
||
|
||
#### Entry Creation Flow
|
||
|
||
1. **CMap Parsing** (`crates/pdftract-core/src/font/cmap.rs:86-88`)
|
||
- **Entry Point**: `ToUnicodeMap::add_mapping()` method
|
||
- **MARKER**: Line 85-88 explicitly marks this as the CMAP entry creation point
|
||
- Called by:
|
||
- `parse_beginbfchar()` (single character mappings)
|
||
- `parse_beginbfrange()` (range mappings)
|
||
|
||
2. **Data Structure**: `ToUnicodeMap` (`cmap.rs:67-71`)
|
||
```rust
|
||
pub struct ToUnicodeMap {
|
||
/// Mapping from source byte sequence to destination Unicode codepoints.
|
||
/// Uses Vec<u8> as key (source bytes) and Vec<char> as value (destination chars).
|
||
mappings: HashMap<Vec<u8>, Vec<char>>,
|
||
}
|
||
```
|
||
|
||
3. **Mapping Types Processed**:
|
||
|
||
a. **Single Character Mappings** (`beginbfchar...endbfchar` blocks)
|
||
- Format: `beginbfchar <count> <src1> <dst1> <src2> <dst2> ... endbfchar`
|
||
- Example: `beginbfchar 1 <00> <0041> endbfchar` maps byte `0x00` to 'A'
|
||
- Supports ligature expansion: `<00660069>` → `['f', 'i']` (two codepoints)
|
||
|
||
b. **Range Mappings** (`beginbfrange...endbfrange` blocks)
|
||
- **Contiguous form**: `<lo> <hi> <dst>` - maps range with incrementing destination
|
||
- Example: `beginbfrange 1 <0041> <005A> <0041> endbfrange` maps A-Z to U+0041..U+005A
|
||
- **Explicit array form**: `<lo> <hi> [<d0> <d1> ...]` - each destination explicitly specified
|
||
- Example: `beginbfrange 1 <0001> <0003> [<FB01> <FB02> <FB03>] endbfrange`
|
||
- Maps codes 1,2,3 to ligatures fi, fl, ffi
|
||
|
||
4. **Glyph Names Processed**:
|
||
- CMap does NOT process glyph names directly
|
||
- It processes **raw byte sequences** (character codes) as keys
|
||
- Source byte sequences can be 1-4 bytes (variable-length)
|
||
- Destination is always Unicode codepoints (UTF-16BE decoded)
|
||
|
||
5. **UTF-16BE Decoding** (`cmap.rs:329-367`)
|
||
- Hex strings in CMap are UTF-16BE encoded
|
||
- Decoded to `Vec<char>` supporting surrogate pairs and multi-codepoint results
|
||
- Unpaired surrogates produce replacement character (U+FFFD)
|
||
- Odd byte counts emit diagnostic but continue processing
|
||
|
||
#### Lookup Flow
|
||
|
||
1. **Resolution Entry** (`resolver.rs:302-377`)
|
||
- `resolve_unicode()` is the main entry point
|
||
- Checks per-font cache first (thread-safe LRU)
|
||
- Calls `resolve_level1()` for ToUnicode lookup
|
||
|
||
2. **Level 1 Lookup** (`resolver.rs:383-399`)
|
||
```rust
|
||
fn resolve_level1(char_code: &[u8], to_unicode: Option<&ToUnicodeMap>) -> ResolvedGlyph
|
||
```
|
||
- Returns failure if ToUnicode CMap is missing
|
||
- Returns failure if character code not in map
|
||
- Returns failure if result is empty or U+FFFD only
|
||
- Success: Returns `ResolvedGlyph` with chars, source=ToUnicode, confidence=1.0
|
||
|
||
### Level 2: Named Encoding + AGL (Confidence 0.9)
|
||
|
||
When ToUnicode is absent or fails, the system falls back to named encoding + AGL lookup.
|
||
|
||
#### Encoding Processing
|
||
|
||
1. **Named Encodings** (`encoding.rs:22-60`)
|
||
- 6 standard encodings: WinAnsi, MacRoman, MacExpert, Standard, Symbol, ZapfDingbats
|
||
- Map character codes (0-255) to glyph names
|
||
- WinAnsiEncoding is most common (Windows-1252 superset)
|
||
|
||
2. **Differences Overlay** (`encoding.rs:129-150`)
|
||
- Sparse overrides from `/Differences` array
|
||
- Format: `[n /Name1 /Name2 ... m /OtherName ...]`
|
||
- Each integer resets position, subsequent names assign to consecutive codes
|
||
- Example: `[39 /quotesingle 96 /grave]` assigns code 39→"quotesingle", 96→"grave"
|
||
|
||
#### AGL Lookup (Glyph Name Processing)
|
||
|
||
1. **Adobe Glyph List** (`agl.rs:41-60`)
|
||
- Compile-time phf::Map of ~4,400 glyph name → Unicode mappings
|
||
- Generated from Adobe's glyphlist.txt at build time
|
||
|
||
2. **Algorithmic Patterns** (`agl.rs:96-100`)
|
||
- `uniXXXX` (4 hex digits) → Unicode codepoint
|
||
- `uXXXXXX` (up to 6 hex digits) → Unicode codepoint
|
||
- Example: `uni20AC` → U+20AC (Euro sign)
|
||
|
||
3. **Variant Suffix Stripping** (`agl.rs:47-56`)
|
||
- Strips suffixes like `.sc`, `.alt`, `.oldstyle` before lookup
|
||
- Example: `A.sc` → `A` → U+0041
|
||
|
||
4. **Ligature Support** (`agl.rs:79-94`)
|
||
- `unicode_for_glyph_name_multi()` handles multi-codepoint glyphs
|
||
- Example: `fi` → `['f', 'i']`
|
||
|
||
#### Level 2 Resolution
|
||
|
||
- Character code → Encoding table → Glyph name → AGL lookup → Unicode
|
||
- Returns `ResolvedGlyph` with source=Agl, confidence=0.9
|
||
|
||
### Level 3: Font Fingerprint Cache (Confidence 0.85)
|
||
|
||
When both ToUnicode and AGL fail, the system uses font fingerprinting.
|
||
|
||
#### Fingerprint Processing (`fingerprint.rs`)
|
||
|
||
1. **Hash Computation**
|
||
- SHA-256 hash of decoded font program bytes
|
||
- Computed over embedded font program (post-stream decode)
|
||
- Stable across different stream filters
|
||
|
||
2. **Lookup Database**
|
||
- Compile-time phf::Map: `[u8; 32]` → `&'static [(u16, u32)]`
|
||
- Maps glyph ID (GID) to Unicode codepoint
|
||
- Generated from `build/font-fingerprints.json`
|
||
|
||
3. **Fallback Trigger**
|
||
- Used when:
|
||
- `/ToUnicode` is missing or empty
|
||
- Embedded font subset has stripped glyph names
|
||
- Font binary matches a known fingerprint
|
||
|
||
### Level 4: Shape Recognition (Confidence 0.7, cfg-gated)
|
||
|
||
Final fallback: visual glyph shape matching.
|
||
|
||
#### Shape Processing (`shape.rs`)
|
||
|
||
1. **Rasterization**
|
||
- Type 3 glyphs: Execute content stream to 32×32 bitmap
|
||
- Embedded fonts: Extract glyph outline, render to bitmap
|
||
|
||
2. **Perceptual Hashing**
|
||
- Compute pHash of bitmap
|
||
- Look up in shape database (compile-time phf::Map)
|
||
|
||
3. **Feature-Gated**
|
||
- Only available when `shape-db` feature is enabled
|
||
- Requires `build/glyph-shapes.json` at compile time
|
||
|
||
## Key Data Structures
|
||
|
||
### 1. `ToUnicodeMap` (`cmap.rs:67-71`)
|
||
```rust
|
||
pub struct ToUnicodeMap {
|
||
mappings: HashMap<Vec<u8>, Vec<char>>,
|
||
}
|
||
```
|
||
- **Purpose**: Stores character code → Unicode mappings from `/ToUnicode` stream
|
||
- **Key**: Variable-length byte sequence (1-4 bytes)
|
||
- **Value**: Unicode codepoints (1-4 chars for ligatures)
|
||
- **Entry point**: `add_mapping()` at line 86
|
||
|
||
### 2. `ResolvedGlyph` (`resolver.rs:168-175`)
|
||
```rust
|
||
pub struct ResolvedGlyph {
|
||
pub chars: SmallVec<[char; 4]>,
|
||
pub source: UnicodeSource,
|
||
pub confidence: f32,
|
||
}
|
||
```
|
||
- **Purpose**: Result of encoding resolution at any level
|
||
- **chars**: 1-4 Unicode characters (supports ligature expansion)
|
||
- **source**: Which level produced the mapping (ToUnicode, Agl, Fingerprint, ShapeMatch, Unknown)
|
||
- **confidence**: Always 1.0, 0.9, 0.85, or 0.7 per INV-30
|
||
|
||
### 3. `FontEncoding` (`encoding.rs`)
|
||
```rust
|
||
pub enum FontEncoding {
|
||
Named(NamedEncoding),
|
||
Custom(DifferencesOverlay),
|
||
}
|
||
```
|
||
- **Purpose**: Level 2 character code → glyph name mapping
|
||
- **Named**: One of 6 standard encodings (WinAnsi, MacRoman, etc.)
|
||
- **Custom**: Base encoding + Differences array overlay
|
||
|
||
### 4. `ResolverCache` (`resolver.rs:223-278`)
|
||
```rust
|
||
pub struct ResolverCache {
|
||
cache: DashMap<CacheKey, ResolvedGlyph>,
|
||
emitted_misses: DashMap<(FontId, SmallVec<[u8; 4]>), ()>,
|
||
}
|
||
```
|
||
- **Purpose**: Per-font LRU cache for resolved glyphs
|
||
- **Thread-safe**: Uses DashMap for concurrent access
|
||
- **Miss tracking**: Ensures GLYPH_UNMAPPED diagnostic emitted once per (font, code)
|
||
|
||
### 5. `Font` (`resolver.rs:33-46`)
|
||
```rust
|
||
pub struct Font {
|
||
id: FontId,
|
||
to_unicode: Option<ToUnicodeMap>,
|
||
encoding: Option<FontEncoding>,
|
||
fingerprint: Option<CachedFingerprint>,
|
||
has_embedded_program: bool,
|
||
cache: ResolverCache,
|
||
}
|
||
```
|
||
- **Purpose**: Encapsulates all data for 4-level fallback chain
|
||
- **to_unicode**: Level 1 CMap (highest priority)
|
||
- **encoding**: Level 2 named encoding + AGL
|
||
- **fingerprint**: Level 3 font fingerprint cache
|
||
- **has_embedded_program**: Skip Level 3 if false (Type 3 fonts)
|
||
|
||
## Entry Addition Mechanism Details
|
||
|
||
### CMAP Entry Creation (`cmap.rs:82-88`)
|
||
|
||
```rust
|
||
pub fn add_mapping(&mut self, src: Vec<u8>, dst: Vec<char>) {
|
||
self.mappings.insert(src, dst);
|
||
}
|
||
```
|
||
|
||
**Call Sites**:
|
||
|
||
1. **From `parse_beginbfchar()` (line 209)**
|
||
- Processes single-character mappings
|
||
- Each `beginbfchar` block produces N calls to `add_mapping()`
|
||
- Source: hex string from CMap
|
||
- Destination: UTF-16BE decoded to `Vec<char>`
|
||
|
||
2. **From `parse_beginbfrange()` (lines 279, 292)**
|
||
- Processes range mappings
|
||
- **Contiguous form**: Calls `add_mapping()` in loop, incrementing both source and destination
|
||
- **Explicit array form**: Calls `add_mapping()` once per array entry
|
||
- Range expansion can produce hundreds of entries
|
||
|
||
### Encoding Entry Creation
|
||
|
||
**Named Encodings** (`encoding.rs`):
|
||
- Compile-time generated from JSON files in `build/named-encodings.json`
|
||
- Each encoding is a 256-element array: `[Option<&str>; 256]`
|
||
- Loaded at build time via `build.rs`
|
||
|
||
**Differences Overlay** (`encoding.rs:144-183`):
|
||
- Parsed at runtime from `/Differences` array
|
||
- Sparse vector of `(u8, Arc<str>)` pairs
|
||
- Created during `FontEncoding::parse_from_font()`
|
||
|
||
## Code Locations Reference
|
||
|
||
### CMAP Processing
|
||
- **ToUnicode parsing**: `crates/pdftract-core/src/font/cmap.rs:114-505`
|
||
- **Entry creation**: `cmap.rs:82-88` (MARKER at line 85)
|
||
- **bfchar parsing**: `cmap.rs:190-216`
|
||
- **bfrange parsing**: `cmap.rs:223-309`
|
||
- **UTF-16BE decoding**: `cmap.rs:329-367`
|
||
|
||
### Resolution Chain
|
||
- **Main resolver**: `crates/pdftract-core/src/font/resolver.rs:302-377`
|
||
- **Level 1 (ToUnicode)**: `resolver.rs:383-399`
|
||
- **Level 2 (AGL)**: `resolver.rs:405-432`
|
||
- **Level 3 (Fingerprint)**: `resolver.rs:438-451`
|
||
- **Level 4 (Shape)**: `resolver.rs:457-470` (cfg-gated)
|
||
|
||
### Glyph Name Processing
|
||
- **AGL lookup**: `crates/pdftract-core/src/font/agl.rs:41-94`
|
||
- **Algorithmic patterns**: `agl.rs:96-141`
|
||
- **Named encodings**: `crates/pdftract-core/src/font/encoding.rs:22-110`
|
||
- **Differences overlay**: `encoding.rs:144-183`
|
||
|
||
### Data Structures
|
||
- **ToUnicodeMap**: `cmap.rs:67-112`
|
||
- **ResolvedGlyph**: `resolver.rs:168-197`
|
||
- **Font**: `resolver.rs:33-104`
|
||
- **ResolverCache**: `resolver.rs:223-284`
|
||
- **FontEncoding**: `encoding.rs` (module)
|
||
|
||
## Glyph Name Summary
|
||
|
||
### Processed by CMAP
|
||
- **None directly** - CMAP processes byte sequences, not glyph names
|
||
|
||
### Processed by AGL (Level 2)
|
||
- Standard glyph names: `A`, `B`, `quotesingle`, `quotedblleft`, etc.
|
||
- Algorithmic patterns: `uniXXXX`, `uXXXXXX`
|
||
- Ligatures: `fi`, `fl`, `ffi`, `ffl`
|
||
- Variant suffixes: `A.sc`, `A.alt`, `one.oldstyle`
|
||
- Symbol font: `alpha`, `beta`, `gamma`, etc.
|
||
- ZapfDingbats: `a1`, `a2`, ..., `a202`
|
||
|
||
### Total Coverage
|
||
- AGL: ~4,400 entries (glyphlist.txt)
|
||
- AGL Multi: ~770 entries (aglfn.txt for ligatures)
|
||
- Algorithmic patterns: Unlimited (any valid Unicode codepoint)
|
||
- Named encodings: 6 × 256 = 1,536 potential mappings
|
||
|
||
## Confidence Hierarchy
|
||
|
||
Per INV-30, confidence is always one of {1.0, 0.9, 0.85, 0.7, 0.0}:
|
||
|
||
1. **1.0** - ToUnicode CMap (authoritative source)
|
||
2. **0.9** - Named encoding + AGL (reliable but indirect)
|
||
3. **0.85** - Font fingerprint (content-based, assumes standard fonts)
|
||
4. **0.7** - Shape recognition (visual similarity, error-prone)
|
||
5. **0.0** - Unknown (U+FFFD replacement character)
|
||
|
||
## Related Documentation
|
||
|
||
- **CMAP entry creation**: `notes/bf-e4uvb-child-1.md` (if exists)
|
||
- **ToUnicode entry creation code locations**: Commit `155e00ab` - `docs(bf-4hwap)`
|
||
- **4-level fallback chain**: `docs/plan/plan.md` (INV-30, EC-NN references)
|
||
|
||
## Testing References
|
||
|
||
- **CMap parsing tests**: `crates/pdftract-core/src/font/cmap.rs:507-732`
|
||
- **Resolver tests**: `crates/pdftract-core/src/font/resolver.rs` (mod tests)
|
||
- **AGL tests**: `crates/pdftract-core/src/font/agl.rs` (if present)
|
||
|
||
---
|
||
|
||
**Document Version**: 1.0
|
||
**Last Updated**: 2026-07-05
|
||
**Related Bead**: bf-3ztvp
|
||
**Plan References**: (See plan.md for related EC-NN and INV-30 tags)
|