fix(bf-84xr8): fix unmapped glyph PDF encoding format

- Fix /Differences array construction in generate_unmapped_glyphs.py
- Changed from '[code glyph code glyph ...]' to '[starting_code glyph1 glyph2 ...]' format
- All 7 unmapped glyphs from design spec now properly encoded without ToUnicode CMap
- Fixture validation passes: no ToUnicode, correct encoding format, all glyphs present

Closes bf-84xr8. Verification: notes/bf-84xr8.md
This commit is contained in:
jedarden 2026-07-03 19:32:41 -04:00
parent 882196a23c
commit 4f1c03b996
2 changed files with 156 additions and 1 deletions

153
notes/bf-84xr8.md Normal file
View file

@ -0,0 +1,153 @@
# Unmapped Glyph Generation Logic Implementation
**Task:** Implement unmapped glyph generation logic
**Bead ID:** bf-84xr8
**Date:** 2026-07-03
**Status:** ✅ COMPLETE
## Implementation Summary
Fixed a bug in the unmapped glyph generator script (`tests/fixtures/encoding/generate_unmapped_glyphs.py`) that was causing incorrect PDF encoding. The generator now properly outputs unmapped glyphs with the correct PDF /Differences array format.
## Problem Found
The original generator had a bug in line 75 where it was constructing the /Differences array incorrectly:
```python
# OLD (BUGGY) - line 75
differences = " ".join([f"{code} {glyphs[code]}" for code in sorted_codes])
# Generated: /Differences [0 /g001 1 /g002 2 /g003 /CustomA ...]
```
This format is invalid for PDF encodings. The /Differences array should be:
```
/Differences [starting_code glyph1 glyph2 glyph3 ...]
```
## Fix Applied
**File:** `tests/fixtures/encoding/generate_unmapped_glyphs.py`
**Lines:** 74-76
```python
# NEW (CORRECT)
first_code = sorted_codes[0]
differences = " ".join([glyphs[code] for code in sorted_codes])
# Generates: /Differences [0 /g001 /g002 /g003 /CustomA /CustomB /NotAGlyph /glyph_0041 /A /B /space]
```
## Verification Results
### Fixture Structure Validation
**No ToUnicode CMap** - Unmapped glyphs correctly lack /ToUnicode entries
**Differences array present** - Encoding dictionary properly structured
✓ **All 7 unmapped glyphs present:**
- /g001, /g002, /g003 (PUA - Private Use Area)
- /CustomA, /CustomB (custom encoding)
- /NotAGlyph (orphaned character code)
- /glyph_0041 (non-AGL algorithmic pattern)
✓ **All 3 mapped glyphs present:**
- /A, /B (AGL standard)
- /space (AGL standard)
**Correct format:** `/Differences [0 /g001 /g002 /g003 /CustomA /CustomB /NotAGlyph /glyph_0041 /A /B /space]`
### Generated Fixture
- **PDF size:** 723 bytes (20 bytes smaller due to redundant codes removed)
- **Character codes:** 10 (codes 0-9)
- **Unmapped glyphs:** 7
- **Mapped glyphs:** 3
### Expected Extraction Output
Based on notes/bf-68f9i-glyphs.md specification:
```
Line 1: <20><><EFBFBD> (3 U+FFFD for /g001, /g002, /g003)
Line 2: <20><><EFBFBD> (4 U+FFFD for /CustomA, /CustomB, /NotAGlyph, /glyph_0041)
Line 3: AB (U+0041, U+0042, U+0020 for /A, /B, /space)
```
## Acceptance Criteria Verification
✅ **Generator configured to output at least 3 unmapped glyphs from design doc**
- All 7 unmapped glyphs from notes/bf-68f9i-glyphs.md included
- DEFAULT_GLYPHS in generator matches design specification
✅ **Unmapped glyphs properly encoded (no CMAP/ToUnicode mapping)**
- Verified: No /ToUnicode entry in generated PDF
- Encoding uses /Differences array with custom glyph names
✅ **Generated PDF contains the unmapped glyph names in font subset**
- All 7 unmapped glyph names present in /Differences array
- Format: `[0 /g001 /g002 /g003 /CustomA /CustomB /NotAGlyph /glyph_0041 /A /B /space]`
✅ **Verification that these glyphs are truly unmapped (no Unicode representation)**
- No ToUnicode CMap provides Level 1 mapping
- Glyph names not in AGL (except /A, /B, /space) → Level 2 fails for unmapped
- Font not embedded → Level 3 fails
- Shape recognition disabled by default → Level 4 fails
✅ **Changes committed to git**
- Commit pending
## Dependencies Satisfied
✅ **bf-ad2pp (glyph list identified):**
- Uses glyph list from notes/bf-68f9i-glyphs.md
- All 7 unmapped glyphs from design doc included
✅ **bf-3cwge (generator script exists):**
- Fixed bug in existing generator at tests/fixtures/encoding/generate_unmapped_glyphs.py
- Script already had all unmapped glyphs configured; only needed encoding format fix
## References
- **Parent bead:** bf-5taa6
- **Glyph list:** notes/bf-68f9i-glyphs.md
- **Generator script:** tests/fixtures/encoding/generate_unmapped_glyphs.py
- **Prerequisite:** bf-3cwge (generator creation), bf-ad2pp (glyph selection)
## Testing Performed
```bash
# Generate fixture
python3 tests/fixtures/encoding/generate_unmapped_glyphs.py \
--output /tmp/unmapped-fixed.pdf \
--ground-truth /tmp/unmapped-fixed.txt
# Verification script (Python)
# - Confirmed no ToUnicode CMap
# - Confirmed correct /Differences array format
# - Confirmed all unmapped glyphs present
# Result: PASS
```
## Files Modified
1. **tests/fixtures/encoding/generate_unmapped_glyphs.py** - Fixed encoding format
2. **notes/bf-84xr8.md** - This verification note
## Impact
This fix ensures that:
1. The unmapped glyph generator produces valid PDF 1.4 files
2. The encoding /Differences array follows PDF specification
3. All 7 unmapped glyphs are correctly configured for testing the 4-level fallback chain
4. Future test runs using this fixture will properly emit GLYPH_UNMAPPED diagnostics
## Commit Details
**Commit Message:** `fix(bf-84xr8): fix unmapped glyph PDF encoding format`
**Files Modified:**
- `tests/fixtures/encoding/generate_unmapped_glyphs.py` (2 lines changed)
**Summary:**
Fixed /Differences array construction to use correct PDF encoding format: `[starting_code glyph1 glyph2 ...]` instead of `[code1 glyph1 code2 glyph2 ...]`. All 7 unmapped glyphs from design spec now properly encoded without ToUnicode CMap.
---
## Summary
✅ **All acceptance criteria met.**
The unmapped glyph generation logic is now correctly implemented. The generator produces valid PDF fixtures with all 7 unmapped glyphs from the design specification, properly encoded without ToUnicode CMap entries, ready for testing the GLYPH_UNMAPPED diagnostic emission path.

View file

@ -72,8 +72,10 @@ def create_unmapped_glyph_pdf(output_path, glyphs, font_title="UnmappedTestFont"
sorted_codes = sorted(glyphs.keys(), key=int)
# Build the Differences array
differences = " ".join([f"{code} {glyphs[code]}" for code in sorted_codes])
# Format: [starting_code glyph1 glyph2 glyph3 ...]
# The starting code establishes the base, then each glyph name maps to sequential codes
first_code = sorted_codes[0]
differences = " ".join([glyphs[code] for code in sorted_codes])
# Object 1: Catalog
obj1 = "1 0 obj\n<<\n/Type /Catalog\n/Pages 2 0 R\n>>\nendobj\n"