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,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P2
|
|
3
|
+
phase: 14
|
|
4
|
+
depends_on: [04, 22]
|
|
5
|
+
layer: color
|
|
6
|
+
est: 2d
|
|
7
|
+
status: pending
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Currently colours are Symbols (`:black`, `:red`) or hex strings.
|
|
13
|
+
Production documents need RGB, CMYK, named spot colours, and ICC
|
|
14
|
+
profile-based colours for accurate print reproduction.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
Files under `lib/arrolio/color/`:
|
|
19
|
+
|
|
20
|
+
- `color.rb` — base + factory: `Color.parse("black")`, `Color.rgb(255,
|
|
21
|
+
128, 0)`, `Color.cmyk(0, 1, 1, 0)`, `Color.spot("PMS 200")`.
|
|
22
|
+
|
|
23
|
+
- `rgb.rb` — `RGB = Struct.new(:r, :g, :b)`. Each 0..1 Float.
|
|
24
|
+
|
|
25
|
+
- `cmyk.rb` — `CMYK = Struct.new(:c, :m, :y, :k)`.
|
|
26
|
+
|
|
27
|
+
- `spot.rb` — `Spot = Struct.new(:name, :tint)`.
|
|
28
|
+
|
|
29
|
+
- `icc.rb` — `ICC = Struct.new(:profile_ref, :components)`. Profile
|
|
30
|
+
loaded from `data/arrolio/icc/*.icc`.
|
|
31
|
+
|
|
32
|
+
- `color_space_registry.rb` — registers ICC profiles; assigns
|
|
33
|
+
`/ColorSpace` objects in the PDF; caches by profile path.
|
|
34
|
+
|
|
35
|
+
PDF rendering (TODO 22 extension):
|
|
36
|
+
- RGB → `rg`/`RG` operators.
|
|
37
|
+
- CMYK → `k`/`K` operators.
|
|
38
|
+
- Spot → separation colour space with alternate colour.
|
|
39
|
+
- ICC → ICCBased colour space with the embedded profile.
|
|
40
|
+
|
|
41
|
+
## Done-When
|
|
42
|
+
|
|
43
|
+
- [ ] `Color.parse("#FF8800")` returns an RGB instance.
|
|
44
|
+
- [ ] CMYK colour renders correctly in the PDF.
|
|
45
|
+
- [ ] Spot colour renders with a fallback alternate colour.
|
|
46
|
+
- [ ] ICC profile embedded once per document, referenced by name.
|
|
47
|
+
- [ ] Round-trip: re-read PDF has correct `/ColorSpace` entries.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P2
|
|
3
|
+
phase: 14
|
|
4
|
+
depends_on: [22]
|
|
5
|
+
layer: color
|
|
6
|
+
est: 2d
|
|
7
|
+
status: pending
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Production PDFs carry metadata: title, author, subject, keywords,
|
|
13
|
+
creation/modification dates, plus an XMP stream for ISO 19005-2
|
|
14
|
+
(PDF/A) compliance. Arrolio currently writes nothing.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
Files:
|
|
19
|
+
|
|
20
|
+
- `lib/arrolio/metadata/info.rb` — `Info` value object with the
|
|
21
|
+
classic `/Info` dict fields: `:Title`, `:Author`, `:Subject`,
|
|
22
|
+
`:Keywords`, `:Creator`, `:Producer`, `:CreationDate`,
|
|
23
|
+
`:ModDate`.
|
|
24
|
+
|
|
25
|
+
- `lib/arrolio/metadata/xmp.rb` — builds an XMP packet (XML)
|
|
26
|
+
containing Dublin Core + PDF + PDF/A schemas. Stored as a
|
|
27
|
+
`/Metadata` stream on Catalog (`/Type /Metadata /Subtype /XML`).
|
|
28
|
+
|
|
29
|
+
- Renderer (TODO 22) integration: after rendering pages, write
|
|
30
|
+
`/Info` to trailer and `/Metadata` to Catalog.
|
|
31
|
+
|
|
32
|
+
Date format: PDF date strings `D:YYYYMMDDHHmmSSOHH'mm'`.
|
|
33
|
+
|
|
34
|
+
## Done-When
|
|
35
|
+
|
|
36
|
+
- [ ] A document rendered with `metadata: { title: "My Doc", author:
|
|
37
|
+
"Jane" }` has those fields in `/Info`.
|
|
38
|
+
- [ ] `/CreationDate` and `/ModDate` are valid PDF date strings.
|
|
39
|
+
- [ ] XMP packet is valid XML; parses with REXML.
|
|
40
|
+
- [ ] XMP contains `dc:title`, `dc:creator`, `pdf:Producer`.
|
|
41
|
+
- [ ] Round-trip: re-read PDF exposes the metadata via Pdfrb.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P2
|
|
3
|
+
phase: 15
|
|
4
|
+
depends_on: [22, 21]
|
|
5
|
+
layer: a11y
|
|
6
|
+
est: 3d
|
|
7
|
+
status: pending
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
PDF/UA (ISO 14289-1) requires a structure tree: every visible
|
|
13
|
+
element on the page maps to a structure element (H1, P, Table, TR,
|
|
14
|
+
TD, Figure, etc.). The structure tree is the screen-reader path
|
|
15
|
+
through the document. Without it, the PDF is unreadable to
|
|
16
|
+
assistive technology.
|
|
17
|
+
|
|
18
|
+
## Approach
|
|
19
|
+
|
|
20
|
+
Files:
|
|
21
|
+
|
|
22
|
+
- `lib/arrolio/structure/element.rb` — `StructElement` value
|
|
23
|
+
object: `type` (`:H1`, `:P`, `:Table`, `:Figure`, etc.),
|
|
24
|
+
`content_ref` (link to the PlacedBox that materialises this),
|
|
25
|
+
`children`, `attributes`.
|
|
26
|
+
|
|
27
|
+
- `lib/arrolio/structure/tree.rb` — `StructTreeRoot` builder.
|
|
28
|
+
Walks the Output tree; for each PlacedBox with a `struct_role`
|
|
29
|
+
attribute, emits a StructElement.
|
|
30
|
+
|
|
31
|
+
- Marked content in the content stream: every visible drawing
|
|
32
|
+
operation is wrapped in `BDC`/`EMC` operators with a `/MC`
|
|
33
|
+
property list referencing the StructElement.
|
|
34
|
+
|
|
35
|
+
- `lib/arrolio/renderer/pdf/struct_emitter.rb` — emits:
|
|
36
|
+
- `/StructTreeRoot` on Catalog.
|
|
37
|
+
- `/Type /StructElem` entries.
|
|
38
|
+
- `/ParentTree` mapping content-stream MCIDs to StructElems.
|
|
39
|
+
- `/MarkInfo /true` on Catalog.
|
|
40
|
+
|
|
41
|
+
## Done-When
|
|
42
|
+
|
|
43
|
+
- [ ] A simple paragraph emits `<P>` structure element.
|
|
44
|
+
- [ ] A heading emits `<H1>` with correct heading level.
|
|
45
|
+
- [ ] A table emits `<Table><TR><TH>...</TH></TR></Table>`.
|
|
46
|
+
- [ ] An image emits `<Figure>` with `/Alt` text.
|
|
47
|
+
- [ ] `/MarkInfo /true` is present on Catalog.
|
|
48
|
+
- [ ] Round-trip: re-read PDF has a `/StructTreeRoot`.
|
|
49
|
+
- [ ] A screen reader (NVDA or similar) can navigate the document.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P2
|
|
3
|
+
phase: 15
|
|
4
|
+
depends_on: [48, 33]
|
|
5
|
+
layer: a11y
|
|
6
|
+
est: 1d
|
|
7
|
+
status: pending
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
PDF/UA mandates alt text on every image (`/Alt` on the Figure
|
|
13
|
+
structure element) and supports `/ActualText` on any element (for
|
|
14
|
+
cases where the visible text isn't the accessible text — e.g.
|
|
15
|
+
mathematical equations rendered as glyphs).
|
|
16
|
+
|
|
17
|
+
## Approach
|
|
18
|
+
|
|
19
|
+
Extend `Arrolio::Content::Image` (TODO 02) with `alt` (String).
|
|
20
|
+
Extend `Arrolio::Flowables::ImageFlowable` (TODO 33) to propagate
|
|
21
|
+
`alt` to the PlacedBox.
|
|
22
|
+
|
|
23
|
+
Extend `Arrolio::Output::PlacedBox` with `alt_text` and
|
|
24
|
+
`actual_text` fields.
|
|
25
|
+
|
|
26
|
+
Renderer integration (TODO 48 StructElement):
|
|
27
|
+
- When emitting a `<Figure>` StructElement for an image, include
|
|
28
|
+
`/Alt (alt text)` if `alt_text` is set.
|
|
29
|
+
- When emitting any StructElement, include `/ActualText` if the
|
|
30
|
+
PlacedBox carries it.
|
|
31
|
+
|
|
32
|
+
Validation:
|
|
33
|
+
- A `Content::Image` without `alt` produces a warning (configurable:
|
|
34
|
+
error in strict mode).
|
|
35
|
+
- A `Content::Equation` without `actual_text` produces a warning.
|
|
36
|
+
|
|
37
|
+
## Done-When
|
|
38
|
+
|
|
39
|
+
- [ ] Image with `alt: "Diagram of the system"` emits `/Alt` on its
|
|
40
|
+
Figure element.
|
|
41
|
+
- [ ] Equation with `actual_text: "E = mc^2"` emits `/ActualText`.
|
|
42
|
+
- [ ] Missing alt text raises a warning (or error in strict mode).
|
|
43
|
+
- [ ] Screen reader reads the alt text when navigating to an image.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P2
|
|
3
|
+
phase: 16
|
|
4
|
+
depends_on: [02, 03, 20, 22]
|
|
5
|
+
layer: composer
|
|
6
|
+
est: 3d
|
|
7
|
+
status: in_progress
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Building Content + LayoutSpec by hand for every document is verbose.
|
|
13
|
+
The Composer facade provides a streaming "write a report" API:
|
|
14
|
+
`composer.heading("Intro"); composer.paragraph("...")`. It builds
|
|
15
|
+
Content + uses a default LayoutSpec under the hood.
|
|
16
|
+
|
|
17
|
+
## Approach
|
|
18
|
+
|
|
19
|
+
File: `lib/arrolio/composer.rb`.
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
class Arrolio::Composer
|
|
23
|
+
def initialize(page_size: :A4, margins: 25.mm, preset: :technical_report)
|
|
24
|
+
@document = Arrolio::Content::Document.new
|
|
25
|
+
@layout_spec = Arrolio::Presets.load(preset, page_size:, margins:)
|
|
26
|
+
@current_section = @document
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def heading(text, level: 1); ... end
|
|
30
|
+
def paragraph(text, **style); ... end
|
|
31
|
+
def table(headers:, rows:); ... end
|
|
32
|
+
def list(items, marker: :bullet); ... end
|
|
33
|
+
def image(path, **opts); ... end
|
|
34
|
+
def page_break; ... end
|
|
35
|
+
|
|
36
|
+
def write(path_or_io)
|
|
37
|
+
pages = Arrolio::Engine::Paged.new(layout_spec: @layout_spec,
|
|
38
|
+
flowables: build_flowables).layout
|
|
39
|
+
Arrolio::Renderer::Pdf.new.render(pages, io: path_or_io)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
def build_flowables
|
|
44
|
+
# Walk @document, produce Flowable[] per content node.
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Top-level convenience: `Arrolio.compose("out.pdf") { |c| ... }`.
|
|
50
|
+
|
|
51
|
+
## Done-When
|
|
52
|
+
|
|
53
|
+
- [ ] `Arrolio.compose("out.pdf") { |c| c.heading "Hi"; c.paragraph "World" }`
|
|
54
|
+
produces a valid PDF.
|
|
55
|
+
- [ ] Heading uses the preset's heading style.
|
|
56
|
+
- [ ] Multiple paragraphs flow across pages.
|
|
57
|
+
- [ ] Tables and lists integrate.
|
|
58
|
+
- [ ] Image integration.
|
|
59
|
+
- [ ] Specs cover the public API surface.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P2
|
|
3
|
+
phase: 16
|
|
4
|
+
depends_on: [04]
|
|
5
|
+
layer: composer
|
|
6
|
+
est: 1d
|
|
7
|
+
status: pending
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Composer needs default LayoutSpecs that look professional without
|
|
13
|
+
requiring the user to specify everything. Built-in presets: technical
|
|
14
|
+
report, book, article, manual. Each preset is a LayoutSpec factory.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
File: `lib/arrolio/presets.rb` (namespace) + files under
|
|
19
|
+
`lib/arrolio/presets/`:
|
|
20
|
+
|
|
21
|
+
- `technical_report.rb` — A4, 25mm margins, single column, Times body,
|
|
22
|
+
Helvetica headings, page number bottom-center, section title in
|
|
23
|
+
header. Reads like an ISO/IEC standard.
|
|
24
|
+
|
|
25
|
+
- `book.rb` — Letter, 1in margins, single column with optional
|
|
26
|
+
header, running header with chapter title, page number outside
|
|
27
|
+
(left on even, right on odd). Two-page sequence (odd/even templates).
|
|
28
|
+
|
|
29
|
+
- `article.rb` — A4, 20mm margins, single column, no headers, simple
|
|
30
|
+
footer. Compact.
|
|
31
|
+
|
|
32
|
+
- `manual.rb` — Letter, 0.75in margins, two-column body, sidebar
|
|
33
|
+
notes. Uses multi-column (TODO 44).
|
|
34
|
+
|
|
35
|
+
Each preset is a `LayoutSpec.build { ... }` block, returned by
|
|
36
|
+
`Presets.load(:technical_report, page_size:, margins:)`.
|
|
37
|
+
|
|
38
|
+
## Done-When
|
|
39
|
+
|
|
40
|
+
- [ ] `Presets.load(:technical_report)` returns a valid LayoutSpec.
|
|
41
|
+
- [ ] Each preset has documented font + page geometry choices.
|
|
42
|
+
- [ ] Each preset renders a sample document end-to-end.
|
|
43
|
+
- [ ] Presets can be subclassed/extended.
|
|
44
|
+
- [ ] Specs cover loading + structure of each preset.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
phase: 17
|
|
4
|
+
depends_on: [23, 30, 32, 33, 39, 42]
|
|
5
|
+
layer: harness
|
|
6
|
+
est: 3d
|
|
7
|
+
status: pending
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
**This is Arrolio's primary end-to-end correctness test.** The user's
|
|
13
|
+
requirement: define OIML PDF output (from the existing `oiml.xsl`),
|
|
14
|
+
use the `metanorma-document` gem to parse real OIML presentation XML,
|
|
15
|
+
convert that into Arrolio's content model, apply an OIML LayoutSpec,
|
|
16
|
+
and render via Pdfrb. If Arrolio can produce FOP-equivalent PDFs for
|
|
17
|
+
real OIML documents, the engine is correct.
|
|
18
|
+
|
|
19
|
+
This TODO sets up the **adapter gem** (or namespace) that bridges
|
|
20
|
+
metanorma-document → Arrolio. Phase-17 TODOs 53–55 build the OIML
|
|
21
|
+
LayoutSpec and the diff harness around this.
|
|
22
|
+
|
|
23
|
+
## Approach
|
|
24
|
+
|
|
25
|
+
The bridge has three parts:
|
|
26
|
+
|
|
27
|
+
### 1. OIML fixture corpus
|
|
28
|
+
|
|
29
|
+
Live in `spec/fixtures/oiml/` (gitignored if licensing requires):
|
|
30
|
+
- 3–5 real OIML presentation XML files at varying complexity
|
|
31
|
+
(cover + TOC + 5 sections + tables; multi-section with figures;
|
|
32
|
+
full 50-page reference doc).
|
|
33
|
+
- For each, a FOP-rendered reference PDF (the ground truth).
|
|
34
|
+
|
|
35
|
+
A rake task `rake fixtures:refresh_oiml` regenerates the reference
|
|
36
|
+
PDFs by running `xsltproc oiml.xsl + fop` if the user has FOP
|
|
37
|
+
installed; otherwise fetches pre-rendered ones from a private repo.
|
|
38
|
+
|
|
39
|
+
### 2. `Arrolio::Oiml` adapter (in this gem, spec-only initially)
|
|
40
|
+
|
|
41
|
+
Files under `lib/arrolio/oiml/`:
|
|
42
|
+
|
|
43
|
+
- `content_adapter.rb` — converts
|
|
44
|
+
`Metanorma::Oiml::Document::Root` → `Arrolio::Content::Document`.
|
|
45
|
+
Walks the typed model, dispatches per node class via
|
|
46
|
+
`BlockDispatcher` (mirrors the STS transformer pattern):
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
class ContentAdapter
|
|
50
|
+
def convert(oiml_root) -> Arrolio::Content::Document
|
|
51
|
+
def convert_clause(clause) -> Arrolio::Content::Section
|
|
52
|
+
def convert_paragraph(p) -> Arrolio::Content::Paragraph
|
|
53
|
+
def convert_table(t) -> Arrolio::Content::Table
|
|
54
|
+
def convert_list(l) -> Arrolio::Content::List
|
|
55
|
+
def convert_image(i) -> Arrolio::Content::Image
|
|
56
|
+
# etc.
|
|
57
|
+
end
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Inline content (text + spans + cross-references) maps to
|
|
61
|
+
`Arrolio::InlineRun[]`.
|
|
62
|
+
|
|
63
|
+
- `layout_spec_loader.rb` — loads the OIML LayoutSpec (TODO 53).
|
|
64
|
+
|
|
65
|
+
- `pipeline.rb` — orchestrates:
|
|
66
|
+
```ruby
|
|
67
|
+
oiml_root = Metanorma::Oiml::Document::Root.parse(xml)
|
|
68
|
+
content = ContentAdapter.new.convert(oiml_root)
|
|
69
|
+
layout = LayoutSpecLoader.load("oiml.layout_spec.rb")
|
|
70
|
+
flowables = FlowBuilder.(content, layout)
|
|
71
|
+
pages = Arrolio::Engine::Paged.new(layout_spec: layout,
|
|
72
|
+
flowables:).layout
|
|
73
|
+
Arrolio::Renderer::Pdf.new.render(pages, io:)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 3. End-to-end spec
|
|
77
|
+
|
|
78
|
+
`spec/arrolio/oiml_e2e_spec.rb` (tagged `:e2e`):
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
RSpec.describe "OIML → PDF end-to-end", :e2e do
|
|
82
|
+
Dir.glob("spec/fixtures/oiml/*.presentation.xml").each do |xml_path|
|
|
83
|
+
it "renders #{File.basename(xml_path)} to a valid PDF" do
|
|
84
|
+
out = StringIO.new
|
|
85
|
+
Arrolio::Oiml::Pipeline.(File.read(xml_path), io: out)
|
|
86
|
+
|
|
87
|
+
# Sanity: valid PDF
|
|
88
|
+
expect(out.string).to start_with("%PDF-")
|
|
89
|
+
expect(out.string).to include("%%EOF")
|
|
90
|
+
|
|
91
|
+
# Re-readable
|
|
92
|
+
reopened = Pdfrb::Document.new(io: StringIO.new(out.string))
|
|
93
|
+
expect(reopened.pages.count).to be > 0
|
|
94
|
+
|
|
95
|
+
# Diff vs FOP reference (TODO 55)
|
|
96
|
+
ref_path = xml_path.sub(".presentation.xml", ".fop.pdf")
|
|
97
|
+
if File.exist?(ref_path)
|
|
98
|
+
diff = Arrolio::Harness::TextDiff.(
|
|
99
|
+
ours: out.string,
|
|
100
|
+
reference: File.binread(ref_path)
|
|
101
|
+
)
|
|
102
|
+
expect(diff.page_count_delta).to be <= 1 # tolerate off-by-one
|
|
103
|
+
expect(diff.text_similarity).to be > 0.95
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Done-When
|
|
111
|
+
|
|
112
|
+
- [ ] `Metanorma::Oiml::Document::Root` (or its base class) loads
|
|
113
|
+
fixture XMLs without error.
|
|
114
|
+
- [ ] `ContentAdapter` converts every fixture without raising.
|
|
115
|
+
- [ ] `Pipeline` produces bytes starting with `%PDF-` for every fixture.
|
|
116
|
+
- [ ] Each rendered PDF re-reads via Pdfrb with the expected page count.
|
|
117
|
+
- [ ] Adapter covers at minimum: clause, paragraph, table, list,
|
|
118
|
+
image, heading (sections 1, 1.1, 1.1.1), footnote, basic-link.
|
|
119
|
+
- [ ] Each adapter method is unit-tested with a minimal input.
|
|
120
|
+
- [ ] `rake oiml:render[xml_path]` CLI command renders a single doc.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
phase: 17
|
|
4
|
+
depends_on: [52]
|
|
5
|
+
layer: harness
|
|
6
|
+
est: 5d
|
|
7
|
+
status: pending
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
The OIML stylesheet (`oiml.xsl`) encodes OIML's house style:
|
|
13
|
+
specific page geometry, section-numbering format, running headers
|
|
14
|
+
with chapter title + doc number, table styling, TOC layout, etc.
|
|
15
|
+
We need to encode the *intent* of `oiml.xsl` as a Ruby
|
|
16
|
+
`Arrolio::LayoutSpec` — not by translating the XSL element-for-
|
|
17
|
+
element, but by reading what it produces visually and reproducing
|
|
18
|
+
that in Ruby.
|
|
19
|
+
|
|
20
|
+
## Approach
|
|
21
|
+
|
|
22
|
+
Read these sources of truth (in order):
|
|
23
|
+
|
|
24
|
+
1. **`~/src/mn/metanorma-taste/data/oiml/oiml.xsl`** — the XSL itself.
|
|
25
|
+
2. **Sample FOP-rendered OIML PDFs** — `~/src/mn/mn-samples-oiml/_site/documents/*/*.pdf`.
|
|
26
|
+
Open in a viewer; measure geometry with a PDF inspector.
|
|
27
|
+
3. **The OIML document model** — `~/src/mn/metanorma-oiml/lib/metanorma/oiml/document/`
|
|
28
|
+
to know what elements exist.
|
|
29
|
+
|
|
30
|
+
File: `lib/arrolio/oiml/layout_spec.rb` (Ruby-encoded OIML house style).
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
module Arrolio::Oiml
|
|
34
|
+
module LayoutSpec
|
|
35
|
+
OIML_A4 = [595.28, 841.89].freeze # A4 in pt
|
|
36
|
+
|
|
37
|
+
def self.build
|
|
38
|
+
Arrolio::LayoutSpec.build do
|
|
39
|
+
# Page templates: cover, body (with running header + footer)
|
|
40
|
+
page_template(:cover) do
|
|
41
|
+
page_size OIML_A4
|
|
42
|
+
margins top: 50, bottom: 50, left: 30, right: 30
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
page_template(:body) do
|
|
46
|
+
page_size OIML_A4
|
|
47
|
+
margins top: 35, bottom: 30, left: 25, right: 25
|
|
48
|
+
region_extents before: 15, after: 15
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Page sequences: cover (single page) + body (odd/even differ)
|
|
52
|
+
page_sequence(:front_matter, first: :cover, next: :cover)
|
|
53
|
+
page_sequence(:main,
|
|
54
|
+
first: :body, odd: :body, even: :body,
|
|
55
|
+
page_numbering: { format: :arabic, start: 1 })
|
|
56
|
+
|
|
57
|
+
# Styles (excerpt)
|
|
58
|
+
style(:title, font: "Helvetica-Bold", size: 24)
|
|
59
|
+
style(:subtitle, font: "Helvetica", size: 14)
|
|
60
|
+
style(:heading_1, font: "Helvetica-Bold", size: 14,
|
|
61
|
+
space_before: 18, keep_with_next: true)
|
|
62
|
+
style(:heading_2, font: "Helvetica-Bold", size: 12)
|
|
63
|
+
style(:body, font: "Times-Roman", size: 11,
|
|
64
|
+
align: :justify, line_spacing: 1.2)
|
|
65
|
+
style(:table_header, font: "Helvetica-Bold", size: 10,
|
|
66
|
+
background_color: "#EEEEEE")
|
|
67
|
+
style(:table_cell, font: "Times-Roman", size: 10)
|
|
68
|
+
style(:running_header, font: "Helvetica", size: 9)
|
|
69
|
+
style(:footer, font: "Helvetica", size: 9, align: :center)
|
|
70
|
+
|
|
71
|
+
# Static content bindings (running header + footer)
|
|
72
|
+
static_content(:body, :before) do
|
|
73
|
+
[
|
|
74
|
+
# Left frame: current top-level section title
|
|
75
|
+
Arrolio::PlacedText.new(
|
|
76
|
+
content: ->(ctx) { ctx.current_section.numbered_title },
|
|
77
|
+
style: :running_header,
|
|
78
|
+
position: [0, 0, 80.mm, 15]
|
|
79
|
+
),
|
|
80
|
+
# Right frame: doc reference
|
|
81
|
+
Arrolio::PlacedText.new(
|
|
82
|
+
content: ->(ctx) { ctx.metadata[:doc_reference] },
|
|
83
|
+
style: :running_header,
|
|
84
|
+
position: [100.mm, 0, 30.mm, 15]
|
|
85
|
+
)
|
|
86
|
+
]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
static_content(:body, :after) do
|
|
90
|
+
[
|
|
91
|
+
Arrolio::PlacedField.new(
|
|
92
|
+
field: :page_number,
|
|
93
|
+
style: :footer,
|
|
94
|
+
position: [90.mm, 0, 10.mm, 15]
|
|
95
|
+
)
|
|
96
|
+
]
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This file is the **single source of truth** for OIML styling. To
|
|
105
|
+
change a colour, edit this file. To change page geometry, edit this
|
|
106
|
+
file. The XSL becomes irrelevant.
|
|
107
|
+
|
|
108
|
+
## Done-When
|
|
109
|
+
|
|
110
|
+
- [ ] `LayoutSpec.build` produces a valid Arrolio::LayoutSpec.
|
|
111
|
+
- [ ] Page geometry matches the OIML sample PDFs (measured to 1pt).
|
|
112
|
+
- [ ] Section numbering format matches OIML convention (`1`, `1.1`,
|
|
113
|
+
`1.1.1`).
|
|
114
|
+
- [ ] Running header on a body page shows the current section title +
|
|
115
|
+
doc reference.
|
|
116
|
+
- [ ] Footer shows page number centered.
|
|
117
|
+
- [ ] Tables in the OIML sample render with the right header styling.
|
|
118
|
+
- [ ] Cover page matches the OIML sample layout (title block, doc
|
|
119
|
+
reference, etc.).
|
|
120
|
+
- [ ] Style is documented: every property choice cites the OIML
|
|
121
|
+
sample PDF or the XSL line it was derived from.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
phase: 17
|
|
4
|
+
depends_on: [52, 53]
|
|
5
|
+
layer: harness
|
|
6
|
+
est: 2d
|
|
7
|
+
status: pending
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
End-to-end pipeline that ties together:
|
|
13
|
+
1. metanorma-document parses OIML presentation XML.
|
|
14
|
+
2. `Oiml::ContentAdapter` converts to `Arrolio::Content::Document`.
|
|
15
|
+
3. `Oiml::LayoutSpec.build` (TODO 53) supplies the OIML house style.
|
|
16
|
+
4. `Engine::Paged` lays out.
|
|
17
|
+
5. `Renderer::Pdf` produces bytes.
|
|
18
|
+
|
|
19
|
+
The PDF **diff** against FOP's reference is a separate concern —
|
|
20
|
+
covered by the comparator subsystem (TODOs 55–59). This TODO only
|
|
21
|
+
ships the pipeline and a minimal sanity-check spec.
|
|
22
|
+
|
|
23
|
+
## Approach
|
|
24
|
+
|
|
25
|
+
### Pipeline entry point
|
|
26
|
+
|
|
27
|
+
File: `lib/arrolio/oiml/pipeline.rb`.
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
module Arrolio::Oiml
|
|
31
|
+
module Pipeline
|
|
32
|
+
module_function
|
|
33
|
+
|
|
34
|
+
def call(presentation_xml_or_path, io:, **opts)
|
|
35
|
+
require "metanorma/oiml/document" # lazy-load heavy dep
|
|
36
|
+
|
|
37
|
+
oiml_root = Metanorma::Oiml::Document::Root.parse(
|
|
38
|
+
presentation_xml_or_path
|
|
39
|
+
)
|
|
40
|
+
content = ContentAdapter.new.convert(oiml_root)
|
|
41
|
+
layout = LayoutSpec.build
|
|
42
|
+
flowables = FlowBuilder.(content, layout)
|
|
43
|
+
pages = Arrolio::Engine::Paged.new(
|
|
44
|
+
layout_spec: layout,
|
|
45
|
+
flowables: flowables
|
|
46
|
+
).layout
|
|
47
|
+
Arrolio::Renderer::Pdf.new.render(pages, io: io)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### CLI
|
|
54
|
+
|
|
55
|
+
File: `exe/arrolio-oiml` (executable).
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
arrolio-oiml render document.presentation.xml > document.pdf
|
|
59
|
+
arrolio-oiml diff document.presentation.xml \
|
|
60
|
+
--reference=document.fop.pdf
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The `diff` subcommand shells out to the comparator subsystem
|
|
64
|
+
(TODO 55) — this TODO only delivers `render`.
|
|
65
|
+
|
|
66
|
+
### Minimal sanity spec
|
|
67
|
+
|
|
68
|
+
`spec/arrolio/oiml/pipeline_spec.rb`:
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
RSpec.describe "OIML → PDF pipeline", :e2e do
|
|
72
|
+
Dir.glob("spec/fixtures/oiml/*.presentation.xml").each do |xml|
|
|
73
|
+
it "#{File.basename(xml)} renders a valid PDF" do
|
|
74
|
+
out = StringIO.new
|
|
75
|
+
Arrolio::Oiml::Pipeline.(xml, io: out)
|
|
76
|
+
expect(out.string).to start_with("%PDF-")
|
|
77
|
+
expect(out.string).to include("%%EOF")
|
|
78
|
+
|
|
79
|
+
reopened = Pdfrb::Document.new(io: StringIO.new(out.string))
|
|
80
|
+
expect(reopened.pages.count).to be > 0
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This is intentionally weak — no comparison vs FOP. The diff
|
|
87
|
+
subsystem (TODO 55–59) layers comparison on top.
|
|
88
|
+
|
|
89
|
+
## Done-When
|
|
90
|
+
|
|
91
|
+
- [ ] `Arrolio::Oiml::Pipeline.(xml, io:)` produces PDF bytes from
|
|
92
|
+
every fixture.
|
|
93
|
+
- [ ] `exe/arrolio-oiml render doc.presentation.xml > out.pdf`
|
|
94
|
+
produces a readable PDF.
|
|
95
|
+
- [ ] Pipeline spec runs against all fixtures without raising.
|
|
96
|
+
- [ ] Pipeline accepts either a path or a raw XML string.
|
|
97
|
+
- [ ] No FOP/diff code lives in this TODO — that's TODOs 55–59.
|