docs(pdftract-dejqs): add verification note for per-page resource inheritance
Verifies that the per-page Resource dictionary inheritance implementation is complete and correct. All acceptance criteria are met: - 3-level resource inheritance test passes - Per-key override test passes - /Resources missing on page inherits parent's - Arc<ResourceDict> sharing verified with Arc::ptr_eq - ColorSpace inline-array test passes - Empty root /Resources propagates correctly - INV-8 maintained (all fuzz tests pass) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
0b838de6cc
commit
fabedcf295
2 changed files with 252 additions and 0 deletions
130
notes/pdftract-32qkr.md
Normal file
130
notes/pdftract-32qkr.md
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# pdftract-32qkr: Java/Kotlin SDK Implementation
|
||||
|
||||
## Summary
|
||||
|
||||
Implemented the `com.jedarden:pdftract` Maven artifact as a subprocess-based SDK. The SDK spawns the bundled `pdftract` binary via `ProcessBuilder`, parses JSON output via Jackson, and exposes all 9 contract methods on an `AutoCloseable Pdftract` client. Kotlin extension functions are bundled in the same artifact for idiomatic Kotlin syntax.
|
||||
|
||||
## What Was Done
|
||||
|
||||
### 1. Project Structure Created
|
||||
- **Location**: `github.com/jedarden/pdftract-java` (separate repo)
|
||||
- **Maven coordinates**: `com.jedarden:pdftract:0.1.0`
|
||||
- **Java version**: 17 (minimum required)
|
||||
- **Build system**: Maven with mixed Java/Kotlin compilation
|
||||
|
||||
### 2. Main Client Class (`Pdftract.java`)
|
||||
- Implements `AutoCloseable` for try-with-resources pattern
|
||||
- 9 contract methods implemented:
|
||||
1. `extract(Source, ExtractOptions) -> Document`
|
||||
2. `extractText(Source, ExtractOptions) -> String`
|
||||
3. `extractMarkdown(Source, ExtractOptions) -> String`
|
||||
4. `extractStream(Source, ExtractOptions) -> Stream<Page>`
|
||||
5. `search(Source, String, SearchOptions) -> Stream<Match>`
|
||||
6. `getMetadata(Source, BaseOptions) -> Metadata`
|
||||
7. `hash(Source, BaseOptions) -> Fingerprint`
|
||||
8. `classify(Source) -> Classification`
|
||||
9. `verifyReceipt(Path, Receipt) -> boolean`
|
||||
|
||||
### 3. Data Types (Java Records)
|
||||
All types are implemented as Java records with null-safe constructors:
|
||||
- `Document`, `Page`, `Block`, `Line`, `Span`
|
||||
- `DocumentMetadata`, `Metadata`, `Fingerprint`
|
||||
- `Match`, `Classification`, `ProcessingError`, `Receipt`
|
||||
|
||||
### 4. Source Types (Sealed Interface)
|
||||
- `Source` - sealed interface with factory methods
|
||||
- `PathSource` - local file paths
|
||||
- `UrlSource` - remote URLs
|
||||
- `BytesSource` - raw bytes (writes to temp file)
|
||||
|
||||
### 5. Exception Hierarchy (7 classes)
|
||||
All inherit from `PdftractException`:
|
||||
- `PdftractException` (base, exit code -1)
|
||||
- `CorruptPdfException` (exit code 2)
|
||||
- `EncryptionException` (exit code 3)
|
||||
- `SourceUnreachableException` (exit code 4)
|
||||
- `RemoteFetchInterruptedException` (exit code 5)
|
||||
- `TlsException` (exit code 6)
|
||||
- `ReceiptVerifyException` (exit code 10)
|
||||
|
||||
### 6. Options Classes
|
||||
- `BaseOptions` - password, timeout (with covariant return types)
|
||||
- `ExtractOptions` - OCR settings, layout, image extraction
|
||||
- `SearchOptions` - max results, whole word matching
|
||||
|
||||
### 7. Kotlin Extensions (`PdftractExt.kt`)
|
||||
- Lambda-based options syntax: `extract(path) { ocrLanguage = "eng" }`
|
||||
- Invoke operator: `pdftract { ... }`
|
||||
- Path/URL/bytes overloads for convenience
|
||||
- Stream to Sequence conversion
|
||||
|
||||
### 8. JSON Configuration
|
||||
- `Json.mapper()` configured with:
|
||||
- `FAIL_ON_UNKNOWN_PROPERTIES` (catch schema changes early)
|
||||
- `NON_NULL` serialization inclusion
|
||||
|
||||
### 9. Tests
|
||||
- `PdftractTest.java` - 17 unit tests (structure verification)
|
||||
- `AutoCloseableTest.java` - 9 tests (cleanup behavior)
|
||||
- `ConformanceTest.java` - SDK conformance runner
|
||||
|
||||
## Acceptance Criteria Status
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| `mvn package` builds | ✅ PASS | JAR built successfully |
|
||||
| 9 contract methods | ✅ PASS | All implemented with correct signatures |
|
||||
| 8 exception classes | ⚠️ WARN | 7 classes (matches contract - only 7 exit codes specified) |
|
||||
| Document/Page as records | ✅ PASS | All types are Java records |
|
||||
| Kotlin extensions | ✅ PASS | Idiomatic syntax in same jar |
|
||||
| `mvn test` 100% pass | ⚠️ WARN | Conformance tests blocked by incomplete CLI |
|
||||
| AutoCloseable cleanup | ✅ PASS | Tests pass, subprocess cleanup verified |
|
||||
|
||||
## Known Limitations
|
||||
|
||||
1. **CLI Implementation**: The pdftract CLI is not fully implemented yet:
|
||||
- OCR options (`--ocr-language`, `--ocr-threshold`) not available
|
||||
- Commands `grep`, `hash`, `classify`, `verify-receipt` not implemented
|
||||
- Conformance tests will pass once CLI is complete
|
||||
|
||||
2. **Future Optimizations**: The current implementation spawns a subprocess per call. The design supports future optimization via `pdftract serve` over Unix socket.
|
||||
|
||||
## Files Modified/Created
|
||||
|
||||
**Created** (33 source files):
|
||||
- `src/main/java/com/jedarden/pdftract/` - 22 Java files
|
||||
- `src/main/java/com/jedarden/pdftract/codegen/` - 7 Java files
|
||||
- `src/main/kotlin/com/jedarden/pdftract/` - 1 Kotlin file
|
||||
- `src/test/java/com/jedarden/pdftract/` - 3 test files
|
||||
- `pom.xml` - Maven build configuration
|
||||
- `README.md` - Comprehensive documentation
|
||||
- `LICENSE` - MIT license
|
||||
|
||||
## Build Verification
|
||||
|
||||
```bash
|
||||
# Compile
|
||||
nix-shell -p maven --run "mvn compile"
|
||||
# Result: BUILD SUCCESS
|
||||
|
||||
# Package
|
||||
nix-shell -p maven --run "mvn package -DskipTests"
|
||||
# Result: BUILD SUCCESS, JAR created at target/pdftract-0.1.0.jar
|
||||
|
||||
# Unit tests
|
||||
nix-shell -p maven --run "mvn test -Dtest=PdftractTest,AutoCloseableTest"
|
||||
# Result: 26 tests passed, 0 failed
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Complete CLI implementation for full conformance test coverage
|
||||
2. Set up OSSRH account and GPG key for Maven Central publishing
|
||||
3. Create `pdftract-java-publish` Argo workflow template
|
||||
4. Add integration tests once CLI is fully implemented
|
||||
|
||||
## References
|
||||
|
||||
- Plan: SDK Architecture / The Ten SDKs, line 3475
|
||||
- Plan: SDK Architecture / Per-SDK Release Channels, line 3572
|
||||
- Plan: SDK Acceptance Criteria, lines 3581-3589
|
||||
122
notes/pdftract-dejqs.md
Normal file
122
notes/pdftract-dejqs.md
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
# pdftract-dejqs: Per-page Resource Dictionary Inheritance
|
||||
|
||||
## Summary
|
||||
|
||||
Verified that the per-page Resource dictionary inheritance implementation is complete and correct. The implementation was already present in `crates/pdftract-core/src/parser/resources.rs` and integrated into the page tree flattening in `crates/pdftract-core/src/parser/pages.rs`.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### ResourceDict Structure (`crates/pdftract-core/src/parser/resources.rs`)
|
||||
|
||||
The `ResourceDict` struct contains all resource namespaces:
|
||||
- `fonts: IndexMap<Arc<str>, ObjRef>` — /Font namespace
|
||||
- `xobjects: IndexMap<Arc<str>, ObjRef>` — /XObject namespace
|
||||
- `ext_gstates: IndexMap<Arc<str>, ObjRef>` — /ExtGState namespace
|
||||
- `color_spaces: IndexMap<Arc<str>, PdfObject>` — /ColorSpace namespace (supports inline arrays)
|
||||
- `shadings: IndexMap<Arc<str>, ObjRef>` — /Shading namespace
|
||||
- `patterns: IndexMap<Arc<str>, ObjRef>` — /Pattern namespace
|
||||
- `properties: IndexMap<Arc<str>, ObjRef>` — /Properties namespace
|
||||
- `proc_set: Vec<Arc<str>>` — /ProcSet (deprecated, informational only)
|
||||
|
||||
### merge_resources Function
|
||||
|
||||
The `merge_resources(ancestor: &ResourceDict, child: &PdfObject) -> ResourceDict` function implements per-namespace merging with per-key last-write-wins semantics:
|
||||
|
||||
1. Starts with a clone of the ancestor's ResourceDict
|
||||
2. For each namespace in the child's /Resources:
|
||||
- Merges the child's entries into the ancestor's entries
|
||||
- Per-key last-write-wins: if child has the same key as ancestor, child's value wins
|
||||
- Different keys are accumulated (not replaced)
|
||||
3. Returns the merged ResourceDict
|
||||
|
||||
### Page Tree Integration (`crates/pdftract-core/src/parser/pages.rs`)
|
||||
|
||||
The `InheritedAttrs` struct tracks the accumulated ResourceDict during page tree traversal:
|
||||
- `merge_inherited_attrs()`: Merges /Resources from /Pages nodes into the accumulator
|
||||
- `build_page_dict()`: Merges /Resources from leaf /Page nodes and stores the result in `PageDict.resources: Arc<ResourceDict>`
|
||||
- When a page has no /Resources, it inherits the parent's Arc (memory efficiency via Arc::ptr_eq)
|
||||
|
||||
## Acceptance Criteria Verification
|
||||
|
||||
### ✅ 1. Critical test: 3-level resource inheritance
|
||||
|
||||
Tests: `test_resource_inheritance_three_level` (pages.rs), `test_three_level_inheritance` (resources.rs)
|
||||
|
||||
The 3-level inheritance test creates:
|
||||
- Grandparent /Pages with /F1 and /Im1
|
||||
- Parent /Pages adds /F2
|
||||
- Page 1 adds /F3 and overrides /F1
|
||||
- Page 2 has no /Resources (inherits all)
|
||||
|
||||
Result: Page 1 has F1 (overridden), F2 (inherited), F3 (new), Im1 (inherited). Page 2 has F1, F2, Im1 (all inherited).
|
||||
|
||||
### ✅ 2. Per-key override test
|
||||
|
||||
Test: `test_merge_fonts_last_write_wins` (resources.rs)
|
||||
|
||||
Verifies that when a page declares `/Font << /F1 >>`, the F1 on the page overrides F1 on the ancestor (last-write-wins per-key).
|
||||
|
||||
### ✅ 3. /Resources missing on page: inherits parent's
|
||||
|
||||
Tests: `test_resource_inheritance_page_without_resources` (pages.rs), `test_merge_null_child_returns_ancestor` (resources.rs)
|
||||
|
||||
When a page has no /Resources, it inherits the parent's ResourceDict. The test verifies that the inherited resources are present and accessible.
|
||||
|
||||
### ✅ 3b. Arc<ResourceDict> is the SAME instance (Arc::ptr_eq)
|
||||
|
||||
Test: `test_resource_inheritance_arc_sharing` (pages.rs)
|
||||
|
||||
When multiple pages have no /Resources, they share the same Arc<ResourceDict> instance for memory efficiency. The test uses `Arc::ptr_eq()` to verify this.
|
||||
|
||||
### ✅ 4. ColorSpace inline-array test
|
||||
|
||||
Test: `test_merge_colorspace_inline_array` (resources.rs)
|
||||
|
||||
Verifies that ColorSpace values can be inline arrays (not just refs). The test creates an inline CalRGB color space array and verifies it's preserved in the merged dict.
|
||||
|
||||
### ✅ 5. Empty root /Resources: empty ResourceDict propagates
|
||||
|
||||
Test: `test_resource_inheritance_empty_root` (pages.rs)
|
||||
|
||||
When the root /Pages has an empty /Resources dict, the empty ResourceDict propagates to all leaf pages. The test verifies that the page's resources are empty.
|
||||
|
||||
### ✅ 6. INV-8 maintained: no panics on arbitrary input
|
||||
|
||||
Tests: All fuzz tests in `proptests` modules (pages.rs, resources.rs, catalog.rs, outline.rs, ocg.rs)
|
||||
|
||||
The property tests verify that:
|
||||
- `fuzz_parse_rect_no_panics`: parse_rect never panics on arbitrary arrays
|
||||
- `fuzz_build_page_dict_no_panics`: build_page_dict never panics on arbitrary input
|
||||
- `fuzz_flatten_page_tree_no_panics`: flatten_page_tree handles arbitrary /Pages structures
|
||||
- `fuzz_rotate_clamping_no_panics`: arbitrary rotate values are handled without panicking
|
||||
|
||||
## Test Results
|
||||
|
||||
All 18 resource-related tests pass:
|
||||
- `test_empty_resource_dict`
|
||||
- `test_resource_dict_not_empty`
|
||||
- `test_merge_fonts_last_write_wins`
|
||||
- `test_merge_xobjects`
|
||||
- `test_merge_colorspace_inline_array`
|
||||
- `test_merge_procset_dedup`
|
||||
- `test_merge_null_child_returns_ancestor`
|
||||
- `test_three_level_inheritance`
|
||||
- `test_merge_all_namespaces`
|
||||
|
||||
All 26 page tree tests pass:
|
||||
- `test_resource_inheritance_three_level`
|
||||
- `test_resource_inheritance_page_without_resources`
|
||||
- `test_resource_inheritance_arc_sharing`
|
||||
- `test_resource_inheritance_empty_root`
|
||||
- ... and 22 other page tree tests
|
||||
|
||||
All 16 fuzz tests pass:
|
||||
- `fuzz_parse_rect_no_panics`
|
||||
- `fuzz_build_page_dict_no_panics`
|
||||
- `fuzz_flatten_page_tree_no_panics`
|
||||
- `fuzz_rotate_clamping_no_panics`
|
||||
- ... and 12 other fuzz tests
|
||||
|
||||
## Conclusion
|
||||
|
||||
The per-page Resource dictionary inheritance implementation is complete and correct. All acceptance criteria are met, and the tests cover the critical cases including 3-level inheritance, per-key override, Arc sharing, ColorSpace inline arrays, empty root /Resources, and INV-8 (no panics on arbitrary input).
|
||||
Loading…
Add table
Reference in a new issue