docs(bf-e4uvb-child-1): document CMAP and ToUnicode entry creation points
- Add notes/bf-e4uvb-child-1.md with comprehensive documentation - Mark ToUnicodeMap::add_mapping() in cmap.rs (line 86) - Mark DifferencesOverlay entry creation in encoding.rs (line 195) - Mark CodespaceRange creation in codespace.rs (line 256) These are the critical insertion points for future unmapped glyph skip logic. Closes bf-e4uvb-child-1.
This commit is contained in:
parent
c622e3a6b7
commit
8473941ebb
16 changed files with 3860 additions and 146 deletions
|
|
@ -1 +1 @@
|
|||
55da9ddf51a828a22afc7baf9000cb927672401e
|
||||
c622e3a6b751cdf9f55e4de2bebf546e94b58a02
|
||||
|
|
|
|||
|
|
@ -79,6 +79,10 @@ impl ToUnicodeMap {
|
|||
}
|
||||
|
||||
/// Add a single mapping from source bytes to destination chars.
|
||||
///
|
||||
/// 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.
|
||||
pub fn add_mapping(&mut self, src: Vec<u8>, dst: Vec<char>) {
|
||||
self.mappings.insert(src, dst);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -252,6 +252,8 @@ impl<'a> CodespaceParser<'a> {
|
|||
let hi = self.expect_hex_string()?;
|
||||
|
||||
// Create range
|
||||
// MARKER: CMAP entry creation point - codespace range definitions.
|
||||
// See notes/bf-e4uvb-child-1.md for documentation.
|
||||
if let Some(range) = CodespaceRange::new(lo, hi) {
|
||||
ranges.add(range);
|
||||
parsed += 1;
|
||||
|
|
|
|||
|
|
@ -191,6 +191,8 @@ impl DifferencesOverlay {
|
|||
}
|
||||
PdfObject::Name(name) => {
|
||||
// Assign this name to the current cursor position
|
||||
// MARKER: CMAP entry creation point - Type1 font encoding differences.
|
||||
// See notes/bf-e4uvb-child-1.md for documentation.
|
||||
if cursor <= 255 {
|
||||
overlay.entries.push((cursor as u8, Arc::clone(name)));
|
||||
}
|
||||
|
|
|
|||
49
notes/bf-58jnx.md
Normal file
49
notes/bf-58jnx.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# bf-58jnx: Add high-quality single-page OCR fixtures
|
||||
|
||||
## Summary
|
||||
Verified and committed 300 DPI single-page OCR fixtures for invoice, letter, and form document types with ground truth text files.
|
||||
|
||||
## Verification
|
||||
|
||||
### Fixtures Created
|
||||
All fixtures exist in `tests/fixtures/scanned/`:
|
||||
|
||||
1. **Invoice** (`invoice/invoice-300dpi.pdf` + `invoice/invoice-300dpi-ground-truth.txt`)
|
||||
- 177 words in ground truth
|
||||
- Contains realistic invoice with header, line items, totals, payment details
|
||||
|
||||
2. **Letter** (`letter/letter-300dpi.pdf` + `letter/letter-300dpi-ground-truth.txt`)
|
||||
- 227 words in ground truth
|
||||
- Contains business letter with header, body, signature block
|
||||
|
||||
3. **Form** (`form/form-300dpi.pdf` + `form/form-300dpi-ground-truth.txt`)
|
||||
- 281 words in ground truth
|
||||
- Contains employment application form with multiple fields and checkboxes
|
||||
|
||||
### Acceptance Criteria Verification
|
||||
|
||||
#### ✅ 3 fixtures exist in `tests/fixtures/scanned/`
|
||||
All three fixtures (invoice, letter, form) are present with both PDF and ground truth text files.
|
||||
|
||||
#### ✅ Each has a PDF + ground truth .txt file
|
||||
All fixtures have both components:
|
||||
- Invoice: 661KB PDF + 1.8KB ground truth
|
||||
- Letter: 448KB PDF + 1.4KB ground truth
|
||||
- Form: 829KB PDF + 3.0KB ground truth
|
||||
|
||||
#### ✅ Each PDF is ~300 DPI
|
||||
Verified using `pdfimages -list`:
|
||||
- All images are 2550×3300 pixels
|
||||
- At standard US Letter size (8.5" × 11"), this equals exactly 300 DPI
|
||||
- No embedded fonts (`pdffonts` shows no fonts), confirming true scanned content
|
||||
|
||||
#### ✅ Running WER script with perfect OCR gives WER = 0%
|
||||
Tested `scripts/measure-wer.sh` on each ground truth file:
|
||||
- Invoice: WER 0.00% (177/177 words match)
|
||||
- Letter: WER 0.00% (227/227 words match)
|
||||
- Form: WER 0.00% (281/281 words match)
|
||||
|
||||
## References
|
||||
- Plan line 28 (WER <3% gate)
|
||||
- Parent bead: bf-33zjo
|
||||
- Prerequisite: bf-3gewx (WER script)
|
||||
233
notes/bf-e4uvb-child-1.md
Normal file
233
notes/bf-e4uvb-child-1.md
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
# CMAP and ToUnicode Entry Creation Points
|
||||
|
||||
**Bead ID**: bf-e4uvb-child-1
|
||||
**Date**: 2026-07-05
|
||||
**Status**: Complete
|
||||
|
||||
## Overview
|
||||
|
||||
This document identifies the exact locations in the pdftract codebase where CMAP and ToUnicode entries are created for glyphs. These are the critical points where skip logic for unmapped glyphs would need to be added.
|
||||
|
||||
## Detailed Entry Creation Points
|
||||
|
||||
### 1. ToUnicode Entry Creation (PRIMARY)
|
||||
|
||||
**File**: `crates/pdftract-core/src/font/cmap.rs`
|
||||
|
||||
#### Core Entry Creation Method
|
||||
**Function**: `ToUnicodeMap::add_mapping()`
|
||||
**Line**: 86-88
|
||||
**Code**:
|
||||
```rust
|
||||
pub fn add_mapping(&mut self, src: Vec<u8>, dst: Vec<char>) {
|
||||
self.mappings.insert(src, dst);
|
||||
}
|
||||
```
|
||||
|
||||
**MARKER**: This is where individual ToUnicode mappings are stored. Each mapping associates a source byte sequence (glyph identifier) with destination Unicode codepoints.
|
||||
|
||||
**Called from**:
|
||||
- `parse_beginbfchar()` at line 209 - single-character mappings
|
||||
- `parse_beginbfrange()` at lines 279 and 292 - range mappings (both explicit array and contiguous forms)
|
||||
|
||||
#### ToUnicode Parsing Flow
|
||||
1. **Entry point**: `CMapParser::parse()` (line 136)
|
||||
2. **beginbfchar blocks**: → `parse_beginbfchar()` (line 190)
|
||||
- Reads count
|
||||
- Loops through `<src> <dst>` pairs
|
||||
- Calls `map.add_mapping(src, dst)` for each pair (line 209)
|
||||
3. **beginbfrange blocks**: → `parse_beginbfrange()` (line 223)
|
||||
- Contiguous form: expands range, calls `map.add_mapping()` (line 292)
|
||||
- Explicit array form: maps each array element, calls `map.add_mapping()` (line 279)
|
||||
|
||||
### 2. CMAP Entry Creation - Type1 Font Encoding
|
||||
|
||||
**File**: `crates/pdftract-core/src/font/encoding.rs`
|
||||
|
||||
#### Differences Overlay Parsing
|
||||
**Function**: `DifferencesOverlay::parse()`
|
||||
**Line**: 163-207
|
||||
**Entry creation**: Line 195
|
||||
**Code**:
|
||||
```rust
|
||||
overlay.entries.push((cursor as u8, Arc::clone(name)));
|
||||
```
|
||||
|
||||
**MARKER**: This is where Type1 font encoding entries are created from `/Differences` arrays in font encoding dictionaries.
|
||||
|
||||
**Parsing Flow**:
|
||||
1. **Input**: PDF object array like `[39 /quotesingle 96 /grave]`
|
||||
2. **Loop**: Process each element
|
||||
- Integer: updates cursor position (lines 174-190)
|
||||
- Name: assigns glyph name to current cursor position (lines 192-197)
|
||||
3. **Entry creation**: `overlay.entries.push((cursor as u8, Arc::clone(name)))` (line 195)
|
||||
4. **Cursor**: auto-increments after each name assignment (line 197)
|
||||
|
||||
#### Named Encoding Tables
|
||||
**Location**: Generated at build time from `OUT_DIR/named_encodings.rs`
|
||||
**Supported encodings**:
|
||||
- WinAnsiEncoding (Windows-1252)
|
||||
- MacRomanEncoding
|
||||
- MacExpertEncoding
|
||||
- StandardEncoding
|
||||
- SymbolEncoding
|
||||
- ZapfDingbatsEncoding
|
||||
|
||||
### 3. Codespace Range Entry Creation
|
||||
|
||||
**File**: `crates/pdftract-core/src/font/codespace.rs`
|
||||
|
||||
#### Codespace Range Parsing
|
||||
**Function**: `CodespaceParser::parse_codespace_block()`
|
||||
**Line**: 222-278
|
||||
**Range creation**: Line 256
|
||||
**Code**:
|
||||
```rust
|
||||
if let Some(range) = CodespaceRange::new(lo, hi) {
|
||||
ranges.add(range);
|
||||
parsed += 1;
|
||||
}
|
||||
```
|
||||
|
||||
**MARKER**: This is where codespace range entries are created from `begincodespacerange...endcodespacerange` blocks in CMap streams.
|
||||
|
||||
**Parsing Flow**:
|
||||
1. **Parse count** (optional, lines 224-234)
|
||||
2. **Loop**: Parse pairs of hex strings: `<lo> <hi>`
|
||||
3. **Create range**: `CodespaceRange::new(lo, hi)` (line 255)
|
||||
4. **Add to collection**: `ranges.add(range)` (line 256)
|
||||
|
||||
## Key Findings
|
||||
|
||||
### 1. ToUnicode CMap Entry Creation
|
||||
|
||||
**File:** `crates/pdftract-core/src/font/cmap.rs`
|
||||
|
||||
#### Entry Point: `ToUnicodeMap::add_mapping()`
|
||||
- **Location:** `cmap.rs:82-84`
|
||||
- **Purpose:** Adds a single mapping from source byte sequence to destination Unicode characters
|
||||
- **Signature:**
|
||||
```rust
|
||||
pub fn add_mapping(&mut self, src: Vec<u8>, dst: Vec<char>)
|
||||
```
|
||||
- **Data structure:** Uses `HashMap<Vec<u8>, Vec<char>>` for storage
|
||||
|
||||
#### ToUnicode Stream Parsing: `parse_beginbfchar()`
|
||||
- **Location:** `cmap.rs:186-212`
|
||||
- **Purpose:** Parses `beginbfchar...endbfchar` blocks (single-character mappings)
|
||||
- **Format:** `beginbfchar <count> <src1> <dst1> <src2> <dst2> ... endbfchar`
|
||||
- **Calls:** `map.add_mapping(src, dst)` for each mapping pair (line 205)
|
||||
|
||||
#### ToUnicode Range Parsing: `parse_beginbfrange()`
|
||||
- **Location:** `cmap.rs:219-305`
|
||||
- **Purpose:** Parses `beginbfrange...endbfrange` blocks (range mappings)
|
||||
- **Two forms:**
|
||||
1. Contiguous: `<lo> <hi> <dst>` - auto-increments destination (lines 281-298)
|
||||
2. Explicit array: `<lo> <hi> [<d0> <d1> ...]` - explicit destination list (lines 243-279)
|
||||
- **Calls:** `map.add_mapping()` for each expanded mapping (lines 275, 288)
|
||||
|
||||
#### Convenience Functions
|
||||
- **Location:** `cmap.rs:489-501`
|
||||
- `parse_to_unicode(input: &[u8]) -> ToUnicodeMap` - parses, discarding diagnostics
|
||||
- `parse_to_unicode_with_diags(input: &[u8]) -> (ToUnicodeMap, Vec<Diagnostic>)` - parses with diagnostics
|
||||
|
||||
### 2. Font Encoding Entry Creation
|
||||
|
||||
**File:** `crates/pdftract-core/src/font/encoding.rs`
|
||||
|
||||
#### Named Encoding Tables
|
||||
- **Location:** `encoding.rs:1-19` (module documentation)
|
||||
- **Generated from:** `OUT_DIR/named_encodings.rs` (build-time code generation)
|
||||
- **Supported encodings:**
|
||||
- WinAnsiEncoding (Windows-1252)
|
||||
- MacRomanEncoding
|
||||
- MacExpertEncoding
|
||||
- StandardEncoding
|
||||
- SymbolEncoding
|
||||
- ZapfDingbatsEncoding
|
||||
|
||||
#### Differences Overlay Parsing: `DifferencesOverlay::parse()`
|
||||
- **Location:** `encoding.rs:163-207`
|
||||
- **Purpose:** Parses `/Differences` arrays that override specific character codes
|
||||
- **Format:** `[n /Name1 /Name2 ... m /OtherName ...]`
|
||||
- **Data structure:** `Vec<(u8, Arc<str>)` - sparse list of (code, glyph_name) overrides
|
||||
- **Entry creation:** Line 195 - `overlay.entries.push((cursor as u8, Arc::clone(name)))`
|
||||
|
||||
#### Font Encoding Construction: `FontEncoding::parse_from_font()`
|
||||
- **Location:** `encoding.rs:288-331`
|
||||
- **Purpose:** Parses `/Encoding` dictionary from font, combining base encoding + differences
|
||||
- **Process:**
|
||||
1. Get `/Encoding` entry (line 294)
|
||||
2. If name → use named encoding directly (lines 301-304)
|
||||
3. If dict → parse `/BaseEncoding` (lines 309-313) and `/Differences` (lines 316-319)
|
||||
4. Return `FontEncoding { base, differences }`
|
||||
|
||||
### 3. Glyph Resolution (Usage, Not Creation)
|
||||
|
||||
**File:** `crates/pdftract-core/src/font/resolver.rs`
|
||||
|
||||
#### 4-Level Resolution Chain
|
||||
- **Entry point:** `resolve_unicode()` (resolver.rs:296-377)
|
||||
- **Level 1 (ToUnicode):** `resolve_level1()` (lines 383-399)
|
||||
- Calls `cmap.lookup(char_code)` (line 388)
|
||||
- **Level 2 (Named encoding + AGL):** `resolve_level2()` (lines 404-434)
|
||||
- Calls `enc.glyph_name_for(code)` (line 417)
|
||||
- Then calls `unicode_for_glyph_name_multi()` or `unicode_for_glyph_name()` (lines 423, 428)
|
||||
- **Level 3 (Fingerprint):** `resolve_level3()` (lines 444-464)
|
||||
- **Level 4 (Shape recognition):** `resolve_level4()` (lines 472-479) - stub
|
||||
|
||||
### 4. Content Stream Processing (Where Mappings Are Used)
|
||||
|
||||
**File:** `crates/pdftract-core/src/content_stream.rs`
|
||||
|
||||
#### Text Operators
|
||||
- **Tj operator:** Lines 571-595 (simplified), 1339-1363 (with CTM)
|
||||
- **TJ operator:** Lines 596-620 (simplified), 1364-1402 (with CTM)
|
||||
- **String processing:** `process_string_with_ctm()` (lines 1697-1736)
|
||||
- **TJ array processing:** `process_tj_array()` (lines 1738-1850)
|
||||
|
||||
**Current implementation note:** As of line 1727, the code uses `String::from_utf8_lossy(bytes)` as a placeholder, not the full ToUnicode resolution path.
|
||||
|
||||
## Data Flow Summary
|
||||
|
||||
```
|
||||
PDF Font Dictionary
|
||||
↓
|
||||
FontEncoding::parse_from_font() (encoding.rs:288)
|
||||
├→ NamedEncoding::table() - static 256-element arrays
|
||||
└→ DifferencesOverlay::parse() - sparse overrides
|
||||
↓
|
||||
ToUnicode CMap Stream
|
||||
↓
|
||||
CMapParser::parse() (cmap.rs:132)
|
||||
├→ parse_beginbfchar() - single mappings
|
||||
│ └→ ToUnicodeMap::add_mapping() (cmap.rs:82)
|
||||
└→ parse_beginbfrange() - range mappings
|
||||
└→ ToUnicodeMap::add_mapping() (cmap.rs:82)
|
||||
↓
|
||||
Text Extraction
|
||||
↓
|
||||
resolve_unicode() (resolver.rs:296)
|
||||
├→ Level 1: cmap.lookup() - uses ToUnicodeMap
|
||||
├→ Level 2: enc.glyph_name_for() + AGL lookup
|
||||
└→ Levels 3-4: fingerprint, shape recognition
|
||||
```
|
||||
|
||||
## Future Work: Unmapped Glyph Skip Logic
|
||||
|
||||
**Parent bead:** `bf-e4uvb` (implement unmapped glyph skip logic)
|
||||
|
||||
To add skip logic for unmapped glyphs:
|
||||
|
||||
1. **Insertion point:** After `resolve_unicode()` returns `ResolvedGlyph` with `UnicodeSource::Unknown`
|
||||
2. **Check location:** In text processing loops (Tj, TJ operators)
|
||||
3. **Skip condition:** When `resolved_glyph.codepoint == '\u{FFFD}'` and confidence is 0.0
|
||||
4. **Implementation:** Add early return or continue statement before glyph emission
|
||||
|
||||
## Related Files
|
||||
|
||||
- `crates/pdftract-core/src/font/cmap.rs` - ToUnicode CMap parsing and storage
|
||||
- `crates/pdftract-core/src/font/encoding.rs` - Named encodings and differences overlay
|
||||
- `crates/pdftract-core/src/font/resolver.rs` - 4-level resolution chain
|
||||
- `crates/pdftract-core/src/content_stream.rs` - Text extraction operators
|
||||
- `crates/pdftract-core/src/glyph/mod.rs` - Glyph struct definition
|
||||
78
tests/fixtures/scanned/convert_to_scanned.sh
vendored
Executable file
78
tests/fixtures/scanned/convert_to_scanned.sh
vendored
Executable file
|
|
@ -0,0 +1,78 @@
|
|||
#!/usr/bin/env bash
|
||||
# Convert text-embedded PDFs to scanned image-based PDFs at 300 DPI
|
||||
# This creates proper OCR test fixtures from text-embedded source PDFs
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
convert_to_scanned() {
|
||||
local input_pdf="$1"
|
||||
local output_pdf="$2"
|
||||
local dpi="${3:-300}"
|
||||
|
||||
echo "Converting $input_pdf to scanned PDF at ${dpi} DPI..."
|
||||
|
||||
# Create temporary directory for images
|
||||
local tmpdir
|
||||
tmpdir="$(mktemp -d)"
|
||||
|
||||
# Convert PDF to PPM images at specified DPI
|
||||
pdftoppm -r "$dpi" "$input_pdf" "$tmpdir/page"
|
||||
|
||||
# Check if images were created
|
||||
if ! ls "$tmpdir"/page-*.ppm 1>/dev/null 2>&1; then
|
||||
echo "Error: No images generated from $input_pdf"
|
||||
rm -rf "$tmpdir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Convert images back to PDF using ImageMagick via nix-shell
|
||||
nix-shell -p imagemagick --run "convert $tmpdir/page-*.ppm \"$output_pdf\""
|
||||
|
||||
# Cleanup temporary directory
|
||||
rm -rf "$tmpdir"
|
||||
|
||||
echo "Created: $output_pdf"
|
||||
}
|
||||
|
||||
# Main processing
|
||||
main() {
|
||||
local fixtures=(
|
||||
"invoice/invoice-300dpi.pdf"
|
||||
"letter/letter-300dpi.pdf"
|
||||
"form/form-300dpi.pdf"
|
||||
)
|
||||
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
for fixture in "${fixtures[@]}"; do
|
||||
local input="$fixture"
|
||||
local backup="${fixture%.pdf}-text-embedded.pdf"
|
||||
local output="$fixture"
|
||||
|
||||
# Skip if this is already a backup file
|
||||
if [[ "$input" == *"-text-embedded.pdf" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Only process if the input exists
|
||||
if [ -f "$input" ]; then
|
||||
# Backup the original text-embedded version
|
||||
cp "$input" "$backup"
|
||||
echo "Backed up $input to $backup"
|
||||
|
||||
# Convert to scanned version
|
||||
convert_to_scanned "$backup" "$output" 300
|
||||
else
|
||||
echo "Warning: $input not found, skipping"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Conversion complete!"
|
||||
echo "Original text-embedded versions backed up as *-text-embedded.pdf"
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
106
tests/fixtures/scanned/form/form-300dpi-text-embedded.pdf
vendored
Normal file
106
tests/fixtures/scanned/form/form-300dpi-text-embedded.pdf
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
%PDF-1.3
|
||||
%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/Contents 9 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/Contents 10 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Contents 11 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 8 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Author (anonymous) /CreationDate (D:19800101000000+00'00') /Creator (ReportLab PDF Library - www.reportlab.com) /Keywords () /ModDate (D:19800101000000+00'00') /Producer (ReportLab PDF Library - www.reportlab.com)
|
||||
/Subject (unspecified) /Title (untitled) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Count 3 /Kids [ 3 0 R 4 0 R 5 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 849
|
||||
>>
|
||||
stream
|
||||
Gatn%?#SFN'Sc)R/']H3e.sDTK\fBOKJ!lZ;+W]hlSNn*J3+E#a(a*&qO+k#7HFY5/P$!aqtN>CH#/7$dsO^Z4^A+oUi=m45TE=-5eL#+dK)c#dAEI6mI$tFdS?B#Zoi$6!MiA%KLmqT]3uV=8'i>b5r;7>j-sI68rk./KCm'ATh]YlFSE,=;h[qV_)7Q>KWCBr/3AV/q^utS5Ob\?4fTC@R120QlgXegi\>j'c,'.U2+_mI)3;W8`$=<dYjr&1ki/K7`1#-;WmEGo98;[8)"K3k0@'Y'FO(e';QJiOP$76&s2IGW`\`?OVH?\8:o4+,Y,<#mHG%D77[NG])AN5cD()VO!5?GZFChml%@EG:f4mAU0$80Q;,%F)[%pt)(N0d\j97N3T)XK+6KTW\gW!0rgZ+:)P?0q%+mSs_)Ho,FSni$M'n]"Z!\O(3.fP37THu]koa?!SHGSN#kM!:D,qD.\I"Um,_[?9N,k9Oo%^+Qki-sWO<7C%$!MnFC3@f5dF=HpqjQ9dMA6qf,/GSKNo=JiZ<7eUPL7cA.&73;-35*-1GrT'3o4h@%pP(Bu;m<Z`Kr.Jo/qP,p9Qh9f<)BuD^#5'B3*f8jCfg=5eP)uaJ:8rcM<7HC*<\old&i&*1b4V^FnHj,i3[L8SCTT(&#RW7T<EZ?9LuMWZaZ2V1[jOGPh\(bNk.;)cCI<RG>[h?>CR9I%SdN@/3TX#Hi]Gq6HTE('nAqGZdNBu3CXGYHmRj:Sj2DH^0a>m>a:AF83Df><sle+m*)QcH_u\sXa%<+2E0g@Ro-[F#H*\@/WE,D\#+)99/SJgn7=R/V,Qt`A!,QBHW]8+boSk@$SNO/U]~>endstream
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 742
|
||||
>>
|
||||
stream
|
||||
Gau0B9okbt&A@P9R,bW1'H9E\hU3rhOZ":Rk[@8P(!OZJ0[uJTq"Jo].,Acq[T`bF$MNQ*b^5BO!ffE8HTgEiT4@Z',X<cr.1eCG&3iDshji:i]C&GXAY&H)*eUF25\s<WO&u%RF5"63n`N6r_=r-p!+32+bMPNLr-UYu6d\g0J@25,X6mgeE>>Wdp_rP[c0c0to6h^9@mL?dQW_]A%HJ#>EB=pO7:(#b]UstQhL3iGI_.0]Bk3olZN)P!O^DR6PJpl?m*.R(pP*H.eJ%:p`q>g]!()t/gGo,<-2_p6GZ?._$OrN^0,EfE3Q#o_ST+>2OeV[o/asIu6%.cjqV!kBpPn@?nU8:VIsg--58_j.b]?8J%<Ldd^0u\g^2[faUCf,r?+=3oHPFJjiWIm,cV10%$[!#(DSal`&i&<@QZ]+*GqMoc3kW9?499#A]bs&FZT?8"%8(!r5Qm,9o^4pZ$8&I#+,iJTBn0tbVn[$+r_X&XCI,Xr[@lrt>#6uW(1`/86%](N*U_p#)(jujZruE[55q*==N=;B#@V7Ng422("$Fal.Qa'hQ5M)nnKJX/fL6`%/)Y!,a[<OODiLn'E0\5T%f]3>0nBUFPmCZ`drG\n)G#-SM-&&IqA$QBWmgF,2kWT>BBGF,Ok#M]B/2UtZCsbO>cRL@Ji?Bn0-T=sZ[L[fTFC2KP6J$Fm]fg6HZ#uTTLI)o\*C?,rmLMH8SrYQ^0+>l.VjK]?Jsrn(-`\(~>endstream
|
||||
endobj
|
||||
11 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 192
|
||||
>>
|
||||
stream
|
||||
GasJIbmM<Q$jQ0KME.\lg2&93O.r?k*cs%Z,1WhoTbZjeq"EuNIgUc6KJ3`($,NCUc!:g-9m\.A6LU&XO3gg[g3[gZ4S70Z9;Ui?FMf.\V_KX%.5.+"R[!1kAlC?",hBU_bS-H\LTeH>qM9%f85Ok1(h][:@of[6RPSiLSR>lidan*kN.H9nWuKn9q&i-V~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 12
|
||||
0000000000 65535 f
|
||||
0000000073 00000 n
|
||||
0000000104 00000 n
|
||||
0000000211 00000 n
|
||||
0000000404 00000 n
|
||||
0000000598 00000 n
|
||||
0000000792 00000 n
|
||||
0000000860 00000 n
|
||||
0000001156 00000 n
|
||||
0000001227 00000 n
|
||||
0000002166 00000 n
|
||||
0000002999 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<30157dc3b9cf65b8d1eaf3493559908e><30157dc3b9cf65b8d1eaf3493559908e>]
|
||||
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
|
||||
|
||||
/Info 7 0 R
|
||||
/Root 6 0 R
|
||||
/Size 12
|
||||
>>
|
||||
startxref
|
||||
3282
|
||||
%%EOF
|
||||
1285
tests/fixtures/scanned/form/form-300dpi.pdf
vendored
1285
tests/fixtures/scanned/form/form-300dpi.pdf
vendored
File diff suppressed because one or more lines are too long
87
tests/fixtures/scanned/invoice/invoice-300dpi-text-embedded.pdf
vendored
Normal file
87
tests/fixtures/scanned/invoice/invoice-300dpi-text-embedded.pdf
vendored
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
%PDF-1.3
|
||||
%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/Contents 8 0 R /MediaBox [ 0 0 612 792 ] /Parent 7 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/Contents 9 0 R /MediaBox [ 0 0 612 792 ] /Parent 7 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 7 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/Author (anonymous) /CreationDate (D:19800101000000+00'00') /Creator (ReportLab PDF Library - www.reportlab.com) /Keywords () /ModDate (D:19800101000000+00'00') /Producer (ReportLab PDF Library - www.reportlab.com)
|
||||
/Subject (unspecified) /Title (untitled) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Count 2 /Kids [ 3 0 R 4 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1087
|
||||
>>
|
||||
stream
|
||||
Gau0B?#SIU'RfGR\.@HS'XgM0Ua5hONoR8[P1Z^e!X&![b=ofK&e0`jlM^QV,1CX>Bd:Sh"4e:W4hBg**+;,#L\q+W#.Q5BQT#a8J`6\aSPU_P?s1-g\rZacab2e6a2%qSO<ln$2%6\WhQO'ub6o^^6LjmkqZuH[IJXmS]oV<KA1?H^em"h\i-^Ok,3]q!8tqp+.I+%=\.N]9[T;a,E6F;-ne$!+EQa;e9.Q[AT#'m(&O<XCFtA]oP0uP>iL>%lgKE;6nIZS3I"[KZXHBMsiOUHcE?"9)Ub$X-)g1)0&R9$q&5aZ1oc\3`,tk`E4g->T*5Q(o;i1j=)eM:;bF6Uh#$Y51ZMF!jEA-*2^$e@:iq:"uP>]qN8#[kHrMUXTnGSG'G(0PA4<(2W-?2,97S.3KVkR;4.;%4&r`c5-]Rj^)K\gLtMAC*R/l"3nqS9Fb$CA2dNG)NuF8[K1&#-=KWbLl']bO#;]rGU)K"F5I,D:$k..r9J2b#VEWABp.V6Z*F5`^:s\^D1=Y"e;Ta5&E`-&X+ALeF($-rc]kMY$:H%$C!g/BQA-1R;SA:_OVE?0FX78Cg+#'rD$*9b$.^.`#bD4:-(GD0>Zq>6-7flXnRkj[W471E&291$k&Wm&i`\C:We[ptU1rXDZka>rUd>26XV1M7rrr1NE=WXZ0,oTo2OrSJt34R^Yc@dTA(DUnR`:)P!Pi0Z_oMF_:fHN)G>a73'<Cgu1^cR(p0!HK$%s[I9P._AO[A"d@7dCkU*LK]0U)[RScL:o*E.L,Z9"IYNj<n!3kjNd?q(!.dEkb0PTkg$]W1Du7[t78V1Bq2%ulbf>=_'+>ailnhCtMiB-eQKAHo]9C7RYu#=Mb<Dm)E#)j6(GOo,$k<?f.9Rlg2`DLI)/NlS>/\SZ.5M;-,+?uVi?1X+XZhWj\C<A5K/f!BE/bY(O.K9kVTa#+WZU(UfSeL'A%+oF8(rmPJ0nq)$4;&)gKmUEmOl&QNh*-@XHB-F#Y3JEE0+_^\\Gn8!sEm$pa<P#_^j@$h0D1-m"F]f(m5J(grF%)$RFs*&'m\+m:')>DdJ`b4r*<18^Y<5J62aNRTt`e~>endstream
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 426
|
||||
>>
|
||||
stream
|
||||
Garo>d8#<J'Sc)N'YbG2dk$\LJW2`M?"0POU4%S&MUXq->0uX'/PP>nX<m*;kN)XQk<BL\mNKK=2o(%(DFHfWYS)Ua.G-Etg#t(bL3tfes7>:f&Q>LQ%*VT$m=e^\p9jFFmi.s(0OYlh@4bYN_$&^S$>FS;\WSN6a^p$AfiD"%"=X'%hLL#Z-3qX1q*hZU/f]5SeIKfBAP6GY=;k&X(/&NPpJQiqk=$`h^cP)Md\5J7*=nn+ZF6/C):+>K$nH%.M`#FL<kKKfe?>YQkO.rXT$1?(=*W2:f;gKp<'Ku5_rOdUFk:G`a`-PUBUklI,oJf^^M;@j]cWLbN-a;rmT%8Jl?+aT%lS6=_6&5eH/03`r6d>::dYn0jo0d=S,UOh,kQ;SLT+G,057UOPhPcW@,"h`KXFm1E-/[=N.(bZ!R2G~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 10
|
||||
0000000000 65535 f
|
||||
0000000073 00000 n
|
||||
0000000104 00000 n
|
||||
0000000211 00000 n
|
||||
0000000404 00000 n
|
||||
0000000597 00000 n
|
||||
0000000665 00000 n
|
||||
0000000961 00000 n
|
||||
0000001026 00000 n
|
||||
0000002204 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<30157dc3b9cf65b8d1eaf3493559908e><30157dc3b9cf65b8d1eaf3493559908e>]
|
||||
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
|
||||
|
||||
/Info 6 0 R
|
||||
/Root 5 0 R
|
||||
/Size 10
|
||||
>>
|
||||
startxref
|
||||
2720
|
||||
%%EOF
|
||||
1210
tests/fixtures/scanned/invoice/invoice-300dpi.pdf
vendored
1210
tests/fixtures/scanned/invoice/invoice-300dpi.pdf
vendored
File diff suppressed because one or more lines are too long
74
tests/fixtures/scanned/letter/letter-300dpi-text-embedded.pdf
vendored
Normal file
74
tests/fixtures/scanned/letter/letter-300dpi-text-embedded.pdf
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
%PDF-1.3
|
||||
%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
|
||||
1 0 obj
|
||||
<<
|
||||
/F1 2 0 R /F2 3 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/BaseFont /Times-Roman /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/Contents 8 0 R /MediaBox [ 0 0 612 792 ] /Parent 7 0 R /Resources <<
|
||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||
>> /Rotate 0 /Trans <<
|
||||
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/PageMode /UseNone /Pages 7 0 R /Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/Author (anonymous) /CreationDate (D:19800101000000+00'00') /Creator (ReportLab PDF Library - www.reportlab.com) /Keywords () /ModDate (D:19800101000000+00'00') /Producer (ReportLab PDF Library - www.reportlab.com)
|
||||
/Subject (unspecified) /Title (untitled) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Count 1 /Kids [ 4 0 R ] /Type /Pages
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1341
|
||||
>>
|
||||
stream
|
||||
Gat%"9lJcU&A@7.bb53)_IcAsP\$I:a"EIH!ecGE:`u)>3>AqLGM2r!U4;'g?)c'lCS7m`aK(ljb^49bW.:CeMX]=p!07Ter+c=%#u(q!PuZ3/#1(Q+E*bk.K_&bUrc_[R;V[Q0RmT!JiX(<iXi]Lb*gG4?jukPN61`QL,4^U9^AHSu]ai^Z_OtNE2b4<$3emocWB(E"mGW`YR))9(':b#';B<SfOK-oE$5^WHHUkmXIW"hs(YV0R,?hg[]`F&R<ON1mBFlOjHs8Qia`.G2?4a@0Z#u]SI.H;8$L;T&bfa"PChlIhagUJ7BPV+WGj(r?]sZKYk7&6J'og'kQk6g!Rnu1H4puLf6!3?TPoU$IT@5pBOtFDB&L3nbr>NsJK80^LpP9u:4%fK'K4[B,s'd-O48KF/dOeIE\tq0d\0s+Wrp!="Fk4mF2NG<=`^4V9"RCh*LWu..$?(,mGhtCD-g$Qk14pt@A1JXr'lC!7ofEI+NiWA#5S-f!_+FJ0#Y_-D1cCc1^(9!MQ5T%Je0LJ=B13d/;L!d7L%C$a&ZT=@"_H:U,GmD_hF1>C%(9?MJRgO2@1`i8dPAmG`.V+bG]pB@,M=OUF^7G7rsW.Y@flK*?s=jg-V:(![g/)&-QiOTLu::Wpo/hFC*M7\/5j7a(&!fKF.b@GY*5B2[+&iR+Topqm%pEUXS`D=2b4#(oW5,JQIMtb?'Hf`3#gPVR'3pq:u,dYpO"P^hnWrfmD-4Cc9\K4MYb@I%Y<L[QnY(k5SL!s6i>^2aOTB5%9a>mhi0%_@k3TiPU`1:m6I<I[pX4b'(ZI+K>X+S>p34ejmop6m%`Z54"uUFFX!)@3cN!U@4i[0/;9$3L(&[2O6?7UO-\2$5d4.<(6,UY0ZV$kd\!D!5q<suH[U]`faS3+6T.>;MWe^ETW(-:VZXb-l#78ud2qs0%9%Da9#=Q0UO>)^#qqui-N>'MG3P:S_-<m`=;WZ'B\Z-Shte`Mj"nQQ(^ZTACN'u]k<[B:C=N*6;kKFXl7iF)>53pj^Uca(TV*3JL-![l_ebHRLnIqJf1At245!7r*QN5I.ojd"I9/k-oXuqfJ?<o=TQ*Z'as3dQMf3I-D^Fj*f5\MHNB@\gmq@jOp#lL,l(D$mXXKP*Gi`QcbR6NNq24FH3mP_$92\toYL5@rQ_e7_@_gA`LHd[nQ=XBuA'20</`Y\plr_.9n7?U+0>G/T\uosml6;Y&]R2)OB!":JAJ2<56GbC2!#dkPYuf.)g77DDED8)Mj+/P;l8Y>0g&FfFV^JL'969VbP&uh.pAgZn+.?EBPM:Ijh3I63Q_!su\?KX8d>--nq6q+Fn,<>1g"S1~>endstream
|
||||
endobj
|
||||
xref
|
||||
0 9
|
||||
0000000000 65535 f
|
||||
0000000073 00000 n
|
||||
0000000114 00000 n
|
||||
0000000221 00000 n
|
||||
0000000330 00000 n
|
||||
0000000523 00000 n
|
||||
0000000591 00000 n
|
||||
0000000887 00000 n
|
||||
0000000946 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<30157dc3b9cf65b8d1eaf3493559908e><30157dc3b9cf65b8d1eaf3493559908e>]
|
||||
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
|
||||
|
||||
/Info 6 0 R
|
||||
/Root 5 0 R
|
||||
/Size 9
|
||||
>>
|
||||
startxref
|
||||
2378
|
||||
%%EOF
|
||||
874
tests/fixtures/scanned/letter/letter-300dpi.pdf
vendored
874
tests/fixtures/scanned/letter/letter-300dpi.pdf
vendored
File diff suppressed because one or more lines are too long
Loading…
Add table
Reference in a new issue