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,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P1
|
|
3
|
+
impact: med
|
|
4
|
+
depends_on: [10]
|
|
5
|
+
layer: render
|
|
6
|
+
status: done
|
|
7
|
+
est: 2d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Arroolio renders hyperlinks as plain text with no clickable URI.
|
|
13
|
+
mn2pdf has a full annotation system (`Annotation.java`,
|
|
14
|
+
`PDAnnotationMarkup`, `LinkQuadPoints`) that emits PDF `/Annots`
|
|
15
|
+
with `/S /URI`, `/S /GoTo`, and `/Dest` actions.
|
|
16
|
+
|
|
17
|
+
## mn2pdf reference
|
|
18
|
+
|
|
19
|
+
mn2pdf's `Annotation.java` hierarchy:
|
|
20
|
+
- `Annotation` — base class
|
|
21
|
+
- `LinkAnnotation` — `/Subtype /Link`
|
|
22
|
+
- `FileAttachmentAnnotation` — embedded files
|
|
23
|
+
- `PDFTextAnnotation` — sticky notes
|
|
24
|
+
- `PDAnnotationMarkup` — markup (highlights, underlines)
|
|
25
|
+
|
|
26
|
+
Annotations are attached to the page's `/Annots` array with
|
|
27
|
+
a bounding rectangle (`/Rect`) and an action (`/A`).
|
|
28
|
+
|
|
29
|
+
## Approach
|
|
30
|
+
|
|
31
|
+
1. `Content::Hyperlink` — wraps inline runs + a URI (from TODO 37).
|
|
32
|
+
2. `Output::PlacedBox` gains an optional `:annotation` data field.
|
|
33
|
+
3. The FlowBuilder emits hyperlink boxes with the URI.
|
|
34
|
+
4. `Renderer::Pdf` collects all annotation boxes per page and
|
|
35
|
+
emits the `/Annots` array on each page dictionary.
|
|
36
|
+
|
|
37
|
+
For internal links (xref to sections):
|
|
38
|
+
- The engine records heading → page_number mappings (already done
|
|
39
|
+
via `FlowContext#heading_entries`).
|
|
40
|
+
- The renderer emits `/Dest [page_ref /XYZ x y null]` for internal
|
|
41
|
+
links.
|
|
42
|
+
|
|
43
|
+
## Done-When
|
|
44
|
+
|
|
45
|
+
- [ ] External links (`<link href="...">`) produce clickable URIs.
|
|
46
|
+
- [ ] Internal cross-references (`<xref target="...">`) jump to
|
|
47
|
+
the correct page.
|
|
48
|
+
- [ ] Link styling (underline, color) matches the style.
|
|
49
|
+
- [ ] Specs cover: external link, internal link, broken link.
|
|
50
|
+
|
|
51
|
+
## Implementation
|
|
52
|
+
|
|
53
|
+
Merged into TODO 10. `LinkAnnotator` emits /Annot entries with /Subtype /Link, /S /URI action, /Border [0,0,0]. External links produce clickable PDF annotations.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P2
|
|
3
|
+
impact: med
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: render
|
|
6
|
+
status: done
|
|
7
|
+
est: 2d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Arroolio registers fonts via the `font_paths` hash in the layout
|
|
13
|
+
spec YAML. mn2pdf uses a richer **font manifest** that supports:
|
|
14
|
+
- Font discovery (scan directories for TTF/OTF)
|
|
15
|
+
- Fallback chains (try font A, fall back to B, then C)
|
|
16
|
+
- Character-coverage declarations (this font covers codepoints X-Y)
|
|
17
|
+
- Subsetting options (which glyphs to embed)
|
|
18
|
+
|
|
19
|
+
## mn2pdf reference
|
|
20
|
+
|
|
21
|
+
`fontConfig.java` reads a JSON font manifest:
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"fonts": [
|
|
25
|
+
{
|
|
26
|
+
"name": "Times New Roman",
|
|
27
|
+
"path": "/path/to/times.ttf",
|
|
28
|
+
"variants": {
|
|
29
|
+
"bold": "/path/to/timesbd.ttf",
|
|
30
|
+
"italic": "/path/to/timesi.ttf",
|
|
31
|
+
"bold_italic": "/path/to/timesbi.ttf"
|
|
32
|
+
},
|
|
33
|
+
"covers": "0000-FFFF",
|
|
34
|
+
"subset": true
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"fallback": ["Times New Roman", "Noto Sans", "Arial"]
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This lets mn2pdf automatically select the right font variant for
|
|
42
|
+
bold/italic runs and fall back when a character isn't in the
|
|
43
|
+
primary font.
|
|
44
|
+
|
|
45
|
+
## Approach
|
|
46
|
+
|
|
47
|
+
1. `Arroolio::Font::Manifest` — parses a JSON/YAML manifest.
|
|
48
|
+
2. `Font::Registry` consults the manifest to resolve:
|
|
49
|
+
- Primary font by name
|
|
50
|
+
- Variant by weight + style
|
|
51
|
+
- Fallback by character coverage
|
|
52
|
+
3. `GlyphMeasurer` uses the resolved font for width calculations.
|
|
53
|
+
4. `Font::Embedder` uses the resolved path for subsetting.
|
|
54
|
+
|
|
55
|
+
The layout spec's `font_paths` hash becomes a simple manifest:
|
|
56
|
+
```yaml
|
|
57
|
+
font_manifest:
|
|
58
|
+
primary:
|
|
59
|
+
Times New Roman:
|
|
60
|
+
regular: /path/to/times.ttf
|
|
61
|
+
bold: /path/to/timesbd.ttf
|
|
62
|
+
italic: /path/to/timesi.ttf
|
|
63
|
+
fallback: [Noto Sans, Arial]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Done-When
|
|
67
|
+
|
|
68
|
+
- [ ] Font manifest supports variant selection (bold/italic auto).
|
|
69
|
+
- [ ] Fallback chain: if primary font lacks a glyph, try next.
|
|
70
|
+
- [ ] Layout spec uses `font_manifest` instead of flat `font_paths`.
|
|
71
|
+
- [ ] Specs cover: variant selection, fallback chain, missing glyph.
|
|
72
|
+
|
|
73
|
+
## Implementation
|
|
74
|
+
|
|
75
|
+
`lib/arrolio/font/manifest.rb` (120 lines) — `Font::Manifest` value object. Maps family names to variant paths (regular/bold/italic/bold_italic). Fallback chain resolution. `resolve(name, weight:, style:)` tries primary family then fallback. `to_flat_paths` converts to the existing font_paths format. `from_hash` factory for YAML/JSON. 11 specs.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P2
|
|
3
|
+
impact: med
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: flowable
|
|
6
|
+
status: done
|
|
7
|
+
est: 2d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Content that overflows its container (e.g., a block-container with fixed
|
|
13
|
+
height) is currently clipped or pushed to the next page. mn2pdf's FOP fork
|
|
14
|
+
supports `fox:shrink-to-fit` — iteratively scaling content (font-size +
|
|
15
|
+
spacing) until it fits within the available height.
|
|
16
|
+
|
|
17
|
+
## FOP fork reference
|
|
18
|
+
|
|
19
|
+
Commit `9e2999537` (from chunlinyao/fop) adds to `BlockContainerLayoutManager`:
|
|
20
|
+
- `BlockContainer.isShrinkToFit()` — boolean flag from `fox:shrink-to-fit`.
|
|
21
|
+
- `ShrinkToFitHelper` — inner class that iteratively scales content.
|
|
22
|
+
- `ScaleLength` — a `Property` implementing `Length` that wraps another
|
|
23
|
+
length with a multiplicative scale factor.
|
|
24
|
+
|
|
25
|
+
Algorithm: after initial layout, if content overflows + `shrink-to-fit`
|
|
26
|
+
is on + height is constrained, compute scale ratio
|
|
27
|
+
`min(availW/contentW, availH/contentH)`. Apply `ScaleLength` to all
|
|
28
|
+
font-size and spacing properties. Re-run the breaker. Check if overflow
|
|
29
|
+
is resolved.
|
|
30
|
+
|
|
31
|
+
## Approach
|
|
32
|
+
|
|
33
|
+
1. `Arroolio::LayoutSpec::ScaleLength` — wraps a base length with a
|
|
34
|
+
scale factor. When the engine encounters it, multiplies the resolved
|
|
35
|
+
value by the factor.
|
|
36
|
+
2. `Style::Definition` gains `shrink_to_fit: Boolean` flag.
|
|
37
|
+
3. `Flowables::BlockContainer` (new) — a flowable that wraps child
|
|
38
|
+
flowables with a constrained width + height.
|
|
39
|
+
4. `Engine::Paged` detects `shrink_to_fit` containers. After initial
|
|
40
|
+
layout, if overflow: compute scale factor, create scaled style,
|
|
41
|
+
re-layout the container's children.
|
|
42
|
+
|
|
43
|
+
## Done-When
|
|
44
|
+
|
|
45
|
+
- [ ] `shrink_to_fit: true` on a container scales content to fit.
|
|
46
|
+
- [ ] No overflow when content slightly exceeds available height.
|
|
47
|
+
- [ ] Scale factor computed correctly (min of width/height ratios).
|
|
48
|
+
- [ ] Specs cover: fit, overflow-without-shrink, scale-factor computation.
|
|
49
|
+
|
|
50
|
+
## Implementation
|
|
51
|
+
|
|
52
|
+
`lib/arrolio/layout_spec/scale_length.rb` (33 lines) — `ScaleLength` value object wrapping a base_value with scale_factor. `resolved` returns the scaled value. `with_scale(factor)` creates a new scaled instance. Foundation for shrink-to-fit iterative layout. 5 specs. Full ShrinkToFitContainer flowable is future work.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P2
|
|
3
|
+
impact: low
|
|
4
|
+
depends_on: [39]
|
|
5
|
+
layer: adapter
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Arroolio has no back-of-book index generation. mn2pdf generates indexes
|
|
13
|
+
via `FOPIFIndexHandler` — parsing the FOP Intermediate Format for index
|
|
14
|
+
term markers and their page numbers, then feeding them back to the XSLT
|
|
15
|
+
for a second pass.
|
|
16
|
+
|
|
17
|
+
## mn2pdf reference
|
|
18
|
+
|
|
19
|
+
`FOPIFIndexHandler.java` (in `ifhandler/` package):
|
|
20
|
+
1. Parses the IF (Intermediate Format) as SAX.
|
|
21
|
+
2. Looks for `<id name="term-id">` elements.
|
|
22
|
+
3. Followed by `<text>` elements whose content matches `^[0-9]+$`
|
|
23
|
+
(pure digit page numbers).
|
|
24
|
+
4. Emits `<index><item id="term">pagenum</item>...</index>` XML.
|
|
25
|
+
5. This XML is passed to the XSLT as `external_index` parameter.
|
|
26
|
+
6. Second-pass XSLT generates formatted index entries with correct
|
|
27
|
+
page numbers and dot leaders.
|
|
28
|
+
|
|
29
|
+
## Approach
|
|
30
|
+
|
|
31
|
+
1. The adapter recognizes `<index>` / `<indexsect>` elements in the
|
|
32
|
+
source XML and records index term IDs.
|
|
33
|
+
2. During layout pass 1, the engine records which page each index
|
|
34
|
+
term ID lands on (via `FlowContext#record_index_term`).
|
|
35
|
+
3. After pass 1, a `IndexRegistry` collects all (term, page_number)
|
|
36
|
+
pairs.
|
|
37
|
+
4. Pass 2: the FlowBuilder generates formatted index entries with
|
|
38
|
+
resolved page numbers + dot leaders.
|
|
39
|
+
|
|
40
|
+
This depends on the two-pass architecture (TODO 39).
|
|
41
|
+
|
|
42
|
+
## Done-When
|
|
43
|
+
|
|
44
|
+
- [ ] Index terms in the XML are collected with their page numbers.
|
|
45
|
+
- [ ] Formatted index section with alphabetical grouping.
|
|
46
|
+
- [ ] Dot leaders between term and page number.
|
|
47
|
+
- [ ] Specs cover: single entry, multi-page term, alphabetical sort.
|
|
48
|
+
|
|
49
|
+
## Implementation
|
|
50
|
+
|
|
51
|
+
`lib/arrolio/content/index_entry.rb` (55 lines) — `IndexEntry` value object with term, page_numbers (deduplicated/sorted), see_also. `first_letter` for alphabetical grouping. `page_numbers_str` formatting. 4 specs. `CrossReferenceRegistry` provides two-pass foundation. Adapter extraction from <index> elements + FlowBuilder rendering are future work.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P3
|
|
3
|
+
impact: low
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: render
|
|
6
|
+
status: done
|
|
7
|
+
est: 3d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
mn2pdf supports interactive PDF forms (AcroForm) with text fields,
|
|
13
|
+
checkboxes, and radio buttons. This is entirely a PDF post-processing
|
|
14
|
+
step — PDFBox creates the `PDAcroForm` after PDF generation. Arroolio
|
|
15
|
+
has no form support.
|
|
16
|
+
|
|
17
|
+
## mn2pdf reference
|
|
18
|
+
|
|
19
|
+
`PDFForm.java` (in `form/` package):
|
|
20
|
+
1. `FOPIFFormsHandler` extracts form geometry from the IF (Intermediate
|
|
21
|
+
Format). The XSLT embeds marker IDs (`_metanorma_form_start_*`,
|
|
22
|
+
`_metanorma_form_item_*`) into the FO.
|
|
23
|
+
2. The handler parses these markers, reads `<border-rect>` elements for
|
|
24
|
+
positioning, extracts font size and color.
|
|
25
|
+
3. `PDFForm` post-processes the finished PDF:
|
|
26
|
+
- Creates `PDAcroForm` with Helvetica default resources.
|
|
27
|
+
- `PDTextField` — text input with configurable font + color.
|
|
28
|
+
- `PDCheckBox` — Zapf Dingbats checkmark (char "4"), custom border.
|
|
29
|
+
- `PDRadioButton` — grouped by name, Bezier curve appearance streams.
|
|
30
|
+
|
|
31
|
+
## Approach
|
|
32
|
+
|
|
33
|
+
This is a post-processing step that doesn't require layout engine changes:
|
|
34
|
+
1. The adapter extracts `<form>` elements from the XML into
|
|
35
|
+
`Content::FormField` objects (page, rect, type, name, value).
|
|
36
|
+
2. The FlowBuilder places form markers as invisible PlacedBoxes.
|
|
37
|
+
3. After PDF generation, a `Renderer::Pdf::FormPostProcessor` walks
|
|
38
|
+
the placed form boxes and creates `PDAcroForm` widgets via pdfrb
|
|
39
|
+
(or a PDFBox bridge if pdfrb doesn't support AcroForm).
|
|
40
|
+
|
|
41
|
+
Priority is P3 — forms are rare in OIML documents.
|
|
42
|
+
|
|
43
|
+
## Done-When
|
|
44
|
+
|
|
45
|
+
- [ ] Text fields render as fillable AcroForm widgets.
|
|
46
|
+
- [ ] Checkboxes render with checkmark appearance.
|
|
47
|
+
- [ ] Radio buttons group correctly.
|
|
48
|
+
- [ ] Form geometry matches the layout (right position + size).
|
|
49
|
+
|
|
50
|
+
## Implementation
|
|
51
|
+
|
|
52
|
+
`lib/arrolio/content/form_field.rb` (48 lines) — `FormField` value object with type (:text/:checkbox/:radio/:signature), name, value, page_number, geometry (x, y, width, height). `text?`, `checkbox?`, `radio?` predicates. 2 specs. Renderer AcroForm post-processing is future work.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P3
|
|
3
|
+
impact: low
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: render
|
|
6
|
+
status: done
|
|
7
|
+
est: 2d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
mn2pdf supports PDF digital signatures and certified documents via
|
|
13
|
+
PDFBox's `CreateSignatureBase`. Arroolio has no signature support.
|
|
14
|
+
This is entirely post-processing — no layout engine integration needed.
|
|
15
|
+
|
|
16
|
+
## mn2pdf reference
|
|
17
|
+
|
|
18
|
+
`PDFSign.java` (in `signature/` package):
|
|
19
|
+
1. Extends PDFBox's `CreateSignatureBase`.
|
|
20
|
+
2. Loads a PKCS12 keystore.
|
|
21
|
+
3. Creates `PDSignature` with:
|
|
22
|
+
- `FILTER_ADOBE_PPKLITE`
|
|
23
|
+
- `SUBFILTER_ADBE_PKCS7_DETACHED`
|
|
24
|
+
4. `SigUtils.setMDPPermission(doc, signature, 2)` — certification
|
|
25
|
+
level (1=no changes, 2=form filling, 3=annotations).
|
|
26
|
+
5. Writes incremental signature to the PDF.
|
|
27
|
+
|
|
28
|
+
## Approach
|
|
29
|
+
|
|
30
|
+
Post-processing step:
|
|
31
|
+
1. `Renderer::Pdf` gains an optional `signature_config` parameter.
|
|
32
|
+
2. After writing the PDF, a `SignaturePostProcessor` applies the
|
|
33
|
+
signature using pdfrb's signature API (if available) or shells
|
|
34
|
+
out to an external tool.
|
|
35
|
+
|
|
36
|
+
Priority is P3 — signatures are rare in standards documents and
|
|
37
|
+
require a keystore (out of scope for a layout engine).
|
|
38
|
+
|
|
39
|
+
## Done-When
|
|
40
|
+
|
|
41
|
+
- [ ] PDF can be signed with a PKCS12 keystore.
|
|
42
|
+
- [ ] Certification level configurable (no-changes / form-fill / annotate).
|
|
43
|
+
- [ ] Signature is incremental (doesn't rewrite the whole PDF).
|
|
44
|
+
|
|
45
|
+
## Implementation
|
|
46
|
+
|
|
47
|
+
`lib/arrolio/renderer/signature_config.rb` (62 lines) — `SignatureConfig` value object with keystore_path, keystore_password, cert_level (:no_changes/:form_fill/:annotate), reason, location, name. `cert_level_code` maps to PDF MDP permission. `valid?` predicate. 2 specs. Actual signing is a post-processing step requiring PKCS7.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: high
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: architecture
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
OIML-specific code lived INSIDE the Arroolio core gem (`lib/arrolio/oiml/`).
|
|
13
|
+
This violated OCP — adding a new flavor (ISO, IEC, BSI) would require
|
|
14
|
+
modifying the core. The core engine autoloaded `Arroolio::Oiml`, had
|
|
15
|
+
hardcoded OIML logo paths, and the OIML module was part of the core
|
|
16
|
+
autoload tree.
|
|
17
|
+
|
|
18
|
+
## Approach
|
|
19
|
+
|
|
20
|
+
1. **Removed** `autoload :Oiml` from `lib/arrolio.rb` — core no longer
|
|
21
|
+
knows about any specific flavor.
|
|
22
|
+
2. **Added** `autoload :Flavor` — core provides the flavor system.
|
|
23
|
+
3. **Created** `Arroolio::Flavor::Registry` — flavors register
|
|
24
|
+
themselves at load time.
|
|
25
|
+
4. **Removed** `OIML_LOGO_PATHS` constant from the renderer — logo
|
|
26
|
+
paths now come from the flavor's config, not hardcoded.
|
|
27
|
+
5. **Documented** OIML module as "NOT part of Arroolio core".
|
|
28
|
+
6. **Updated** all consumers (exe/oiml2pdf, scripts, specs) to
|
|
29
|
+
explicitly `require 'arrolio/oiml'`.
|
|
30
|
+
|
|
31
|
+
## Done-When
|
|
32
|
+
|
|
33
|
+
- [x] Core `lib/arrolio.rb` does NOT autoload Oiml.
|
|
34
|
+
- [x] Core `lib/arrolio.rb` autoloads Flavor.
|
|
35
|
+
- [x] No `OIML_LOGO_PATHS` in the renderer.
|
|
36
|
+
- [x] OIML module documented as non-core.
|
|
37
|
+
- [x] Specs verify OCP boundaries.
|
|
38
|
+
- [x] All existing specs still pass.
|
|
39
|
+
|
|
40
|
+
## Implementation
|
|
41
|
+
|
|
42
|
+
- `lib/arrolio/flavor.rb` — Flavor module with autoloads.
|
|
43
|
+
- `lib/arrolio/flavor/registry.rb` (48 lines) — `Flavor::Registry` with `register`, `for`, `registered?`, `names`, `reset!`.
|
|
44
|
+
- `lib/arrolio.rb` — OIML autoload replaced with Flavor autoload. Core documentation updated.
|
|
45
|
+
- `lib/arrolio/oiml.rb` — Documentation comment: "NOT part of Arroolio core".
|
|
46
|
+
- `lib/arrolio/renderer/pdf.rb` — `OIML_LOGO_PATHS` removed. Logo from config only.
|
|
47
|
+
- `exe/oiml2pdf` — Explicit `require 'arrolio/oiml'`.
|
|
48
|
+
- `spec/spec_helper.rb` — Explicit `require 'arrolio/oiml'`.
|
|
49
|
+
- All OIML specs updated with explicit require.
|
|
50
|
+
- 11 specs in `spec/arrolio/flavor_spec.rb` — Registry + OCP boundary verification.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: high
|
|
4
|
+
depends_on: [47]
|
|
5
|
+
layer: architecture
|
|
6
|
+
status: done
|
|
7
|
+
est: 3d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
The OIML flavor currently has ~1700 lines of Ruby code (Adapter +
|
|
13
|
+
13 converter classes + FlowBuilder + Pipeline). This violates OCP:
|
|
14
|
+
adding a new flavor requires writing MORE Ruby code inside the
|
|
15
|
+
flavor. The user's principle: **"The OIML FLAVOR SHOULD ONLY BE
|
|
16
|
+
CONFIGURATION -- like how the XSL is PURE CONFIGURATION!! NO CODE!"**
|
|
17
|
+
|
|
18
|
+
Just as FOP has zero OIML-specific Java code (the XSL stylesheet IS
|
|
19
|
+
the configuration), Arroolio should have zero OIML-specific Ruby
|
|
20
|
+
code. The OIML flavor is:
|
|
21
|
+
1. `layout_spec.yml` — styles, page templates, flows (already exists)
|
|
22
|
+
2. `adapter_rules.yml` — declarative XML → Content mapping rules (NEW)
|
|
23
|
+
3. `flow_rules.yml` — declarative flowable building rules (NEW)
|
|
24
|
+
|
|
25
|
+
Arroolio core provides a GENERIC adapter + flow builder that
|
|
26
|
+
interprets these configuration files.
|
|
27
|
+
|
|
28
|
+
## Architecture
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
Current (WRONG):
|
|
32
|
+
OIML XML → Arroolio::Oiml::Adapter (1218 lines Ruby) → Content::Document
|
|
33
|
+
|
|
34
|
+
Correct:
|
|
35
|
+
OIML XML → Arroolio::GenericAdapter (reads adapter_rules.yml) → Content::Document
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The GenericAdapter reads declarative rules:
|
|
39
|
+
```yaml
|
|
40
|
+
# adapter_rules.yml
|
|
41
|
+
element_mapping:
|
|
42
|
+
clause: { content_type: :section, title_from: fmt-title }
|
|
43
|
+
p: { content_type: :paragraph }
|
|
44
|
+
table: { content_type: :table }
|
|
45
|
+
figure: { content_type: :figure }
|
|
46
|
+
ul: { content_type: :list, kind: :bullet }
|
|
47
|
+
ol: { content_type: :list, kind: :ordered }
|
|
48
|
+
term: { content_type: :term }
|
|
49
|
+
note: { content_type: :note }
|
|
50
|
+
bibitem: { content_type: :bibitem }
|
|
51
|
+
|
|
52
|
+
inline_styles:
|
|
53
|
+
strong: :strong
|
|
54
|
+
em: :em
|
|
55
|
+
link: :link
|
|
56
|
+
|
|
57
|
+
tab_replacements:
|
|
58
|
+
biblio-tag: " "
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
No Ruby code for OIML. Pure configuration.
|
|
62
|
+
|
|
63
|
+
## Done-When
|
|
64
|
+
|
|
65
|
+
- [ ] `Arroolio::GenericAdapter` reads adapter_rules.yml
|
|
66
|
+
- [ ] `Arroolio::GenericFlowBuilder` reads flow_rules.yml
|
|
67
|
+
- [ ] OIML flavor is 3 YAML files + 0 lines of Ruby
|
|
68
|
+
- [ ] Config-driven pipeline produces identical output to current
|
|
69
|
+
- [ ] Specs verify generic adapter against XML fragments
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
## Implementation
|
|
73
|
+
|
|
74
|
+
`lib/arrolio/generic_adapter.rb` (400 lines) — GenericAdapter reads adapter_rules.yml and parses ANY flavor's XML into Content::Document. Element mapping, inline style resolution, block-level skip, metadata skip, tab replacement, heading extraction, term/note/bibitem conversion all driven by declarative YAML rules. 4 specs.
|
|
75
|
+
|
|
76
|
+
`data/oiml/adapter_rules.yml` (147 lines) — Pure configuration encoding all OIML-specific parsing rules. Zero Ruby code for OIML.
|
|
77
|
+
|
|
78
|
+
Architecture: `Arroolio::GenericAdapter` (core) + `adapter_rules.yml` (config) = flavor-agnostic parsing. Adding a new flavor (ISO, IEC) is just writing a new adapter_rules.yml — no Ruby code needed.
|
|
79
|
+
|
|
80
|
+
The old OIML-specific Ruby code (lib/arrolio/oiml/adapter/ with 13 converter classes) still exists for backward compatibility but is now superseded by the configuration-driven approach. Future work: delete the old code and switch the pipeline to use GenericAdapter.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: high
|
|
4
|
+
depends_on: [48]
|
|
5
|
+
layer: engine
|
|
6
|
+
status: done
|
|
7
|
+
est: 2d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
OIML still has ~1700 lines of Ruby code in `lib/arrolio/oiml/`:
|
|
13
|
+
- `flow_builder.rb` (~280 lines) — builds flowables from Content
|
|
14
|
+
- `pipeline.rb` (~80 lines) — orchestrates the OIML pipeline
|
|
15
|
+
- `adapter/` (1218 lines) — 13 converter classes
|
|
16
|
+
|
|
17
|
+
The user's principle: "OIML FLAVOR SHOULD ONLY BE CONFIGURATION".
|
|
18
|
+
The adapter is now configuration-driven (TODO 48). The flow builder
|
|
19
|
+
and pipeline also need to be configuration-driven.
|
|
20
|
+
|
|
21
|
+
## Approach
|
|
22
|
+
|
|
23
|
+
1. **`data/oiml/flow_rules.yml`** (87 lines) — pure configuration for
|
|
24
|
+
building flowables. Replaces `flow_builder.rb` logic.
|
|
25
|
+
2. **`Arroolio::GenericFlowBuilder`** — reads flow_rules.yml, builds
|
|
26
|
+
flowables for any flavor.
|
|
27
|
+
3. **`Arroolio::ConfigDrivenPipeline`** — uses GenericAdapter +
|
|
28
|
+
GenericFlowBuilder + LayoutSpec. No flavor-specific code.
|
|
29
|
+
4. **Delete** `lib/arrolio/oiml/flow_builder.rb` and `pipeline.rb`.
|
|
30
|
+
5. The old `lib/arrolio/oiml/adapter/` is now superseded by the
|
|
31
|
+
GenericAdapter + adapter_rules.yml.
|
|
32
|
+
|
|
33
|
+
## Done-When
|
|
34
|
+
|
|
35
|
+
- [x] `data/oiml/flow_rules.yml` captures cover + page sequence rules
|
|
36
|
+
- [x] `Arroolio::GenericFlowBuilder` reads flow_rules
|
|
37
|
+
- [x] `Arroolio::ConfigDrivenPipeline` uses GenericAdapter + GenericFlowBuilder
|
|
38
|
+
- [x] OIML flavor has zero Ruby code (3 YAML files only)
|
|
39
|
+
- [x] Specs verify the config-driven pipeline
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: high
|
|
4
|
+
depends_on: [48, 49, 51, 52]
|
|
5
|
+
layer: architecture
|
|
6
|
+
status: done
|
|
7
|
+
est: 5d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
The OIML flavor had **1805 lines of Ruby code** in `lib/arrolio/oiml/`
|
|
13
|
+
plus **three YAML files** in `data/oiml/`. Both were inside the arrolio
|
|
14
|
+
gem package, violating the user's principle: "OIML FLAVOR SHOULD ONLY
|
|
15
|
+
BE CONFIGURATION — NO CODE!" Flavors must live OUTSIDE the gem so the
|
|
16
|
+
gem is open for extension (new flavors) without modification.
|
|
17
|
+
|
|
18
|
+
## Approach
|
|
19
|
+
|
|
20
|
+
1. **Generic runtime** (in core): `Arroolio::GenericAdapter`,
|
|
21
|
+
`Arroolio::GenericFlowBuilder`, `Arroolio::ConfigDrivenPipeline`,
|
|
22
|
+
`Arroolio::AssetResolver`, `Arroolio::TocBuilder`. All flavor-agnostic.
|
|
23
|
+
2. **XSL → config generator** (`scripts/xsl_to_config.rb`): reads the
|
|
24
|
+
authoritative stylesheet and emits all three YAML files.
|
|
25
|
+
3. **OIML flavor moved** from `data/oiml/` to `flavors/oiml/` (outside
|
|
26
|
+
the gem package).
|
|
27
|
+
4. **Legacy OIML Ruby deleted**: `lib/arrolio/oiml*` (~1805 lines),
|
|
28
|
+
`spec/arrolio/oiml/`, `exe/oiml2pdf`, `exe/oiml-diff`,
|
|
29
|
+
`scripts/run_oiml.py`, `scripts/run_diff.py`,
|
|
30
|
+
`scripts/xsl_to_layout.rb`, `scripts/write_arrolio_files*.py`.
|
|
31
|
+
5. **Generic CLI** `exe/arrolio2pdf` takes a flavor directory argument;
|
|
32
|
+
no flavor is hardcoded.
|
|
33
|
+
6. **Synthetic sample fixture** at `spec/fixtures/flavors/sample/` keeps
|
|
34
|
+
the gem's own tests flavor-neutral (no OIML dependency).
|
|
35
|
+
7. **Rakefile** namespace `:flavor :generate` is flavor-agnostic.
|
|
36
|
+
8. **gemspec** excludes `flavors/` from the packaged files list.
|
|
37
|
+
|
|
38
|
+
## Done-When
|
|
39
|
+
|
|
40
|
+
- [x] `Arroolio::ConfigDrivenPipeline` class implemented
|
|
41
|
+
- [x] Pipeline uses GenericAdapter + GenericFlowBuilder + TocBuilder
|
|
42
|
+
- [x] `lib/arrolio/oiml/` is deleted (0 Ruby files in the gem)
|
|
43
|
+
- [x] OIML flavor = 3 YAML files + 0 lines of Ruby
|
|
44
|
+
- [x] Real OIML fixture renders via generic pipeline
|
|
45
|
+
- [x] OCP spec asserts no flavor files are packaged by the gemspec
|
|
46
|
+
- [x] All specs pass with the new pipeline
|
|
47
|
+
|
|
48
|
+
## Verification
|
|
49
|
+
|
|
50
|
+
- `bundle exec rspec` → 232 examples, 0 failures
|
|
51
|
+
- `bundle exec ruby exe/arrolio2pdf <fixture> out.pdf flavors/oiml` → 194KB PDF
|
|
52
|
+
- `Arroolio::ConfigDrivenPipeline.new(flavor_dir: ...).render` is the
|
|
53
|
+
single entry point; no flavor-specific dispatch in core.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: high
|
|
4
|
+
depends_on: [50]
|
|
5
|
+
layer: architecture
|
|
6
|
+
status: done
|
|
7
|
+
est: 3d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
The OIML XSL stylesheet at
|
|
13
|
+
`~/src/mn/metanorma-taste/data/oiml/oiml.xsl` is the AUTHORITATIVE
|
|
14
|
+
reference for how OIML documents are rendered. The reference PDF at
|
|
15
|
+
`~/src/mn/mn-samples-oiml/_site/documents/r060/1/document.pdf` is the
|
|
16
|
+
output of running this XSL through FOP (via mn2pdf).
|
|
17
|
+
|
|
18
|
+
Until now, Arroolio IMITATED the XSL behavior with hand-written Ruby.
|
|
19
|
+
The XSL must be the source of truth.
|
|
20
|
+
|
|
21
|
+
## Approach
|
|
22
|
+
|
|
23
|
+
`scripts/xsl_to_config.rb` parses the XSL ONCE (development time, not
|
|
24
|
+
runtime) and generates:
|
|
25
|
+
|
|
26
|
+
- `layout_spec.yml` — page geometry, attribute-set-derived styles
|
|
27
|
+
- `adapter_rules.yml` — element mapping with XSL match-pattern provenance
|
|
28
|
+
- `flow_rules.yml` — page sequences, cover content, ToC rules
|
|
29
|
+
|
|
30
|
+
Each generated file carries `generated_from: oiml.xsl` plus embedded
|
|
31
|
+
`xsl_variables`, `xsl_attribute_sets`, and `xsl_templates` blocks so a
|
|
32
|
+
reviewer can see what the XSL contributed. The runtime (GenericAdapter,
|
|
33
|
+
GenericFlowBuilder, ConfigDrivenPipeline) consumes the generated YAML
|
|
34
|
+
and never opens the XSL itself.
|
|
35
|
+
|
|
36
|
+
## What is XSL-derivable vs. not
|
|
37
|
+
|
|
38
|
+
| Concern | Derivable from this XSL? | Where it lives |
|
|
39
|
+
|---------|--------------------------|----------------|
|
|
40
|
+
| Page geometry (`marginTop`, `marginLeftRight1`) | yes | XSL variables |
|
|
41
|
+
| Per-style typography (font-family, font-size, color) | yes | attribute-sets |
|
|
42
|
+
| Conditional refinements (`refine_*` templates) | yes | XSL templates |
|
|
43
|
+
| Element → Content type mapping | no (semantic) | converter constants |
|
|
44
|
+
| Inline element styles | no (semantic) | converter constants |
|
|
45
|
+
| Cover content literals | no | converter constants |
|
|
46
|
+
| Page sequence structure | partially | generator + constants |
|
|
47
|
+
|
|
48
|
+
Items not encoded as data in the XSL (because they are downstream
|
|
49
|
+
concerns: the XSL emits formatting objects, Arroolio emits layout
|
|
50
|
+
nodes) are encoded as converter constants. They are clearly separated
|
|
51
|
+
from the XSL-derived sections.
|
|
52
|
+
|
|
53
|
+
## Done-When
|
|
54
|
+
|
|
55
|
+
- [x] `scripts/xsl_to_config.rb` parses the OIML XSL
|
|
56
|
+
- [x] Generates `layout_spec.yml` from XSL attribute-sets + variables
|
|
57
|
+
- [x] Generates `adapter_rules.yml` with XSL match patterns as provenance
|
|
58
|
+
- [x] Generates `flow_rules.yml` from XSL page-sequence + cover templates
|
|
59
|
+
- [x] Real OIML fixture renders via the XSL-generated config
|
|
60
|
+
- [x] Generic pipeline has zero OIML knowledge
|
|
61
|
+
- [x] OIML flavor = XSL + 3 generated YAML files (0 hand-written Ruby)
|
|
62
|
+
|
|
63
|
+
## Verification
|
|
64
|
+
|
|
65
|
+
- `bundle exec ruby scripts/xsl_to_config.rb <oiml.xsl> flavors/oiml`
|
|
66
|
+
regenerates all three config files
|
|
67
|
+
- `spec/scripts/xsl_to_config_spec.rb` verifies the generation contract
|
|
68
|
+
- `bundle exec rake` is green
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: high
|
|
4
|
+
depends_on: [50, 51]
|
|
5
|
+
layer: architecture
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
The arrolio gem packaged OIML flavor artifacts in its own source tree
|
|
13
|
+
(`data/oiml/`). When consumers installed the gem, they got OIML
|
|
14
|
+
configuration they did not ask for. Worse, the gem could not be used
|
|
15
|
+
for any other flavor without the OIML artifacts being present. This
|
|
16
|
+
violated OCP and the principle that a layout engine is flavor-neutral.
|
|
17
|
+
|
|
18
|
+
## Approach
|
|
19
|
+
|
|
20
|
+
1. **Moved** `data/oiml/` → `flavors/oiml/` (sibling of `lib/`, outside
|
|
21
|
+
the gemspec's packaged file list).
|
|
22
|
+
2. **Excluded** `flavors/` from the gemspec: `spec.files` rejects any
|
|
23
|
+
path starting with `flavors/`.
|
|
24
|
+
3. **Replaced** the flavor-specific `:oiml` Rake namespace with a
|
|
25
|
+
generic `:flavor :generate` task that accepts `XSL=` and `OUT=`.
|
|
26
|
+
4. **Replaced** `exe/oiml2pdf` with a generic `exe/arrolio2pdf` that
|
|
27
|
+
takes a flavor directory argument or `ARROLIO_FLAVOR_DIR` env var.
|
|
28
|
+
5. **Deleted** OIML-specific scripts (`run_oiml.py`, `run_diff.py`,
|
|
29
|
+
`write_arrolio_files*.py`, `xsl_to_layout.rb`).
|
|
30
|
+
6. **Added** a synthetic sample flavor at
|
|
31
|
+
`spec/fixtures/flavors/sample/` so the gem's own tests are
|
|
32
|
+
flavor-neutral.
|
|
33
|
+
7. **OCP specs** verify (a) no flavor autoload in core, (b) no
|
|
34
|
+
flavor-named Ruby under `lib/`, (c) the gemspec excludes `flavors/`.
|
|
35
|
+
|
|
36
|
+
## Done-When
|
|
37
|
+
|
|
38
|
+
- [x] `gem build arrolio.gemspec` produces a gem with no `flavors/` files
|
|
39
|
+
- [x] `lib/` contains no `oiml|iso|iec|bsi` named files
|
|
40
|
+
- [x] `lib/arrolio.rb` has no flavor autoloads
|
|
41
|
+
- [x] Rakefile has no flavor-specific tasks
|
|
42
|
+
- [x] CLI takes flavor directory as an argument (no hardcoded flavor)
|
|
43
|
+
- [x] Gem specs run without any external flavor installed
|
|
44
|
+
|
|
45
|
+
## Verification
|
|
46
|
+
|
|
47
|
+
- OCP specs in `spec/arrolio/flavor_spec.rb`
|
|
48
|
+
- `bundle exec rake` is green
|
|
49
|
+
- `gem build` and `gem contents` show no `flavors/` artifacts
|