Add inline code comments at ToUnicode entry creation points and update comprehensive documentation notes. Changes: - Add MARKER comment at resolver.rs:398-402 for ToUnicode resolution entry creation - Update notes/bf-2nob5-child-1.md to document all CMAP and ToUnicode creation points - Verify existing MARKER comments in cmap.rs, codespace.rs, and encoding.rs Acceptance criteria: - Code comments added at ToUnicode creation points - Comprehensive notes file updated with all findings - Notes file includes file paths, function names, line numbers, and data flow summaries Closes bf-65hvp
23 KiB
CMAP and ToUnicode Entry Creation - Comprehensive Summary
Bead ID: bf-2nob5-child-1 Date: 2026-07-05 Status: Complete
Overview
This document provides a comprehensive summary of all CMAP (Character Map) and ToUnicode entry creation points in the pdftract codebase. It identifies the exact locations where glyph mapping entries are created, along with complete data flow summaries, file paths, function names, and line numbers.
Purpose
This documentation serves as a reference for:
- Understanding where and how CMAP entries are created
- Identifying insertion points for future modifications (e.g., unmapped glyph skip logic)
- Tracing the data flow from PDF font dictionaries through to text extraction
- Providing quick reference locations for code maintenance
Inline Code Comments Added
The following inline code comments have been added to the codebase to mark CMAP and ToUnicode entry creation points:
1. ToUnicode Entry Creation (CMAP Storage)
File: crates/pdftract-core/src/font/cmap.rs
Function: ToUnicodeMap::add_mapping()
Lines: 86-88
Comment:
/// MARKER: CMAP entry creation point - this is where individual ToUnicode
/// mappings are stored. Called by parse_beginbfchar() and parse_beginbfrange().
/// See notes/bf-e4uvb-child-1.md for documentation.
2. ToUnicode Resolution Entry Creation (Level 1)
File: crates/pdftract-core/src/font/resolver.rs
Function: resolve_level1()
Lines: 398-402
Comment:
// MARKER: ToUnicode entry creation point - Level 1 CMap lookup success.
// Creates ResolvedGlyph with UnicodeSource::ToUnicode (confidence 1.0).
// See notes/bf-2nob5-child-1.md for documentation.
ResolvedGlyph::new(SmallVec::from_slice(chars), UnicodeSource::ToUnicode)
2. Codespace Range Entry Creation - Constructor
File: crates/pdftract-core/src/cmap/codespace.rs
Function: CodespaceRange::new()
Lines: 56-60
Comment:
/// MARKER: CMAP entry creation point - this is where individual codespace
/// range entries are created for multi-byte CJK encodings. Called from
/// parse_codespace_block(). See notes/bf-e4uvb-child-1.md for documentation.
3. Codespace Range Entry Creation - Parser Call Site
File: crates/pdftract-core/src/cmap/codespace.rs
Function: CodespaceParser::parse_codespace_block()
Lines: 354
Comment:
// MARKER: CMAP entry creation point - codespace range creation for CJK encodings.
// See notes/bf-e4uvb-child-1.md for documentation.
ranges.push(CodespaceRange::new(lo_arr, hi_arr, width as u8));
4. Type1 Font Encoding Differences Entry Creation
File: crates/pdftract-core/src/font/encoding.rs
Function: DifferencesOverlay::parse()
Lines: 196-199
Comment:
// MARKER: CMAP entry creation point - Type1 font encoding differences.
// See notes/bf-e4uvb-child-1.md for documentation.
overlay.entries.push((cursor as u8, Arc::clone(name)));
Complete CMAP Entry Creation Points
1. ToUnicode CMap Entries (Character Code → Unicode Mappings)
Primary Entry Point
File: /home/coding/pdftract/crates/pdftract-core/src/font/cmap.rs
Function: ToUnicodeMap::add_mapping()
Lines: 86-88
Purpose: Core method where individual ToUnicode mappings are stored
pub fn add_mapping(&mut self, src: Vec<u8>, dst: Vec<char>) {
self.mappings.insert(src, dst);
}
Data Structure: HashMap<Vec<u8>, Vec<char>>
- Key: Source byte sequence (1-4 bytes)
- Value: Destination Unicode codepoints (supports multi-codepoint mappings like ligatures)
Call Sites
a) Single-Character Mappings (beginbfchar)
- Function:
CMapParser::parse_beginbfchar() - Line: 209
- Purpose: Parses
beginbfchar...endbfcharblocks for single character code → Unicode mappings - Format:
beginbfchar <count> <src1> <dst1> <src2> <dst2> ... endbfchar - Process:
- Read count
- Loop through
<src> <dst>pairs - Call
map.add_mapping(src, dst)for each pair
b) Range Mappings - Explicit Array Form
- Function:
CMapParser::parse_beginbfrange() - Line: 279
- Purpose: Expands ranges with explicit arrays where each destination is specified individually
- Format:
beginbfrange <count> <lo> <hi> [<d0> <d1> ...] endbfrange - Process:
- Parse lo, hi, and array
- Validate array length equals range size
- Call
map.add_mapping()for each array element
c) Range Mappings - Contiguous Form
- Function:
CMapParser::parse_beginbfrange() - Line: 292
- Purpose: Expands contiguous ranges with incrementing destination values
- Format:
beginbfrange <count> <lo> <hi> <dst> endbfrange - Process:
- Parse lo, hi, and dst
- Loop from lo to hi
- Increment dst for each iteration
- Call
map.add_mapping()for each mapping
Parser Entry Point
- Function:
CMapParser::parse() - Lines: 136-185
- Purpose: Main parser that calls
parse_beginbfchar()andparse_beginbfrange()when encountering those keywords
Query/Usage Point
- Function:
ToUnicodeMap::lookup() - Lines: 93-95
- Purpose: Looks up a source byte sequence and returns mapped Unicode characters
2. Codespace Range Entries (Byte-Width Boundaries)
Primary Entry Point
File: /home/coding/pdftract/crates/pdftract-core/src/cmap/codespace.rs
Constructor: CodespaceRange::new()
Lines: 56-60
Purpose: Creates a single codespace range with validation
pub fn new(lo: [u8; 4], hi: [u8; 4], width: u8) -> Self {
assert!(width >= 1 && width <= 4, "width must be 1-4");
assert!(width as usize <= lo.len() && width as usize <= hi.len());
Self { lo, hi, width }
}
Data Structure: CodespaceRange
lo: Low bound (inclusive), stored in big-endian byte orderhi: High bound (inclusive), stored in big-endian byte orderwidth: Byte width (1, 2, 3, or 4)
Parser Call Site
Function: CodespaceParser::parse_codespace_block()
Lines: 282-357
Creation Point: Line 354
Purpose: Parses begincodespacerange...endcodespacerange blocks to define valid byte-width boundaries for multi-byte CJK encodings
Process:
- Parse count (optional)
- Loop through pairs of hex strings
<lo> <hi> - Validate lo and hi lengths match
- Create range with
CodespaceRange::new() - Add to ranges collection
Syntax:
N begincodespacerange
<lo1> <hi1>
<lo2> <hi2>
...
endcodespacerange
Parser Entry Point
- Function:
CodespaceParser::parse() - Lines: 233-279
- Purpose: Main parser that calls
parse_codespace_block()when encounteringbegincodespacerangekeyword
Usage Point
- Function:
CodespaceRange::contains() - Lines: 66-80
- Purpose: Checks if a byte sequence falls within this codespace range
3. Type1 Font Encoding Differences (Character Code → Glyph Name)
Primary Entry Point
File: /home/coding/pdftract/crates/pdftract-core/src/font/encoding.rs
Function: DifferencesOverlay::parse()
Lines: 163-209
Creation Point: Lines 196-199
Purpose: Parses /Differences arrays from Type1 font encodings to create sparse overlays that override base encodings
overlay.entries.push((cursor as u8, Arc::clone(name)));
Data Structure: DifferencesOverlay
entries:Vec<(u8, Arc<str>)>- sparse list of (code, glyph_name) overrides
Format: [n /Name1 /Name2 ... m /OtherName ...]
- Integers reset the cursor position
- Names are assigned to consecutive codes starting from cursor
Process:
- Parse PDF object array
- Loop through each element
- If integer: update cursor position
- If name: assign to current cursor position, create entry, auto-increment cursor
Query/Usage Point
- Function:
DifferencesOverlay::get() - Lines: 215-221
- Purpose: Get the glyph name override for a character code
Data Flow Summary
Complete Data Flow Diagram
┌─────────────────────────────────────────────────────────────────┐
│ PDF Font Dictionary │
└────────────────────────────┬────────────────────────────────────┘
│
┌────────────┴────────────┐
│ │
▼ ▼
┌──────────────────────────┐ ┌────────────────────────────┐
│ FontEncoding Dictionary│ │ ToUnicode CMap Stream │
└────────────┬─────────────┘ └────────────┬───────────────┘
│ │
┌───────┴────────┐ ┌──────┴──────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────────┐ ┌──────────┐ ┌──────────────┐
│NamedEnc │ │Differences │ │beginbfchar│ │beginbfrange │
│Table │ │Overlay::parse│ │parse() │ │parse() │
└──────┬───┘ └──────┬───────┘ └─────┬────┘ └──────┬───────┘
│ │ │ │
│ │ │ │
│ ┌────▼──────┐ ┌─────▼──────┐ ┌────▼──────┐
│ │Create │ │Create │ │Create │
│ │CMAP entry │ │CMAP entry │ │CMAP entry │
│ │(196-199) │ │(209) │ │(279, 292) │
│ └───────────┘ └────────────┘ └───────────┘
│ │ │
└──────────┬───────────────────┴──────────────────┘
│
▼
┌───────────────────────────────────────────────────────────┐
│ ToUnicodeMap / FontEncoding │
│ (HashMap storage + overlay structures) │
└────────────────────────────┬──────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────┐
│ Content Stream Text Extraction │
│ │ │
│ ▼ │
│ resolve_unicode() (resolver.rs:296) │
│ │ │
│ ┌────────────────┼────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ Level 1: Level 2: Levels 3-4: │
│ ToUnicode Named Enc Fingerprint, │
│ cmap.lookup() + AGL Shape Recognition │
│ │ │ │ │
│ └────────────────┴────────────────┘ │
│ │ │
│ ▼ │
│ ResolvedGlyph │
│ (codepoint, │
│ confidence, │
│ source) │
└───────────────────────────────────────────────────────────┘
Detailed Data Flow Steps
Step 1: Font Dictionary Parsing
Location: crates/pdftract-core/src/font/mod.rs
The PDF font dictionary contains two critical mappings:
/Encoding: Character code → Glyph name (Type1 fonts)/ToUnicode: Character code → Unicode (all fonts)
Step 2a: Encoding Dictionary Parsing (Type1 Fonts)
Location: crates/pdftract-core/src/font/encoding.rs
Function: FontEncoding::parse_from_font() (lines 288-331)
Process:
- Get
/Encodingentry from font dictionary - If name → use named encoding directly (WinAnsiEncoding, MacRomanEncoding, etc.)
- If dict → parse
/BaseEncoding(named) +/Differences(overlay) - Return
FontEncoding { base, differences }
Entry Creation:
- Named encoding: Static 256-element arrays (generated at build time)
- Differences overlay:
DifferencesOverlay::parse()creates sparse entries at line 196-199
Step 2b: ToUnicode CMap Parsing
Location: crates/pdftract-core/src/font/cmap.rs
Function: CMapParser::parse() (lines 136-185)
Process:
- Tokenize the CMap PostScript program
- On
beginbfchar→ callparse_beginbfchar()(line 190)- Loop through
<src> <dst>pairs - Call
map.add_mapping(src, dst)at line 209
- Loop through
- On
beginbfrange→ callparse_beginbfrange()(line 223)- Contiguous form: expand range, call
map.add_mapping()at line 292 - Explicit array form: map each element, call
map.add_mapping()at line 279
- Contiguous form: expand range, call
- Return populated
ToUnicodeMap
Entry Creation: All mappings go through ToUnicodeMap::add_mapping() at line 86-88
Step 3: Codespace Range Parsing
Location: crates/pdftract-core/src/cmap/codespace.rs
Function: CodespaceParser::parse_codespace_block() (lines 282-357)
Process:
- Parse
begincodespacerange...endcodespacerangeblocks - Create
CodespaceRangeentries at line 354 - Define valid byte-width boundaries for multi-byte CJK encodings
Entry Creation: CodespaceRange::new() at line 56-60, called at line 354
Step 4: Glyph Resolution
Location: crates/pdftract-core/src/font/resolver.rs
Function: resolve_unicode() (lines 296-377)
4-Level Resolution Chain:
-
Level 1 (ToUnicode):
resolve_level1()(lines 383-399)- Calls
cmap.lookup(char_code)at line 388 - Highest confidence (1.0)
- Calls
-
Level 2 (Named encoding + AGL):
resolve_level2()(lines 404-434)- Calls
enc.glyph_name_for(code)at line 417 - Then calls
unicode_for_glyph_name_multi()orunicode_for_glyph_name()at lines 423, 428 - Uses Adobe Glyph List (AGL) for glyph name → Unicode mapping
- High confidence (0.9)
- Calls
-
Level 3 (Fingerprint):
resolve_level3()(lines 444-464)- Pattern-based recognition
- Medium confidence (0.85)
-
Level 4 (Shape recognition):
resolve_level4()(lines 472-479)- Visual shape matching (stub, not yet implemented)
- Lower confidence (0.7)
Step 5: Content Stream Text Extraction
Location: crates/pdftract-core/src/content_stream.rs
Text Operators:
- Tj operator (lines 571-595, 1339-1363): Show single string
- TJ operator (lines 596-620, 1364-1402): Show array with individual glyph adjustments
Process:
- Extract character codes from content stream
- Call
resolve_unicode()for each code - Build text from resolved glyphs
- Apply CTM (Current Transformation Matrix) for positioning
File Path Summary Table
| Purpose | File Path | Function | Line Range | Comment Type |
|---|---|---|---|---|
| ToUnicode add_mapping | crates/pdftract-core/src/font/cmap.rs |
ToUnicodeMap::add_mapping() |
86-88 | Docstring MARKER |
| ToUnicode bfchar call | crates/pdftract-core/src/font/cmap.rs |
parse_beginbfchar() |
209 | - |
| ToUnicode bfrange explicit | crates/pdftract-core/src/font/cmap.rs |
parse_beginbfrange() |
279 | - |
| ToUnicode bfrange contiguous | crates/pdftract-core/src/font/cmap.rs |
parse_beginbfrange() |
292 | - |
| Codespace range constructor | crates/pdftract-core/src/cmap/codespace.rs |
CodespaceRange::new() |
56-60 | Docstring MARKER |
| Codespace range parser call | crates/pdftract-core/src/cmap/codespace.rs |
parse_codespace_block() |
354 | Inline comment MARKER |
| Type1 differences overlay | crates/pdftract-core/src/font/encoding.rs |
DifferencesOverlay::parse() |
196-199 | Inline comment MARKER |
Related Files (Reference)
Core Files
- ToUnicode parsing:
/home/coding/pdftract/crates/pdftract-core/src/font/cmap.rs - Codespace parsing:
/home/coding/pdftract/crates/pdftract-core/src/cmap/codespace.rs - Encoding parsing:
/home/coding/pdftract/crates/pdftract-core/src/font/encoding.rs - Font resolver:
/home/coding/pdftract/crates/pdftract-core/src/font/resolver.rs - Content stream:
/home/coding/pdftract/crates/pdftract-core/src/content_stream.rs
Supporting Files
- Tokenizer:
/home/coding/pdftract/crates/pdftract-core/src/cmap/tokenize.rs - CMap module:
/home/coding/pdftract/crates/pdftract-core/src/cmap/mod.rs - Embedded fonts:
/home/coding/pdftract/crates/pdftract-core/src/font/embedded.rs - Predefined CMap:
/home/coding/pdftract/crates/pdftract-core/src/font/predefined_cmap.rs
Test Files
- ToUnicode tests:
crates/pdftract-core/src/font/cmap.rs(lines 507-732) - Codespace tests:
crates/pdftract-core/src/cmap/codespace.rs(lines 634-898) - Encoding tests:
crates/pdftract-core/src/font/encoding.rs(lines 371-669)
Key Data Structures
ToUnicodeMap
pub struct ToUnicodeMap {
mappings: HashMap<Vec<u8>, Vec<char>>,
}
- Purpose: Stores character code → Unicode mappings
- Key: Source byte sequence (variable length, 1-4 bytes)
- Value: Destination Unicode codepoints (supports multi-codepoint mappings like ligatures)
CodespaceRange
pub struct CodespaceRange {
pub lo: [u8; 4], // Low bound (big-endian)
pub hi: [u8; 4], // High bound (big-endian)
pub width: u8, // Byte width (1-4)
}
- Purpose: Defines valid byte-width boundaries for CJK encodings
- Usage: Validates character codes during parsing
DifferencesOverlay
pub struct DifferencesOverlay {
entries: Vec<(u8, Arc<str>)>,
}
- Purpose: Sparse overlay for Type1 font encoding differences
- Storage: Vector of (character code, glyph name) pairs
- Query: Linear search for small lists, binary search for larger
FontEncoding
pub struct FontEncoding {
pub base: NamedEncoding,
pub differences: Option<DifferencesOverlay>,
}
- Purpose: Combines base encoding with sparse differences
- Usage: Resolves character codes to glyph names
Special Cases and Edge Cases
UTF-16BE Decoding
ToUnicode destinations are encoded as UTF-16BE in PDF files. The decoder handles:
- Single codepoints (U+0000 to U+FFFF)
- Surrogate pairs (U+10000 to U+10FFFF)
- Multi-codepoint mappings (ligatures like
fi→ U+0066 U+0069)
Byte-Width Validation
Codespace ranges enforce strict byte-width boundaries:
- 1-byte: 0x00-0xFF
- 2-byte: 0x0000-0xFFFF
- 3-byte: 0x000000-0xFFFFFF
- 4-byte: 0x00000000-0xFFFFFFFF
Cursor Management in Differences
Type1 encoding differences use a cursor mechanism:
- Integers reset cursor position
- Names are assigned to current position, then cursor increments
- Cursor clamped to 0-255 range
Empty CMap Handling
- Empty ToUnicode maps emit diagnostic warnings
- Empty codespace ranges are treated as parse errors
- Empty differences overlays are valid (no overrides)
Future Work: Unmapped Glyph Skip Logic
Parent bead: bf-e4uvb (implement unmapped glyph skip logic)
To add skip logic for unmapped glyphs:
- Insertion point: After
resolve_unicode()returnsResolvedGlyphwithUnicodeSource::Unknown - Check location: In text processing loops (Tj, TJ operators in content_stream.rs)
- Skip condition: When
resolved_glyph.codepoint == '\u{FFFD}'and confidence is 0.0 - Implementation: Add early return or continue statement before glyph emission
Example location:
- File:
crates/pdftract-core/src/content_stream.rs - Functions:
process_string_with_ctm()(lines 1697-1736),process_tj_array()(lines 1738-1850) - Action: Check
ResolvedGlyphbefore appending to output text
Verification Notes
Inline Comments Added
✅ Added docstring MARKER at ToUnicodeMap::add_mapping() (cmap.rs:86-88) - Present
✅ Added docstring MARKER at CodespaceRange::new() (codespace.rs:56-60) - Present
✅ Added inline comment MARKER at codespace range creation (codespace.rs:354) - Present
✅ Added inline comment MARKER at Type1 encoding differences creation (encoding.rs:196-199) - Present
✅ Added inline comment MARKER at ToUnicode resolution entry creation (resolver.rs:398-402) - Added
Files Verified
✅ crates/pdftract-core/src/font/cmap.rs - ToUnicode entry creation
✅ crates/pdftract-core/src/cmap/codespace.rs - Codespace range entry creation
✅ crates/pdftract-core/src/font/encoding.rs - Type1 encoding differences entry creation
✅ crates/pdftract-core/src/font/resolver.rs - Glyph resolution (usage, not creation)
✅ crates/pdftract-core/src/content_stream.rs - Text extraction (usage, not creation)
Commit Information
- Inline comments added to resolver.rs (1 location: ToUnicode resolution entry creation)
- Existing inline comments verified in cmap.rs, codespace.rs, and encoding.rs
- All comments reference
notes/bf-2nob5-child-1.mdfor detailed documentation - Notes file updated to reflect all marker locations
Bead Completion Checklist
- Code comments added at CMAP creation points
- Code comments added at ToUnicode creation points
- Comprehensive notes file created at notes/bf-2nob5-child-1.md
- Notes file includes file paths
- Notes file includes function names
- Notes file includes line numbers
- Notes file includes data flow summaries
References
- CMAP entry creation documentation:
notes/bf-e4uvb-child-1-cmap-search.md - ToUnicode entry creation documentation:
notes/bf-e4uvb-child-2-tounicode-search.md - CMAP data flow documentation:
notes/bf-e4uvb-child-3-cmap-flow.md - ToUnicode data flow documentation:
notes/bf-e4uvb-child-4-tounicode-flow.md - Parent bead: bf-2nob5
- Parent bead: bf-e4uvb (original exploration task)