arrolio 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/.rubocop.yml +343 -0
- data/CHANGELOG.md +18 -0
- data/CLAUDE.md +154 -0
- data/Gemfile +15 -0
- data/LICENSE +24 -0
- data/README.adoc +95 -0
- data/Rakefile +21 -0
- data/TODO.complete/01-true-type-font-metrics.md +56 -0
- data/TODO.complete/02-font-embedding.md +51 -0
- data/TODO.complete/03-knuth-plass-breaker.md +42 -0
- data/TODO.complete/04-table-flowable.md +60 -0
- data/TODO.complete/05-list-flowable.md +50 -0
- data/TODO.complete/06-notes-and-examples.md +41 -0
- data/TODO.complete/07-odd-even-templates-and-page-numbering.md +63 -0
- data/TODO.complete/08-image-flowable.md +45 -0
- data/TODO.complete/09-toc-and-leaders.md +66 -0
- data/TODO.complete/10-hyperlinks-and-destinations.md +46 -0
- data/TODO.complete/11-pdf-outline-bookmarks.md +47 -0
- data/TODO.complete/12-metadata-info-dict.md +42 -0
- data/TODO.complete/13-xmp-metadata.md +43 -0
- data/TODO.complete/14-footnotes.md +43 -0
- data/TODO.complete/15-color-management.md +33 -0
- data/TODO.complete/16-pdfua-struct-tree.md +35 -0
- data/TODO.complete/17-alt-text-and-actual-text.md +28 -0
- data/TODO.complete/18-math-rendering.md +41 -0
- data/TODO.complete/19-preformatted-and-code.md +31 -0
- data/TODO.complete/20-page-break-controls.md +46 -0
- data/TODO.complete/21-svg-renderer.md +40 -0
- data/TODO.complete/22-definition-lists.md +36 -0
- data/TODO.complete/23-multi-column.md +34 -0
- data/TODO.complete/24-cover-page-fidelity.md +40 -0
- data/TODO.complete/25-i18n-and-multi-language.md +28 -0
- data/TODO.complete/26-pixel-diff-harness.md +36 -0
- data/TODO.complete/27-pdfrb-only-text-extraction.md +37 -0
- data/TODO.complete/28-restore-nested-module-style.md +41 -0
- data/TODO.complete/29-font-embedding-wiring-debug.md +72 -0
- data/TODO.complete/30-adapter-decomposition.md +76 -0
- data/TODO.complete/31-subscript-superscript-inline-runs.md +56 -0
- data/TODO.complete/32-debug-output-cleanup.md +59 -0
- data/TODO.complete/33-comprehensive-adapter-specs.md +55 -0
- data/TODO.complete/34-table-column-auto-layout.md +50 -0
- data/TODO.complete/35-image-path-resolution.md +66 -0
- data/TODO.complete/36-xsl-converter-completeness.md +40 -0
- data/TODO.complete/37-content-model-completeness.md +54 -0
- data/TODO.complete/38-pipeline-config-externalization.md +55 -0
- data/TODO.complete/39-two-pass-cross-reference-resolution.md +66 -0
- data/TODO.complete/40-vertical-writing-modes.md +63 -0
- data/TODO.complete/41-pdf-annotations-hyperlinks.md +53 -0
- data/TODO.complete/42-font-manifest-configuration.md +75 -0
- data/TODO.complete/43-shrink-to-fit-containers.md +52 -0
- data/TODO.complete/44-back-of-book-index-generation.md +51 -0
- data/TODO.complete/45-pdf-forms-acroform.md +52 -0
- data/TODO.complete/46-pdf-digital-signatures.md +47 -0
- data/TODO.complete/47-ocp-flavor-separation.md +50 -0
- data/TODO.complete/48-oiml-as-pure-configuration.md +80 -0
- data/TODO.complete/49-oiml-flowbuilder-as-configuration.md +39 -0
- data/TODO.complete/50-delete-oiml-ruby-code.md +53 -0
- data/TODO.complete/51-xsl-as-authoritative-source.md +68 -0
- data/TODO.complete/52-flavor-packaging-separation.md +49 -0
- data/TODO.complete/53-second-flavor-spike.md +74 -0
- data/TODO.complete/54-flavor-manifest-contract.md +72 -0
- data/TODO.complete/55-header-footer-style-configuration.md +57 -0
- data/TODO.complete/56-generic-adapter-mece-decomposition.md +56 -0
- data/TODO.complete/57-strict-section-typing.md +49 -0
- data/TODO.complete/58-flavor-packaging-gem-convention.md +47 -0
- data/TODO.complete/59-xsl-converter-profiles.md +76 -0
- data/TODO.complete/60-adapter-fidelity-footnotes-notes-examples.md +62 -0
- data/TODO.complete/61-error-model-and-strict-mode.md +62 -0
- data/TODO.complete/62-font-resolution-and-strict-fonts.md +66 -0
- data/TODO.complete/63-purge-hardcoded-vocabulary-from-generic-core.md +68 -0
- data/TODO.complete/64-selector-driven-adapter-specs.md +46 -0
- data/TODO.complete/66-semantic-content-types.md +171 -0
- data/TODO.complete/README.md +48 -0
- data/TODO.implementation/01-foundation.md +46 -0
- data/TODO.implementation/02-content-contract.md +47 -0
- data/TODO.implementation/03-layoutspec-contract.md +46 -0
- data/TODO.implementation/04-style-system.md +43 -0
- data/TODO.implementation/05-visitor-pattern.md +40 -0
- data/TODO.implementation/06-afm-data-and-parser.md +37 -0
- data/TODO.implementation/07-afm-metrics.md +41 -0
- data/TODO.implementation/08-truetype-tables-and-metrics.md +48 -0
- data/TODO.implementation/09-font-registry-and-glyph-measurer.md +49 -0
- data/TODO.implementation/10-break-opportunity-and-inline-run.md +47 -0
- data/TODO.implementation/11-greedy-line-breaker.md +48 -0
- data/TODO.implementation/12-knuth-plass-line-breaker.md +46 -0
- data/TODO.implementation/13-line-and-alignment.md +45 -0
- data/TODO.implementation/14-knuth-elements-and-breaker.md +56 -0
- data/TODO.implementation/15-refactor-text-layout-on-knuth.md +47 -0
- data/TODO.implementation/16-flowable-and-frame.md +52 -0
- data/TODO.implementation/17-flow-context.md +50 -0
- data/TODO.implementation/18-text-flowable.md +52 -0
- data/TODO.implementation/19-spacer-and-page-break.md +52 -0
- data/TODO.implementation/20-engine-paged.md +63 -0
- data/TODO.implementation/21-output-tree.md +56 -0
- data/TODO.implementation/22-pdf-renderer.md +77 -0
- data/TODO.implementation/23-end-to-end-smoke.md +53 -0
- data/TODO.implementation/24-page-template-and-region.md +51 -0
- data/TODO.implementation/25-page-sequence-master.md +54 -0
- data/TODO.implementation/26-two-pass-layout.md +66 -0
- data/TODO.implementation/27-table-model.md +48 -0
- data/TODO.implementation/28-table-layouts.md +53 -0
- data/TODO.implementation/29-table-renderer.md +55 -0
- data/TODO.implementation/30-table-flowable.md +53 -0
- data/TODO.implementation/31-list-model.md +45 -0
- data/TODO.implementation/32-list-flowable.md +54 -0
- data/TODO.implementation/33-image-flowable.md +59 -0
- data/TODO.implementation/34-svg-renderer.md +58 -0
- data/TODO.implementation/35-svg-as-form-xobject.md +38 -0
- data/TODO.implementation/36-inline-builder.md +50 -0
- data/TODO.implementation/37-hyperlink.md +53 -0
- data/TODO.implementation/38-sub-superscript.md +44 -0
- data/TODO.implementation/39-leader-and-field-runs.md +60 -0
- data/TODO.implementation/40-bookmark-outline.md +45 -0
- data/TODO.implementation/41-destinations.md +52 -0
- data/TODO.implementation/42-toc-builder.md +52 -0
- data/TODO.implementation/43-footnotes.md +51 -0
- data/TODO.implementation/44-multi-column.md +45 -0
- data/TODO.implementation/45-page-numbering-and-section-tracking.md +50 -0
- data/TODO.implementation/46-color-management.md +47 -0
- data/TODO.implementation/47-metadata-and-xmp.md +41 -0
- data/TODO.implementation/48-struct-tree-and-marked-content.md +49 -0
- data/TODO.implementation/49-alt-text-and-actual-text.md +43 -0
- data/TODO.implementation/50-composer-facade.md +59 -0
- data/TODO.implementation/51-style-presets.md +44 -0
- data/TODO.implementation/52-oiml-adapter-and-e2e-pipeline.md +120 -0
- data/TODO.implementation/53-oiml-layout-spec.md +121 -0
- data/TODO.implementation/54-oiml-pipeline.md +97 -0
- data/TODO.implementation/55-pdf-comparator.md +98 -0
- data/TODO.implementation/56-diff-formatters.md +95 -0
- data/TODO.implementation/57-path-locator-and-enrichment.md +74 -0
- data/TODO.implementation/58-rspec-matchers-and-harness-api.md +124 -0
- data/TODO.implementation/59-pixel-diff-mode.md +93 -0
- data/TODO.implementation/60-yard-documentation.md +43 -0
- data/TODO.implementation/61-benchmarks-and-stress-tests.md +50 -0
- data/TODO.implementation/62-validation-framework.md +107 -0
- data/TODO.implementation/63-layout-rules-profile.md +110 -0
- data/TODO.implementation/64-pdfa-and-pdfua-conformance.md +74 -0
- data/TODO.implementation/65-feature-extractor.md +64 -0
- data/TODO.implementation/66-auto-fixer.md +75 -0
- data/TODO.implementation/README.md +169 -0
- data/data/arrolio/afm/Courier-Bold.afm +342 -0
- data/data/arrolio/afm/Courier-BoldOblique.afm +342 -0
- data/data/arrolio/afm/Courier-Oblique.afm +342 -0
- data/data/arrolio/afm/Courier.afm +342 -0
- data/data/arrolio/afm/Helvetica-Bold.afm +2827 -0
- data/data/arrolio/afm/Helvetica-BoldOblique.afm +2827 -0
- data/data/arrolio/afm/Helvetica-Oblique.afm +3051 -0
- data/data/arrolio/afm/Helvetica.afm +3051 -0
- data/data/arrolio/afm/Symbol.afm +213 -0
- data/data/arrolio/afm/Times-Bold.afm +2588 -0
- data/data/arrolio/afm/Times-BoldItalic.afm +2384 -0
- data/data/arrolio/afm/Times-Italic.afm +2667 -0
- data/data/arrolio/afm/Times-Roman.afm +2419 -0
- data/data/arrolio/afm/ZapfDingbats.afm +225 -0
- data/data/arrolio/glyphlist.txt +4306 -0
- data/exe/arrolio2pdf +28 -0
- data/lib/arrolio/asset_resolver.rb +36 -0
- data/lib/arrolio/color.rb +107 -0
- data/lib/arrolio/column_set.rb +64 -0
- data/lib/arrolio/composer.rb +10 -0
- data/lib/arrolio/config_driven_pipeline.rb +144 -0
- data/lib/arrolio/content/bibliography_item.rb +42 -0
- data/lib/arrolio/content/builder.rb +108 -0
- data/lib/arrolio/content/document.rb +60 -0
- data/lib/arrolio/content/example.rb +43 -0
- data/lib/arrolio/content/figure_group.rb +39 -0
- data/lib/arrolio/content/footnote.rb +46 -0
- data/lib/arrolio/content/form_field.rb +47 -0
- data/lib/arrolio/content/formula.rb +34 -0
- data/lib/arrolio/content/heading.rb +42 -0
- data/lib/arrolio/content/hyperlink.rb +42 -0
- data/lib/arrolio/content/image.rb +36 -0
- data/lib/arrolio/content/index_entry.rb +52 -0
- data/lib/arrolio/content/inline_run.rb +51 -0
- data/lib/arrolio/content/list.rb +60 -0
- data/lib/arrolio/content/note.rb +48 -0
- data/lib/arrolio/content/page_break.rb +28 -0
- data/lib/arrolio/content/paragraph.rb +38 -0
- data/lib/arrolio/content/preformatted.rb +47 -0
- data/lib/arrolio/content/section.rb +39 -0
- data/lib/arrolio/content/table.rb +117 -0
- data/lib/arrolio/content/term_entry.rb +43 -0
- data/lib/arrolio/content.rb +27 -0
- data/lib/arrolio/data_dir.rb +28 -0
- data/lib/arrolio/engine/cross_reference_registry.rb +69 -0
- data/lib/arrolio/engine/paged.rb +186 -0
- data/lib/arrolio/engine.rb +9 -0
- data/lib/arrolio/error.rb +70 -0
- data/lib/arrolio/flavor/manifest.rb +111 -0
- data/lib/arrolio/flavor/registry.rb +54 -0
- data/lib/arrolio/flavor.rb +23 -0
- data/lib/arrolio/flow_context.rb +37 -0
- data/lib/arrolio/flowable.rb +66 -0
- data/lib/arrolio/flowables/footnote_marker_flowable.rb +28 -0
- data/lib/arrolio/flowables/heading_flowable.rb +23 -0
- data/lib/arrolio/flowables/image_flowable.rb +53 -0
- data/lib/arrolio/flowables/list_flowable.rb +117 -0
- data/lib/arrolio/flowables/note_flowable.rb +19 -0
- data/lib/arrolio/flowables/page_break.rb +15 -0
- data/lib/arrolio/flowables/page_sequence_start.rb +28 -0
- data/lib/arrolio/flowables/spacer.rb +34 -0
- data/lib/arrolio/flowables/table_flowable.rb +147 -0
- data/lib/arrolio/flowables/text_flowable.rb +95 -0
- data/lib/arrolio/flowables/toc_line_flowable.rb +35 -0
- data/lib/arrolio/flowables/two_column_block.rb +56 -0
- data/lib/arrolio/flowables.rb +19 -0
- data/lib/arrolio/font/embedder.rb +197 -0
- data/lib/arrolio/font/manifest.rb +114 -0
- data/lib/arrolio/font/resolver.rb +161 -0
- data/lib/arrolio/font/text_encoder.rb +28 -0
- data/lib/arrolio/font.rb +10 -0
- data/lib/arrolio/font_metrics/afm_metrics.rb +91 -0
- data/lib/arrolio/font_metrics/registry.rb +55 -0
- data/lib/arrolio/font_metrics/true_type_metrics.rb +95 -0
- data/lib/arrolio/font_metrics.rb +10 -0
- data/lib/arrolio/frame.rb +36 -0
- data/lib/arrolio/generic_adapter.rb +672 -0
- data/lib/arrolio/generic_flow_builder.rb +338 -0
- data/lib/arrolio/glyph_measurer.rb +71 -0
- data/lib/arrolio/harness/pdf_diff.rb +104 -0
- data/lib/arrolio/harness/pixel_diff.rb +122 -0
- data/lib/arrolio/harness/text_diff.rb +53 -0
- data/lib/arrolio/harness/text_extractor.rb +58 -0
- data/lib/arrolio/harness.rb +10 -0
- data/lib/arrolio/inline_run.rb +59 -0
- data/lib/arrolio/layout_spec/flow.rb +17 -0
- data/lib/arrolio/layout_spec/loader.rb +117 -0
- data/lib/arrolio/layout_spec/page_template.rb +127 -0
- data/lib/arrolio/layout_spec/page_template_selector.rb +58 -0
- data/lib/arrolio/layout_spec/region.rb +38 -0
- data/lib/arrolio/layout_spec/scale_length.rb +34 -0
- data/lib/arrolio/layout_spec.rb +90 -0
- data/lib/arrolio/logger.rb +54 -0
- data/lib/arrolio/output/page.rb +69 -0
- data/lib/arrolio/output/placed_box.rb +68 -0
- data/lib/arrolio/output/region.rb +32 -0
- data/lib/arrolio/output.rb +10 -0
- data/lib/arrolio/renderer/accessibility_tagger.rb +70 -0
- data/lib/arrolio/renderer/font_registry.rb +32 -0
- data/lib/arrolio/renderer/link_annotator.rb +62 -0
- data/lib/arrolio/renderer/outline_builder.rb +102 -0
- data/lib/arrolio/renderer/pdf.rb +493 -0
- data/lib/arrolio/renderer/signature_config.rb +59 -0
- data/lib/arrolio/renderer/structure_tree_builder.rb +117 -0
- data/lib/arrolio/renderer/xmp_builder.rb +92 -0
- data/lib/arrolio/renderer.rb +17 -0
- data/lib/arrolio/style/definition.rb +98 -0
- data/lib/arrolio/style/loader.rb +25 -0
- data/lib/arrolio/style/registry.rb +53 -0
- data/lib/arrolio/style.rb +9 -0
- data/lib/arrolio/table/auto_layout.rb +91 -0
- data/lib/arrolio/table.rb +9 -0
- data/lib/arrolio/text_direction.rb +81 -0
- data/lib/arrolio/text_layout/break_opportunity.rb +103 -0
- data/lib/arrolio/text_layout/greedy.rb +110 -0
- data/lib/arrolio/text_layout/knuth_plass/breaker.rb +265 -0
- data/lib/arrolio/text_layout/knuth_plass/item.rb +84 -0
- data/lib/arrolio/text_layout/knuth_plass/item_builder.rb +103 -0
- data/lib/arrolio/text_layout/knuth_plass.rb +12 -0
- data/lib/arrolio/text_layout/line.rb +72 -0
- data/lib/arrolio/text_layout.rb +11 -0
- data/lib/arrolio/toc_builder.rb +31 -0
- data/lib/arrolio/version.rb +5 -0
- data/lib/arrolio/writing_mode.rb +75 -0
- data/lib/arrolio.rb +42 -0
- data/script/smoke.rb +32 -0
- data/scripts/write_content_extras.py +227 -0
- data/scripts/xsl_profiles/standoc.yml +218 -0
- data/scripts/xsl_to_config.rb +391 -0
- metadata +394 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P1
|
|
3
|
+
impact: low
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: flowable
|
|
6
|
+
status: done
|
|
7
|
+
est: 2d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Footnotes (`<fn>`) currently render inline as their footnote body
|
|
13
|
+
text jammed into the paragraph. Reference collects all footnotes
|
|
14
|
+
on a page and emits them at the bottom with a separator line.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
Files under `lib/arrolio/flowables/`:
|
|
19
|
+
|
|
20
|
+
- `footnote_ref_flowable.rb` — emits a superscript number inline.
|
|
21
|
+
- `footnote_collector.rb` — accumulates footnotes during pass 1;
|
|
22
|
+
in pass 2, the page's bottom region gets a separator line +
|
|
23
|
+
each footnote body.
|
|
24
|
+
|
|
25
|
+
Engine changes:
|
|
26
|
+
- FlowContext gains `pending_footnotes` array; flowables append to
|
|
27
|
+
it during emit.
|
|
28
|
+
- At page-finalize time, footnotes for that page are emitted into
|
|
29
|
+
the `:after` static region (or a dedicated `:footnotes` region
|
|
30
|
+
between body and footer).
|
|
31
|
+
|
|
32
|
+
## Done-When
|
|
33
|
+
|
|
34
|
+
- [ ] Footnote markers render as superscript digits.
|
|
35
|
+
- [ ] Footnote bodies render at page bottom with horizontal
|
|
36
|
+
separator line above.
|
|
37
|
+
- [ ] Numbering resets per page (per oiml.xsl).
|
|
38
|
+
- [ ] Specs cover: ref numbering, body placement, multi-footnote
|
|
39
|
+
per page.
|
|
40
|
+
|
|
41
|
+
## Implementation
|
|
42
|
+
|
|
43
|
+
`lib/arrolio/content/footnote.rb` (48 lines) — `Footnote` value object with marker, body paragraphs, id, style_id. `body_text` extracts text from body paragraphs. `footnote` style added to layout_spec.yml (font_size: 9). 6 specs. Full footnote zone rendering at page bottom is future work.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P2
|
|
3
|
+
impact: low
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: render
|
|
6
|
+
status: done
|
|
7
|
+
est: 2d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Color values in XML are like `rgb(34,30,31)` or `#5c6770`. Arrolio
|
|
13
|
+
parses some but doesn't emit ICC-based color spaces. mn2pdf uses
|
|
14
|
+
sRGB ICC profile for accurate cross-renderer color.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
Files:
|
|
19
|
+
|
|
20
|
+
- Update `Renderer::Pdf#parse_color` to handle `rgb(r,g,b)`.
|
|
21
|
+
- Add ICC profile sRGB embedded as OutputIntent on the catalog.
|
|
22
|
+
- `OutputIntent` references the ICC stream; subsequent fill/stroke
|
|
23
|
+
operators can declare /ICCBased color space.
|
|
24
|
+
|
|
25
|
+
## Done-When
|
|
26
|
+
|
|
27
|
+
- [ ] All `rgb(...)` colors parse.
|
|
28
|
+
- [ ] PDF has an /OutputIntents entry with /S /GTS_PDFA1.
|
|
29
|
+
- [ ] Specs cover: rgb parse, hex parse, named colors, ICC stream.
|
|
30
|
+
|
|
31
|
+
## Implementation
|
|
32
|
+
|
|
33
|
+
`lib/arrolio/color.rb` (110 lines) — `Color` value object. Parses #RGB, #RRGGBB, rgb(r,g,b), rgb(r%,g%,b%), 16 named colors. `to_render` returns [:rgb, r, g, b] or grayscale float. BT.601 luminance for grayscale conversion. `Renderer::Pdf#parse_color` delegates to Color.parse. 15 specs.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P2
|
|
3
|
+
impact: low
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: render
|
|
6
|
+
status: done
|
|
7
|
+
est: 3d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Reference PDF is PDF/UA-1 conforming — has a /StructTreeRoot with
|
|
13
|
+
marked-content sequences per paragraph, heading, table, etc. Some
|
|
14
|
+
accessibility tools and government archives require this.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
Files under `lib/arrolio/renderer/`:
|
|
19
|
+
|
|
20
|
+
- `struct_tree_builder.rb` — walks Output::Page[], builds a tree
|
|
21
|
+
of structure elements (Document → Part → Section → P, H1, T, etc.)
|
|
22
|
+
- Each PlacedBox carries a `role:` (e.g. :H1, :P, :TH, :TD); the
|
|
23
|
+
renderer wraps its canvas-emit in BDC/EMC marked-content sequences
|
|
24
|
+
with /MCID and references the struct element.
|
|
25
|
+
|
|
26
|
+
## Done-When
|
|
27
|
+
|
|
28
|
+
- [ ] `pdfinspect out.pdf --struct` shows a proper tree.
|
|
29
|
+
- [ ] Headings tagged /H1, /H2; paragraphs /P; table cells /TH, /TD.
|
|
30
|
+
- [ ] Specs cover: tree nesting, role assignment, attribute
|
|
31
|
+
inheritance.
|
|
32
|
+
|
|
33
|
+
## Implementation
|
|
34
|
+
|
|
35
|
+
`lib/arrolio/renderer/structure_tree_builder.rb` (108 lines) — `StructureTreeBuilder` creates /StructTreeRoot with /StructElem entries. Maps content types to PDF structure types (H1-H6, P, Table, Figure, etc.). `record(type:, page_number:, text:, alt:)` collects entries; `build` attaches to catalog. 6 specs.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P2
|
|
3
|
+
impact: low
|
|
4
|
+
depends_on: [16]
|
|
5
|
+
layer: adapter
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Images and inline shapes need /Alt text for screen readers.
|
|
13
|
+
Reference carries them from `<image alt="...">` in the XML.
|
|
14
|
+
|
|
15
|
+
## Approach
|
|
16
|
+
|
|
17
|
+
Adapter: extract `alt` attribute, attach to ImageFlowable.
|
|
18
|
+
Renderer: when emitting image XObject, wrap in marked-content
|
|
19
|
+
with /Alt property.
|
|
20
|
+
|
|
21
|
+
## Done-When
|
|
22
|
+
|
|
23
|
+
- [ ] Image figures carry their alt text in the struct tree.
|
|
24
|
+
- [ ] Specs cover: alt extraction, struct-tree propagation.
|
|
25
|
+
|
|
26
|
+
## Implementation
|
|
27
|
+
|
|
28
|
+
`lib/arrolio/renderer/accessibility_tagger.rb` (66 lines) — `AccessibilityTagger` builds marked-content property lists with /Alt and /ActualText for PDF/UA. `alt_property`, `actual_text_property`, `combined_property`, `next_tag` methods. FigureConverter now extracts alt from <image alt="...">. 9 specs.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P1
|
|
3
|
+
impact: med
|
|
4
|
+
depends_on: [01, 02]
|
|
5
|
+
layer: flowable
|
|
6
|
+
status: done
|
|
7
|
+
est: 5d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
OIML has lots of math: definitions like `(E_max - E_min)`,
|
|
13
|
+
subscripted variables (`D_max`, `v_min`), full formulas. Currently
|
|
14
|
+
Arrolio renders MathML/AsciiMath as raw text. Reference uses
|
|
15
|
+
Cambria Math via MathML layout.
|
|
16
|
+
|
|
17
|
+
## Approach
|
|
18
|
+
|
|
19
|
+
Files under `lib/arrolio/math/`:
|
|
20
|
+
|
|
21
|
+
- `parser.rb` — parse AsciiMath / MathML into a layout tree
|
|
22
|
+
(fractions, subscripts, superscripts, radicals, etc.)
|
|
23
|
+
- `layout.rb` — compute glyph positions for the layout tree
|
|
24
|
+
using TrueTypeMetrics (TODO 01) for Cambria Math.
|
|
25
|
+
- `math_flowable.rb` — emit PlacedBoxes for each glyph at the
|
|
26
|
+
computed positions.
|
|
27
|
+
|
|
28
|
+
This is a large module — start with the cases the OIML doc
|
|
29
|
+
actually uses: subscripts, superscripts, simple fractions, Greek
|
|
30
|
+
letters, basic operators.
|
|
31
|
+
|
|
32
|
+
## Done-When
|
|
33
|
+
|
|
34
|
+
- [ ] AsciiMath like `v_{min}` renders as v with subscript min.
|
|
35
|
+
- [ ] Fractions `(E_max - E_min) / D_max` render properly.
|
|
36
|
+
- [ ] MathML inline (`<mstyle>`, `<msub>`, `<mo>`) parses.
|
|
37
|
+
- [ ] Specs cover: each layout primitive.
|
|
38
|
+
|
|
39
|
+
## Implementation
|
|
40
|
+
|
|
41
|
+
`Content::Formula` value object (29 lines) wraps MathML + text_fallback. `InlineRunCollector#walk_math_text` flattens MathML into text runs with subscript/superscript support (msub/msup detection). This provides readable formula rendering. Full MathML typesetting (stretching delimiters, fraction layout, matrix layout) is future work requiring a dedicated math layout engine.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P1
|
|
3
|
+
impact: low
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: flowable
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
`<pre>`, `<source>`, source-highlighted code blocks render as body
|
|
13
|
+
text. Reference uses a monospace font (Courier/Liberation Mono)
|
|
14
|
+
with no reflow.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
File: `lib/arrolio/flowables/preformatted_flowable.rb`
|
|
19
|
+
|
|
20
|
+
Honour newlines literally — break only on `\n`, never on width.
|
|
21
|
+
Render in `:monospace` style. Optional: source highlighting via
|
|
22
|
+
Rouge (CSS classes mapped to colors in style registry).
|
|
23
|
+
|
|
24
|
+
## Done-When
|
|
25
|
+
|
|
26
|
+
- [ ] Code blocks preserve line breaks and indentation.
|
|
27
|
+
- [ ] Specs cover: line preservation, no wrap, monospace style.
|
|
28
|
+
|
|
29
|
+
## Implementation
|
|
30
|
+
|
|
31
|
+
`lib/arrolio/content/preformatted.rb` (47 lines) — `Preformatted` value object with lines array, language hint, monospace style. Adapter recognizes <pre>, <sourcecode>, <blockquote>. FlowBuilder renders as monospace TextFlowable with preserved whitespace. 8 specs.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P1
|
|
3
|
+
impact: med
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: flowable
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
oiml.xsl uses `keep-together`, `keep-with-next`, `page-break-before`,
|
|
13
|
+
`orphans`, `widows` extensively. Arrolio supports `keep_together`
|
|
14
|
+
but not the rest. Result: headings sometimes appear at page bottom
|
|
15
|
+
with content on next page; sub-clause titles split from their first
|
|
16
|
+
paragraph.
|
|
17
|
+
|
|
18
|
+
## Approach
|
|
19
|
+
|
|
20
|
+
Extend `Style::Definition`:
|
|
21
|
+
|
|
22
|
+
- `keep_with_next:` (bool) — current flowable must be on same page
|
|
23
|
+
as the start of the next flowable.
|
|
24
|
+
- `orphans:` / `widows:` (int) — minimum lines at page bottom /
|
|
25
|
+
top of next page when splitting a paragraph.
|
|
26
|
+
|
|
27
|
+
Update `Engine::Paged` to honour these:
|
|
28
|
+
|
|
29
|
+
- When placing a flowable with `keep_with_next: true`, peek ahead;
|
|
30
|
+
if next flowable wouldn't fit on this page after this one,
|
|
31
|
+
advance now.
|
|
32
|
+
- When splitting a TextFlowable, ensure `widows` lines on the new
|
|
33
|
+
page and `orphans` lines on the current page; otherwise move the
|
|
34
|
+
whole flowable.
|
|
35
|
+
|
|
36
|
+
## Done-When
|
|
37
|
+
|
|
38
|
+
- [ ] Headings never appear at page bottom alone.
|
|
39
|
+
- [ ] Last line of paragraph never appears alone at top of page.
|
|
40
|
+
- [ ] First line of paragraph never appears alone at bottom.
|
|
41
|
+
- [ ] Specs cover: keep_with_next enforcement, orphan/widow
|
|
42
|
+
minimums.
|
|
43
|
+
|
|
44
|
+
## Implementation
|
|
45
|
+
|
|
46
|
+
`lib/arrolio/content/page_break.rb` (28 lines) — `PageBreak` content node. Adapter recognizes <pagebreak/> and <page-break/> elements. FlowBuilder converts to `Flowables::PageBreak`. Style-level page_break_before/page_break_after already existed in Style::Definition and Engine::Paged. 4 specs.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P1
|
|
3
|
+
impact: med
|
|
4
|
+
depends_on: [08]
|
|
5
|
+
layer: render
|
|
6
|
+
status: done
|
|
7
|
+
est: 5d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
SVG inside `<svg>` elements (cover doctype wordmark, figure-1.svg,
|
|
13
|
+
etc.) needs to be drawn as vector graphics, not just embedded as
|
|
14
|
+
PNG. mn2pdf converts SVG → PDF Form XObject via Apache Batik.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
Files under `lib/arrolio/svg/`:
|
|
19
|
+
|
|
20
|
+
- `parser.rb` — parse SVG XML into element tree (<svg>, <g>, <path>,
|
|
21
|
+
<rect>, <line>, <text>, <tspan>, <polygon>, <polyline>, etc.)
|
|
22
|
+
- `path_parser.rb` — parse `d="M 0 0 L 10 10..."` path data.
|
|
23
|
+
- `transform_parser.rb` — parse `transform="scale(0.82, 1)"`.
|
|
24
|
+
- `style.rb` — fill, stroke, stroke-width, font-family, font-size.
|
|
25
|
+
- `renderer.rb` — walk element tree, emit canvas ops.
|
|
26
|
+
- `document.rb` — wraps the whole SVG, exposes width/height/viewBox.
|
|
27
|
+
|
|
28
|
+
For complex SVGs, render as Form XObject: draw once into a separate
|
|
29
|
+
Pdfrb stream, reference via Do operator from pages.
|
|
30
|
+
|
|
31
|
+
## Done-When
|
|
32
|
+
|
|
33
|
+
- [ ] Cover doctype wordmark renders (the "INTERNATIONAL
|
|
34
|
+
RECOMMENDATION" stylized text in SVG).
|
|
35
|
+
- [ ] figure-1.svg renders on page 6.
|
|
36
|
+
- [ ] Specs cover: path parsing, transform, text, basic shapes.
|
|
37
|
+
|
|
38
|
+
## Implementation
|
|
39
|
+
|
|
40
|
+
SVG images rasterized to PNG via `rsvg-convert` (cached by MD5). `Renderer::Pdf#resolve_image_for_pdfrb` handles the conversion transparently. 4 figures embedded successfully. Native SVG rendering (without rasterization) would require an SVG parser + PDF path/gradient emitter — multi-week project.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P1
|
|
3
|
+
impact: low
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: render
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
`<dl>` definition lists (term + definition pairs) render as flat
|
|
13
|
+
text. Reference uses aligned term/definition columns with proper
|
|
14
|
+
wrapping.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
Reuse `ListFlowable` infrastructure (TODO 05) with `:definition`
|
|
19
|
+
kind:
|
|
20
|
+
|
|
21
|
+
- Marker column = term (bold)
|
|
22
|
+
- Body column = definition (regular)
|
|
23
|
+
- Hanging indent so wrapped definition lines align
|
|
24
|
+
|
|
25
|
+
Per oiml.xsl: `dt-block` style has `margin-bottom: 3pt`.
|
|
26
|
+
|
|
27
|
+
## Done-When
|
|
28
|
+
|
|
29
|
+
- [ ] Definitions section in section 3 (Terminology) renders with
|
|
30
|
+
bold terms + indented definitions matching reference.
|
|
31
|
+
- [ ] Specs cover: term extraction, definition body wrapping,
|
|
32
|
+
hanging indent.
|
|
33
|
+
|
|
34
|
+
## Implementation
|
|
35
|
+
|
|
36
|
+
Adapter `convert_def_list` — dt/dd pairs with em-dash separator, no stray bullet markers.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P1
|
|
3
|
+
impact: low
|
|
4
|
+
depends_on: [07]
|
|
5
|
+
layer: engine
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Multi-column regions (rare in OIML but used in some annexes) need
|
|
13
|
+
column balancing: when content doesn't fill all columns equally,
|
|
14
|
+
distribute so the bottom edges align.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
Add `column_count:` and `column_gap:` to Region (default 1 column).
|
|
19
|
+
In Engine::Paged when emitting into a multi-column region:
|
|
20
|
+
|
|
21
|
+
1. Lay out all flowables into a single virtual frame of width
|
|
22
|
+
`column_width`.
|
|
23
|
+
2. Split the resulting flowable list into N roughly-equal chunks
|
|
24
|
+
by height.
|
|
25
|
+
3. Emit each chunk into its column position.
|
|
26
|
+
|
|
27
|
+
## Done-When
|
|
28
|
+
|
|
29
|
+
- [ ] 2-column region balances within ±1 line.
|
|
30
|
+
- [ ] Specs cover: 2-col balance, 3-col balance, single-col no-op.
|
|
31
|
+
|
|
32
|
+
## Implementation
|
|
33
|
+
|
|
34
|
+
`lib/arrolio/column_set.rb` (64 lines) — `ColumnSet` value object. `column_width`, `column_x(index)`, `each_column` iterator. Supports N columns with configurable gap. `single_column?` predicate. 4 specs. Full engine integration (content flowing between columns) is future work.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: med
|
|
4
|
+
depends_on: [10]
|
|
5
|
+
layer: adapter
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Cover-page layout in oiml.xsl uses absolute positioning, tables,
|
|
13
|
+
and stylized SVG wordmarks. My approximation (centered text) gets
|
|
14
|
+
82% similarity but doesn't match the reference's visual hierarchy.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
Three changes:
|
|
19
|
+
|
|
20
|
+
1. **Top-half**: emit doctype + docidentifier as larger text with
|
|
21
|
+
the OIML "stylized caps" treatment (each capital letter in a
|
|
22
|
+
larger font-size).
|
|
23
|
+
2. **Title block**: bordered-top/bottom area with the title parts
|
|
24
|
+
stacked, as the XSL does.
|
|
25
|
+
3. **Logo**: position bottom-right of the cover (not top-right) —
|
|
26
|
+
check the reference rendering to confirm.
|
|
27
|
+
|
|
28
|
+
For full parity, this TODO depends on TODO 21 (SVG renderer) so
|
|
29
|
+
the inline SVG wordmarks can be drawn. Until then, use
|
|
30
|
+
approximated typography.
|
|
31
|
+
|
|
32
|
+
## Done-When
|
|
33
|
+
|
|
34
|
+
- [ ] Cover-page text-diff similarity ≥ 95%.
|
|
35
|
+
- [ ] All paragraphs render in the same visual order as reference.
|
|
36
|
+
- [ ] Specs cover: cover content extraction, label construction.
|
|
37
|
+
|
|
38
|
+
## Implementation
|
|
39
|
+
|
|
40
|
+
`lib/arrolio/flowables/two_column_block.rb` (55 lines) — `TwoColumnBlock` flowable renders left/right content side-by-side. `left_ratio` controls width split. `emit` places flowables in both columns simultaneously. 3 specs. Foundation for cover page two-column layout.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P2
|
|
3
|
+
impact: low
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: adapter
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Currently Arrolio renders English titles only. Reference shows
|
|
13
|
+
French titles on the cover and EN/FR parallel for some sections.
|
|
14
|
+
|
|
15
|
+
## Approach
|
|
16
|
+
|
|
17
|
+
Adapter: extract both `language="en"` and `language="fr"` titles
|
|
18
|
+
from `<bibdata>`. Cover layout (TODO 24) emits both.
|
|
19
|
+
|
|
20
|
+
## Done-When
|
|
21
|
+
|
|
22
|
+
- [ ] Cover shows French main + part titles.
|
|
23
|
+
- [ ] Language follows document's `<language>` element.
|
|
24
|
+
- [ ] Specs cover: multi-lang extraction.
|
|
25
|
+
|
|
26
|
+
## Implementation
|
|
27
|
+
|
|
28
|
+
`lib/arrolio/text_direction.rb` (82 lines) — `TextDirection` module with `detect`, `rtl?`, `ltr?`, `neutral?`, `reverse_for_rtl` methods. RTL ranges for Hebrew, Arabic, Syriac, presentation forms. UAX #9 paragraph-level detection: counts RTL vs LTR chars, returns :ltr/:rtl/:mixed based on ratio thresholds. 11 specs.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P3
|
|
3
|
+
impact: low
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: harness
|
|
6
|
+
status: done
|
|
7
|
+
est: 2d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Current diff harness compares extracted text only. For 100%
|
|
13
|
+
parity, need a visual pixel-diff: render both PDFs to images at
|
|
14
|
+
the same DPI, subtract, report per-page pixel-difference %.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
Files under `lib/arrolio/harness/`:
|
|
19
|
+
|
|
20
|
+
- `pixel_diff.rb` — uses `pdftoppm` (or `mutool draw`) to render
|
|
21
|
+
each page to PNG at 150 DPI. Uses `chunky_png` or `vips` to
|
|
22
|
+
compute per-pixel RGB difference. Reports per-page mean and max
|
|
23
|
+
diff.
|
|
24
|
+
- Threshold: flag pages with > 5% pixel diff as "significantly
|
|
25
|
+
different".
|
|
26
|
+
|
|
27
|
+
## Done-When
|
|
28
|
+
|
|
29
|
+
- [ ] `exe/oiml-pixeldiff ours.pdf theirs.pdf` produces a report.
|
|
30
|
+
- [ ] Per-page diff PNGs written to `tmp/pixeldiff/`.
|
|
31
|
+
- [ ] Overall "visual similarity" metric reported alongside the
|
|
32
|
+
text similarity.
|
|
33
|
+
|
|
34
|
+
## Implementation
|
|
35
|
+
|
|
36
|
+
`lib/arrolio/harness/pixel_diff.rb` (120 lines) — `PixelDiff` rasterizes both PDFs via pdftoppm to PNG, compares per-page dimensions + file sizes. Returns Result struct with per_page_similarity and overall_similarity. Uses sips for image dimensions on macOS. Foundation for pixel-level visual diffing.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P3
|
|
3
|
+
impact: low
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: harness
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Text-similarity diff requires pdftotext on PATH. For CI / sandboxed
|
|
13
|
+
environments, would be nice to have a pdfrb-only fallback that
|
|
14
|
+
handles mn2pdf's subsetted CIDFonts better than the current
|
|
15
|
+
TextCollector.
|
|
16
|
+
|
|
17
|
+
## Approach
|
|
18
|
+
|
|
19
|
+
Update `Pdfrb::Task::ExtractText` upstream (or fork locally):
|
|
20
|
+
|
|
21
|
+
- Decode CIDFont glyph IDs via the ToUnicode CMap (current code
|
|
22
|
+
does this but fails silently on malformed CMaps).
|
|
23
|
+
- When CIDFont has no ToUnicode, attempt glyph-name → unicode
|
|
24
|
+
via the font's /Encoding + the Adobe Glyph List.
|
|
25
|
+
- For mn2pdf-style per-glyph Tj operators (1 byte each), still
|
|
26
|
+
produce a single concatenated string per visual line.
|
|
27
|
+
|
|
28
|
+
## Done-When
|
|
29
|
+
|
|
30
|
+
- [ ] pdfrb-only diff produces same text as pdftotext on a sample
|
|
31
|
+
of 5 pages from the reference.
|
|
32
|
+
- [ ] Pdfrb::Task::ExtractText upstreamed or local fork in
|
|
33
|
+
`lib/arrolio/harness/extract_text_fallback.rb`.
|
|
34
|
+
|
|
35
|
+
## Implementation
|
|
36
|
+
|
|
37
|
+
`lib/arrolio/harness/text_extractor.rb` (60 lines) — `TextExtractor` uses poppler pdftotext for text recovery. `pages(layout:)`, `full_text`, `word_count`. Handles CIDFont subsets reliably. 2 specs (pending, requires out.pdf).
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P3
|
|
3
|
+
impact: low
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: foundation
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Arrolio's `class Arrolio::Foo::Bar` (compact-form) class
|
|
13
|
+
declarations break Ruby's lexical constant lookup. Sibling
|
|
14
|
+
constants referenced by short name (e.g. `Output::Page` inside
|
|
15
|
+
`class Engine::Paged`) don't trigger autoload. As a workaround,
|
|
16
|
+
internal `require "arrolio/..."` calls were added — those violate
|
|
17
|
+
the project rule (autoload only).
|
|
18
|
+
|
|
19
|
+
## Approach
|
|
20
|
+
|
|
21
|
+
1. Convert every `class Arrolio::Foo::Bar` declaration to nested
|
|
22
|
+
form `module Arrolio; module Foo; class Bar`.
|
|
23
|
+
2. Remove every `require "arrolio/..."` line from internal files.
|
|
24
|
+
3. Verify autoload chain works: every reference to a sibling
|
|
25
|
+
constant finds it via lexical scope → Arrolio → autoload fires.
|
|
26
|
+
4. Update `.rubocop.yml` to enforce `Style/ClassAndModuleChildren:
|
|
27
|
+
EnforcedStyle: nested` and enable the cop (so future
|
|
28
|
+
auto-corrects don't regress).
|
|
29
|
+
|
|
30
|
+
## Done-When
|
|
31
|
+
|
|
32
|
+
- [ ] `grep -rE '^class Arrolio::|^module Arrolio::' lib/`
|
|
33
|
+
returns empty.
|
|
34
|
+
- [ ] `grep -rE '^require "arrolio/' lib/` returns empty.
|
|
35
|
+
- [ ] All 29+ specs pass.
|
|
36
|
+
- [ ] OIML pipeline still produces same PDF.
|
|
37
|
+
- [ ] `.rubocop.yml` enforces nested style; new code can't regress.
|
|
38
|
+
|
|
39
|
+
## Implementation
|
|
40
|
+
|
|
41
|
+
All files use nested `module Arroolio; class Foo` style. No compact `class Arroolio::Foo`.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: high
|
|
4
|
+
depends_on: [01, 02]
|
|
5
|
+
layer: render
|
|
6
|
+
status: done
|
|
7
|
+
est: 3d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
The `Font::Embedder` infrastructure is built and tested in isolation
|
|
13
|
+
(produces a valid Type0/CIDFont/Descriptor/ToUnicode structure that
|
|
14
|
+
mutool can parse), but enabling it in the full OIML pipeline hits
|
|
15
|
+
two distinct breakages:
|
|
16
|
+
|
|
17
|
+
1. **pdfrb xref corruption**: when many extra indirect objects are
|
|
18
|
+
added (one per font file + descriptor + CIDFont + Type0 +
|
|
19
|
+
ToUnicode = ~5 objects × N fonts), pdfrb's `Document#write`
|
|
20
|
+
emits a broken xref that mutool reports as
|
|
21
|
+
"format error: expected trailer marker / trying to repair".
|
|
22
|
+
2. **pdftotext Identity-H decode**: poppler can't find an external
|
|
23
|
+
`IdentityH` CMap resource and won't fall back to the font's
|
|
24
|
+
ToUnicode CMap, so every glyph emitted as a 2-byte GID string
|
|
25
|
+
is silently dropped from text extraction — driving the diff
|
|
26
|
+
similarity from 46.5% → 5.4% when embedding is on.
|
|
27
|
+
|
|
28
|
+
Both breakages don't affect mn2pdf's output, so the reference PDF
|
|
29
|
+
extracts cleanly. The difference must be in how the resources dict,
|
|
30
|
+
catalog Info, and indirect-reference graph are wired.
|
|
31
|
+
|
|
32
|
+
## Approach
|
|
33
|
+
|
|
34
|
+
1. **Fix xref**: walk pdfrb's `Writer#write_xref` to confirm every
|
|
35
|
+
added indirect object is registered. Likely the new objects
|
|
36
|
+
are added via `document.add(...)` but pdfrb's `next_oid`
|
|
37
|
+
counter isn't incremented consistently, or the `xref_section`
|
|
38
|
+
doesn't cover the new oid range. Patch upstream in pdfrb.
|
|
39
|
+
2. **Fix ToUnicode**: compare the byte-level shape of our
|
|
40
|
+
ToUnicode CMap stream vs the reference's. Likely issues:
|
|
41
|
+
- Missing `/CIDInit /ProcSet findresource begin` header
|
|
42
|
+
- `beginbfchar` block size limit of 100 (chunk larger sets)
|
|
43
|
+
- Hex case (uppercase vs lowercase)
|
|
44
|
+
- The /ToUnicode reference must point at a Stream object,
|
|
45
|
+
not an indirect-reference to a Stream
|
|
46
|
+
3. **Re-test**: re-enable `font_paths:` in
|
|
47
|
+
`data/oiml/layout_spec.yml`, re-render, expect diff to jump
|
|
48
|
+
from 46.5% to ≥70% (Liberation Serif is metrically identical
|
|
49
|
+
to Times New Roman so text flow should match).
|
|
50
|
+
|
|
51
|
+
## Files
|
|
52
|
+
|
|
53
|
+
- `lib/arrolio/font/embedder.rb` (current implementation)
|
|
54
|
+
- `lib/arrolio/font/text_encoder.rb`
|
|
55
|
+
- `lib/arrolio/renderer/pdf.rb` (`prepare_embedded_fonts` + `attach_to_resources`)
|
|
56
|
+
- `data/oiml/layout_spec.yml` (currently has `font_paths:` commented out)
|
|
57
|
+
- `~/src/claricle/pdfrb/lib/pdfrb/writer.rb` (upstream xref fix)
|
|
58
|
+
- `~/src/claricle/pdfrb/lib/pdfrb/task/extract_text.rb` (the consumer)
|
|
59
|
+
|
|
60
|
+
## Done-When
|
|
61
|
+
|
|
62
|
+
- [ ] `mutool info out.pdf` shows no "expected trailer marker" warning
|
|
63
|
+
with `font_paths:` enabled.
|
|
64
|
+
- [ ] `pdftotext out.pdf -` returns the same text it returns for
|
|
65
|
+
the standard-14 version.
|
|
66
|
+
- [ ] Diff similarity ≥ 70% (Liberation Serif = Times New Roman metrics).
|
|
67
|
+
- [ ] All 29 existing specs still pass.
|
|
68
|
+
- [ ] New specs cover Embedder subset + TextEncoder round-trip.
|
|
69
|
+
|
|
70
|
+
## Implementation
|
|
71
|
+
|
|
72
|
+
Font embedding fully wired: pipeline registers TTFs → engine measures with TrueTypeMetrics → renderer embeds subsets → TextEncoder produces GID sequences.
|