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,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: high
|
|
4
|
+
depends_on: [52]
|
|
5
|
+
layer: architecture
|
|
6
|
+
status: done
|
|
7
|
+
est: 2d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
The generic pipeline handled only one real flavor (OIML). Without a
|
|
13
|
+
second flavor implemented using only XSL + 3 generated YAML files, the
|
|
14
|
+
"any flavor is pure configuration" claim was unverified.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
Created a synthetic second flavor at
|
|
19
|
+
`spec/fixtures/flavors/altvocab/` with a deliberately non-Metanorma
|
|
20
|
+
vocabulary:
|
|
21
|
+
|
|
22
|
+
| Role | sample (standoc) | altvocab |
|
|
23
|
+
|------|------------------|----------|
|
|
24
|
+
| Container | `metanorma` | `article` |
|
|
25
|
+
| Metadata | `bibdata` | `meta` |
|
|
26
|
+
| Sections container | `sections` | `body` |
|
|
27
|
+
| Section | `clause` | `sec` |
|
|
28
|
+
| Heading | `fmt-title` (depth) | `h2` (level) |
|
|
29
|
+
| Paragraph | `p` | `para` |
|
|
30
|
+
| List item | `li` | `li` (same) |
|
|
31
|
+
| Marker | `fmt-name` | `marker` |
|
|
32
|
+
| Note label | `fmt-name` | `label` |
|
|
33
|
+
| Figure image | `image` | `img` |
|
|
34
|
+
| Figure caption | `fmt-name` | `caption` |
|
|
35
|
+
| Table row/cell | `tr`/`td,th` | `row`/`cell,header` |
|
|
36
|
+
| Bibliography | `bibliography` | `refs` |
|
|
37
|
+
| References | `references` | `list` |
|
|
38
|
+
| Item | `bibitem` | `entry` |
|
|
39
|
+
| Inline strong | `strong` | `b` |
|
|
40
|
+
| Inline italic | `em` | `i` |
|
|
41
|
+
| Stem | `stem`/`fmt-stem` | `math`/`mathfmt` |
|
|
42
|
+
|
|
43
|
+
This is a genuinely different vocabulary — not just renamed standoc.
|
|
44
|
+
The fact that the generic adapter parses it correctly proves there is
|
|
45
|
+
no hidden Metanorma dependency.
|
|
46
|
+
|
|
47
|
+
The flavor is rendered through the same `ConfigDrivenPipeline` with
|
|
48
|
+
zero core code changes. All element selectors come from the flavor's
|
|
49
|
+
`adapter_rules.yml`. All flow rules come from its `flow_rules.yml`.
|
|
50
|
+
All styles come from its `layout_spec.yml`. It declares its own
|
|
51
|
+
`manifest.yml`.
|
|
52
|
+
|
|
53
|
+
## Done-When
|
|
54
|
+
|
|
55
|
+
- [x] Second flavor directory exists at `spec/fixtures/flavors/altvocab/`
|
|
56
|
+
- [x] Uses a deliberately non-Metanorma vocabulary (article/sec/para/h2)
|
|
57
|
+
- [x] Renders a fixture via the generic pipeline with zero core changes
|
|
58
|
+
- [x] All selectors come from `adapter_rules.yml`
|
|
59
|
+
- [x] Manifest is loadable via `Flavor::Manifest.load`
|
|
60
|
+
- [x] Specs verify parse correctness + PDF render + manifest exposure
|
|
61
|
+
- [x] All 267+ specs still pass
|
|
62
|
+
|
|
63
|
+
## Verification
|
|
64
|
+
|
|
65
|
+
- `spec/arrolio/second_flavor_spike_spec.rb` (3 specs)
|
|
66
|
+
- `bundle exec rake` is green
|
|
67
|
+
- `bundle exec ruby exe/arrolio2pdf <fixture.xml> out.pdf spec/fixtures/flavors/altvocab`
|
|
68
|
+
|
|
69
|
+
## Outcome
|
|
70
|
+
|
|
71
|
+
The generic design is now proven across two distinct vocabularies
|
|
72
|
+
(Metanorma standoc + synthetic altvocab). Adding a third real-world
|
|
73
|
+
flavor (ISO, IEC, BSI, DITA, DocBook) is a configuration exercise, not
|
|
74
|
+
a code change.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P1
|
|
3
|
+
impact: med
|
|
4
|
+
depends_on: [50]
|
|
5
|
+
layer: architecture
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
A flavor directory was just "three YAML files". There was no
|
|
13
|
+
machine-readable manifest declaring the flavor's name, version,
|
|
14
|
+
upstream XSL, required fonts, or config-file locations. This meant:
|
|
15
|
+
|
|
16
|
+
- No way to detect a broken/truncated flavor directory at load time.
|
|
17
|
+
- No way to surface flavor metadata in error messages.
|
|
18
|
+
- No discovery mechanism for installed flavor gems.
|
|
19
|
+
|
|
20
|
+
## Approach
|
|
21
|
+
|
|
22
|
+
1. **`flavors/<name>/manifest.yml`** is the flavor's contract:
|
|
23
|
+
|
|
24
|
+
```yaml
|
|
25
|
+
name: oiml
|
|
26
|
+
version: 1.0.0
|
|
27
|
+
description: OIML International Recommendation
|
|
28
|
+
upstream:
|
|
29
|
+
xsl: oiml.xsl
|
|
30
|
+
doctypes:
|
|
31
|
+
- recommendation
|
|
32
|
+
- document
|
|
33
|
+
fonts:
|
|
34
|
+
required:
|
|
35
|
+
- Times New Roman
|
|
36
|
+
- Jost
|
|
37
|
+
optional:
|
|
38
|
+
- Cambria Math
|
|
39
|
+
config_files:
|
|
40
|
+
layout_spec: layout_spec.yml
|
|
41
|
+
adapter_rules: adapter_rules.yml
|
|
42
|
+
flow_rules: flow_rules.yml
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
2. **`Arroolio::Flavor::Manifest`** class (autoloaded from
|
|
46
|
+
`lib/arrolio/flavor/manifest.rb`): loads + validates the manifest,
|
|
47
|
+
provides typed accessors (`name`, `version`, `description`,
|
|
48
|
+
`required_fonts`, `optional_fonts`, `config_path_for(:layout_spec)`).
|
|
49
|
+
3. **`ConfigDrivenPipeline.new(flavor_dir:)`** auto-detects the
|
|
50
|
+
manifest if present and exposes it via `pipeline.manifest`.
|
|
51
|
+
4. **`Flavor::Manifest.load(flavor_dir)`** raises
|
|
52
|
+
`Arroolio::FlavorError` (new typed error) with structured metadata
|
|
53
|
+
when the manifest is missing or malformed.
|
|
54
|
+
5. **Manifests added** to `flavors/oiml/manifest.yml` and
|
|
55
|
+
`spec/fixtures/flavors/sample/manifest.yml`.
|
|
56
|
+
|
|
57
|
+
## Done-When
|
|
58
|
+
|
|
59
|
+
- [x] `Arroolio::Flavor::Manifest` class with typed accessors
|
|
60
|
+
- [x] `Arroolio::FlavorError` typed error for missing/malformed manifests
|
|
61
|
+
- [x] `flavors/oiml/manifest.yml` exists
|
|
62
|
+
- [x] `spec/fixtures/flavors/sample/manifest.yml` exists
|
|
63
|
+
- [x] `ConfigDrivenPipeline` exposes `pipeline.manifest`
|
|
64
|
+
- [x] Specs cover: valid manifest load, missing manifest, malformed
|
|
65
|
+
manifest, missing required field
|
|
66
|
+
- [x] All 249+ specs still pass
|
|
67
|
+
|
|
68
|
+
## Verification
|
|
69
|
+
|
|
70
|
+
- `bundle exec rspec spec/arrolio/flavor/manifest_spec.rb`
|
|
71
|
+
- `bundle exec rake` is green
|
|
72
|
+
- OIML r060/1 fixture still renders via the generic pipeline
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P1
|
|
3
|
+
impact: med
|
|
4
|
+
depends_on: [50, 63]
|
|
5
|
+
layer: render
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
The renderer (`lib/arrolio/renderer/pdf.rb`) hardcoded OIML-specific
|
|
13
|
+
header/footer and cover-logo values:
|
|
14
|
+
|
|
15
|
+
- Margins: `26.5 * MM_TO_PT` / `25.5 * MM_TO_PT` (OIML values)
|
|
16
|
+
- Header/footer font: `'Arial'` hardcoded
|
|
17
|
+
- Header/footer size: `9.0` hardcoded
|
|
18
|
+
- Rule width: `0.5` hardcoded
|
|
19
|
+
- Cover logo dimensions: `35.0mm` / `1459.0/1667.0` aspect (OIML logo)
|
|
20
|
+
- Cover logo margin: `25.5mm`
|
|
21
|
+
|
|
22
|
+
These are all OIML conventions. A flavor with different margins,
|
|
23
|
+
fonts, or logo dimensions would not render correctly without
|
|
24
|
+
modifying core — violating OCP.
|
|
25
|
+
|
|
26
|
+
## Approach
|
|
27
|
+
|
|
28
|
+
1. **LayoutSpec carries config**: `LayoutSpec` now exposes
|
|
29
|
+
`header_footer_config` and `cover_logo_config` Hashes, loaded from
|
|
30
|
+
`layout_spec.yml`'s `header_footer:` and `cover_logo:` blocks.
|
|
31
|
+
2. **Renderer reads from layout_spec**: `Renderer::Pdf#render`
|
|
32
|
+
accepts a `layout_spec:` keyword; `header_footer_style` and
|
|
33
|
+
`cover_logo_style` read values from it (falling back to
|
|
34
|
+
engine-safe defaults when nil).
|
|
35
|
+
3. **Pipeline passes layout_spec through**: `ConfigDrivenPipeline`
|
|
36
|
+
forwards `layout_spec:` to `Renderer::Pdf#render`.
|
|
37
|
+
4. **Flavor YAML declares its values**: `flavors/oiml/layout_spec.yml`
|
|
38
|
+
declares `header_footer:` (Arial 9pt + 0.5pt rule + 26.5/25.5mm
|
|
39
|
+
margins) and `cover_logo:` (35mm / 0.8758 aspect / 25.5mm margin).
|
|
40
|
+
5. **Sample fixture also declares** its own (Helvetica-based) values.
|
|
41
|
+
|
|
42
|
+
## Done-When
|
|
43
|
+
|
|
44
|
+
- [x] `LayoutSpec` exposes `header_footer_config` and `cover_logo_config`
|
|
45
|
+
- [x] `Loader` reads them from `layout_spec.yml`
|
|
46
|
+
- [x] `Renderer::Pdf#render` accepts `layout_spec:` and reads values
|
|
47
|
+
- [x] No hardcoded `'Arial'` or `26.5` literals in the renderer logic
|
|
48
|
+
- [x] Engine-safe defaults apply when `layout_spec:` is nil
|
|
49
|
+
- [x] Specs verify: configured values used, defaults used, both paths
|
|
50
|
+
tested
|
|
51
|
+
- [x] OIML r060/1 still renders 28 pages via the generic pipeline
|
|
52
|
+
|
|
53
|
+
## Verification
|
|
54
|
+
|
|
55
|
+
- `spec/arrolio/layout_spec_config_spec.rb` (7 specs)
|
|
56
|
+
- `bundle exec rake` is green
|
|
57
|
+
- OIML render via `exe/arrolio2pdf` produces a 28-page PDF
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P1
|
|
3
|
+
impact: high
|
|
4
|
+
depends_on: [50, 52, 63]
|
|
5
|
+
layer: architecture
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
The generic adapter still hardcoded Metanorma standoc vocabulary
|
|
13
|
+
throughout (`preface`, `bibliography`, `references`, `bibitem`,
|
|
14
|
+
`clause`, `foreword`, `title`, `span`, `tab`, `br`). A flavor with a
|
|
15
|
+
different vocabulary (DITA, DocBook, JATS) would not work without
|
|
16
|
+
modifying core — violating OCP.
|
|
17
|
+
|
|
18
|
+
## Approach
|
|
19
|
+
|
|
20
|
+
1. **Extended `selectors` block** in `adapter_rules.yml`: added keys
|
|
21
|
+
for `preface_container`, `preface_children`, `bibliography_container`,
|
|
22
|
+
`bibliography_reference`, `bibliography_item`, `fallback_title`,
|
|
23
|
+
`span`, `tab_inline`, `break_inline`.
|
|
24
|
+
2. **`GenericAdapter` reads selectors** for every element it touches —
|
|
25
|
+
no hardcoded element names outside the clearly-labelled
|
|
26
|
+
`DEFAULT_SELECTORS` constant.
|
|
27
|
+
3. **`DEFAULT_SELECTORS` documents the standoc convention** as a
|
|
28
|
+
fallback so flavors that omit selectors still work, but every
|
|
29
|
+
real flavor (OIML + sample fixture) declares its full selector set.
|
|
30
|
+
4. **`scripts/xsl_to_config.rb`** emits the full `selectors` block
|
|
31
|
+
(27 keys) in generated `adapter_rules.yml`.
|
|
32
|
+
5. **Bug fix**: `extract_bibliography` had referenced `preface_elem`
|
|
33
|
+
in the bibliography scope — fixed to `biblio_elem`.
|
|
34
|
+
6. **Strict typing in constructor**: `Content::Document.new` now
|
|
35
|
+
validates that `sections`, `preface`, and `bibliography` arrays
|
|
36
|
+
contain only `Content::Section` instances, raising `ContentError`
|
|
37
|
+
at the construction site rather than deep in the pipeline.
|
|
38
|
+
|
|
39
|
+
## Done-When
|
|
40
|
+
|
|
41
|
+
- [x] `GenericAdapter` has no Metanorma element names outside
|
|
42
|
+
`DEFAULT_SELECTORS`
|
|
43
|
+
- [x] `DEFAULT_SELECTORS` includes the 9 new document-structure keys
|
|
44
|
+
- [x] OCP grep spec enforces no `fmt-*`, no flavor literals, no OIML
|
|
45
|
+
references in the generic core
|
|
46
|
+
- [x] Non-Metanorma vocabulary spec proves the adapter parses
|
|
47
|
+
custom XML using only selectors
|
|
48
|
+
- [x] `Content::Document` constructor validates Section typing
|
|
49
|
+
- [x] Real OIML pipeline still renders 28 pages via the generic
|
|
50
|
+
pipeline
|
|
51
|
+
|
|
52
|
+
## Verification
|
|
53
|
+
|
|
54
|
+
- `spec/arrolio/ocp_grep_spec.rb` — 5 OCP guard specs
|
|
55
|
+
- `bundle exec rake` is green (244 examples)
|
|
56
|
+
- OIML render: 28 pages, ~258 KB
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: high
|
|
4
|
+
depends_on: [50, 63]
|
|
5
|
+
layer: adapter
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
`Content::Document#sections` returned a mixed Array that, despite
|
|
13
|
+
earlier fixes, had no constructor-level guarantee of being
|
|
14
|
+
`Section[]`. A flavor whose adapter mistakenly pushed a Paragraph
|
|
15
|
+
into the array would fail deep in the pipeline with `NoMethodError:
|
|
16
|
+
undefined method 'heading?'` rather than at the construction site.
|
|
17
|
+
|
|
18
|
+
This is a typing failure: `sections` should be `Section[]` by
|
|
19
|
+
contract. Validation belongs in the constructor.
|
|
20
|
+
|
|
21
|
+
## Approach
|
|
22
|
+
|
|
23
|
+
1. **Constructor validation**: `Content::Document.new` raises
|
|
24
|
+
`ContentError` if any element of `sections` (or `preface`,
|
|
25
|
+
`bibliography`) is not a `Content::Section`.
|
|
26
|
+
2. **Adapter guarantee**: `GenericAdapter#extract_sections` returns
|
|
27
|
+
only Section instances — non-section children of `<sections>` are
|
|
28
|
+
skipped (the previous behavior pushed zzSTDTitle paragraphs,
|
|
29
|
+
which is now handled by the cover extraction).
|
|
30
|
+
3. **Specs** cover the validation: a real Section passes; a
|
|
31
|
+
Paragraph raises with structured metadata.
|
|
32
|
+
|
|
33
|
+
## Done-When
|
|
34
|
+
|
|
35
|
+
- [x] `Content::Document#sections` is guaranteed `Section[]` by
|
|
36
|
+
constructor validation
|
|
37
|
+
- [x] `Content::Document#preface` and `#bibliography` are likewise
|
|
38
|
+
validated as `Section[]`
|
|
39
|
+
- [x] `GenericAdapter#extract_sections` returns `Section[]` only
|
|
40
|
+
- [x] Specs cover: valid sections pass; non-section raises
|
|
41
|
+
ContentError
|
|
42
|
+
- [x] Real OIML pipeline renders (25+ pages) without typing failures
|
|
43
|
+
|
|
44
|
+
## Verification
|
|
45
|
+
|
|
46
|
+
- `spec/arrolio/content/document_spec.rb` (or similar) covers the
|
|
47
|
+
constructor validation
|
|
48
|
+
- `bundle exec rake` is green
|
|
49
|
+
- OIML r060/1 fixture renders via the generic pipeline
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P1
|
|
3
|
+
impact: high
|
|
4
|
+
depends_on: [50, 52]
|
|
5
|
+
layer: architecture
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
There was no documented convention for how third parties should package,
|
|
13
|
+
install, and distribute Arroolio flavors. Without one, "flavors are
|
|
14
|
+
external" is an aspiration, not a deliverable.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
1. **Wrote `flavors/PACKAGING.md`**: the canonical guide describing
|
|
19
|
+
the anatomy of a flavor gem, the selector/layout_spec/flow_rules
|
|
20
|
+
contracts, the `Flavor::Registry` opt-in registration, the
|
|
21
|
+
`xsl_to_config.rb` workflow, and a verification checklist.
|
|
22
|
+
2. **Documented the selector contract**: every XML element/attribute
|
|
23
|
+
name the generic adapter touches is named in
|
|
24
|
+
`adapter_rules.yml`'s `selectors:` block (36 keys). A flavor with a
|
|
25
|
+
non-Metanorma vocabulary overrides these.
|
|
26
|
+
3. **Documented the layout_spec contract**: `header_footer:` and
|
|
27
|
+
`cover_logo:` blocks drive the renderer; `page_templates:`,
|
|
28
|
+
`styles:`, `flows:` drive the engine.
|
|
29
|
+
4. **Documented the flow_rules contract**: `page_sequences:[]`,
|
|
30
|
+
`cover_content:[]`, `section:`, `content_to_flowable:`, `toc:`.
|
|
31
|
+
|
|
32
|
+
## Done-When
|
|
33
|
+
|
|
34
|
+
- [x] `flavors/PACKAGING.md` exists with the full packaging contract
|
|
35
|
+
- [x] Selector contract documented (every key explained)
|
|
36
|
+
- [x] Layout_spec contract documented (every block explained)
|
|
37
|
+
- [x] Flow_rules contract documented (every block explained)
|
|
38
|
+
- [x] Registration-via-Registry pattern documented
|
|
39
|
+
- [x] Verification checklist for flavor encapsulation
|
|
40
|
+
|
|
41
|
+
## Verification
|
|
42
|
+
|
|
43
|
+
- `flavors/PACKAGING.md` is the single source of truth for flavor
|
|
44
|
+
authors
|
|
45
|
+
- The gem itself ships zero flavor artifacts (gemspec excludes
|
|
46
|
+
`flavors/`)
|
|
47
|
+
- The gem's own specs run without any real flavor installed
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P2
|
|
3
|
+
impact: med
|
|
4
|
+
depends_on: [50, 51]
|
|
5
|
+
layer: architecture
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
`scripts/xsl_to_config.rb` hardcoded constants that were not derivable
|
|
13
|
+
from the XSL but were OIML/Metanorma-specific:
|
|
14
|
+
|
|
15
|
+
- 6× `'oiml.xsl'` literal strings (claimed to be derived but were hardcoded)
|
|
16
|
+
- "Times New Roman Italic", "Times New Roman Bold" inline-style defaults
|
|
17
|
+
- "Jost SemiBold", "Jost", "Jost Light" cover-style defaults
|
|
18
|
+
- "Organisation Internationale de Métrologie Légale" + "International Organization of Legal Metrology" literals
|
|
19
|
+
- Standoc element vocabulary (`clause`, `p`, `fmt-title`, `biblio-tag`, ...)
|
|
20
|
+
- Hardcoded default XSL path pointing to OIML
|
|
21
|
+
|
|
22
|
+
Running the converter against an ISO/IEC/DITA XSL would have produced
|
|
23
|
+
YAML claiming OIML provenance with OIML font defaults — silently wrong.
|
|
24
|
+
|
|
25
|
+
## Approach
|
|
26
|
+
|
|
27
|
+
1. **Created `scripts/xsl_profiles/standoc.yml`** — the single file
|
|
28
|
+
where Metanorma standoc vocabulary lives. Carries:
|
|
29
|
+
- `element_mapping`, `inline_styles`, `span_class_styles`
|
|
30
|
+
- `block_level_elements`, `skip_metadata_elements`, `skip_elements`
|
|
31
|
+
- `selectors` (37 keys)
|
|
32
|
+
- `metadata_fields` (XPath mapping)
|
|
33
|
+
- `cover_fields`, `style_overrides`, `cover_content`
|
|
34
|
+
- `header_template`
|
|
35
|
+
|
|
36
|
+
2. **Refactored `xsl_to_config.rb`** to read everything from the
|
|
37
|
+
profile. The converter has ZERO hardcoded element names, font
|
|
38
|
+
names, or OIML literals.
|
|
39
|
+
|
|
40
|
+
3. **`generated_from` and `stylesheet`** are now derived from the XSL
|
|
41
|
+
filename via `File.basename(@xsl_path)`. A new `profile:` field
|
|
42
|
+
records which profile was used.
|
|
43
|
+
|
|
44
|
+
4. **Profile is a constructor argument** with a sensible default
|
|
45
|
+
(`scripts/xsl_profiles/standoc.yml`). A future DITA profile can be
|
|
46
|
+
added without touching the converter.
|
|
47
|
+
|
|
48
|
+
5. **Removed the OIML-default XSL path** from the CLI. The script now
|
|
49
|
+
requires explicit `<xsl-path> <output-dir> [profile-path]` args.
|
|
50
|
+
|
|
51
|
+
## Done-When
|
|
52
|
+
|
|
53
|
+
- [x] `scripts/xsl_profiles/standoc.yml` exists with the constants
|
|
54
|
+
- [x] Generator accepts `profile:` argument, defaults to standoc
|
|
55
|
+
- [x] No OIML-specific literals (`oiml`, `Times New Roman`, `Jost`,
|
|
56
|
+
`Organisation Internationale`) in the generator
|
|
57
|
+
- [x] Generated YAML carries `generated_from: <actual-xsl-filename>`
|
|
58
|
+
and `profile: standoc.yml`
|
|
59
|
+
- [x] Regenerating OIML config produces correct output
|
|
60
|
+
- [x] Real OIML fixture still renders 28 pages via the regenerated config
|
|
61
|
+
- [x] All specs pass
|
|
62
|
+
|
|
63
|
+
## Verification
|
|
64
|
+
|
|
65
|
+
- `grep 'oiml\|Times New Roman\|Jost' scripts/xsl_to_config.rb` → empty
|
|
66
|
+
- `head flavors/oiml/layout_spec.yml` shows
|
|
67
|
+
`generated_from: oiml.xsl`, `profile: standoc.yml`
|
|
68
|
+
- `bundle exec rake` is green
|
|
69
|
+
- `bundle exec ruby exe/arrolio2pdf <fixture.xml> out.pdf flavors/oiml`
|
|
70
|
+
produces a 28-page PDF
|
|
71
|
+
|
|
72
|
+
## Outcome
|
|
73
|
+
|
|
74
|
+
The converter is now truly generic. Adding a new vocabulary family
|
|
75
|
+
(DITA, DocBook, JATS) means adding `scripts/xsl_profiles/<family>.yml`
|
|
76
|
+
— no converter changes needed.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P2
|
|
3
|
+
impact: med
|
|
4
|
+
depends_on: [50]
|
|
5
|
+
layer: adapter
|
|
6
|
+
status: blocked
|
|
7
|
+
est: 2d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Status: blocked
|
|
11
|
+
|
|
12
|
+
Requires:
|
|
13
|
+
1. A fixture document that uses `<fmt-footnote-container>` /
|
|
14
|
+
`<fmt-fn-body>` with realistic content (the OIML r060/1 fixture
|
|
15
|
+
has none).
|
|
16
|
+
2. A reference PDF that renders these elements so we can measure
|
|
17
|
+
fidelity.
|
|
18
|
+
3. Page-bottom footnote-line rendering support in the engine (Engine
|
|
19
|
+
currently has no mechanism for per-page footnotes that collect
|
|
20
|
+
from the body and render at the bottom).
|
|
21
|
+
|
|
22
|
+
The foundation is in place — `Content::Footnote` is already defined
|
|
23
|
+
(autoloaded from `lib/arrolio/content/footnote.rb`), and the skip list
|
|
24
|
+
in `adapter_rules.yml` can drop `fmt-footnote-container`/`fmt-fn-body`
|
|
25
|
+
once the flow builder knows how to render them. But without fixtures
|
|
26
|
+
and an engine-side page-bottom collection mechanism, the implementation
|
|
27
|
+
cannot be validated.
|
|
28
|
+
|
|
29
|
+
## Problem (when unblocked)
|
|
30
|
+
|
|
31
|
+
The generic pipeline currently loses some information that the legacy
|
|
32
|
+
OIML adapter preserved:
|
|
33
|
+
|
|
34
|
+
- `fmt-footnote-container` / `fmt-fn-body` are in the `skip_elements`
|
|
35
|
+
list, so footnotes are dropped from the body and never rendered.
|
|
36
|
+
- `fmt-xref-label` is skipped, so cross-references to notes/examples
|
|
37
|
+
lose their visual prefix.
|
|
38
|
+
- The `example-body-style` margin-left and `note` list-block indent
|
|
39
|
+
from the XSL are not translated into Arroolio layout (notes/examples
|
|
40
|
+
render as plain paragraphs without the XSL's hanging indent).
|
|
41
|
+
|
|
42
|
+
## Approach (when unblocked)
|
|
43
|
+
|
|
44
|
+
1. **Footnote flowable** (`Arroolio::Flowables::FootnoteFlowable`):
|
|
45
|
+
carries a marker + body, renders as superscript marker in the body
|
|
46
|
+
text and a footnote line at the page bottom.
|
|
47
|
+
2. **Note hanging indent**: the XSL renders notes as `fo:list-block`
|
|
48
|
+
with `provisional-distance-between-starts: 14.5mm`. The
|
|
49
|
+
`GenericFlowBuilder` should emit a `NoteFlowable` (already exists)
|
|
50
|
+
with the configured indent.
|
|
51
|
+
3. **Example body margin**: `example-body-style` margin-left 12.5mm
|
|
52
|
+
should come from the layout_spec's `example_body` style.
|
|
53
|
+
4. **Cross-reference label**: `fmt-xref-label` should become a styled
|
|
54
|
+
inline run (caption_label).
|
|
55
|
+
|
|
56
|
+
## Done-When (when unblocked)
|
|
57
|
+
|
|
58
|
+
- [ ] Footnotes render with superscript markers + page-bottom lines
|
|
59
|
+
- [ ] Notes render with a hanging indent matching the XSL
|
|
60
|
+
- [ ] Examples render with the inner-paragraph left margin
|
|
61
|
+
- [ ] Cross-reference labels appear with the correct style
|
|
62
|
+
- [ ] Diff against the reference PDF improves for these features
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P1
|
|
3
|
+
impact: high
|
|
4
|
+
depends_on: [50]
|
|
5
|
+
layer: engine
|
|
6
|
+
status: done
|
|
7
|
+
est: 2d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
The pipeline swallowed many errors silently:
|
|
13
|
+
|
|
14
|
+
- Missing font file → warned and fell back to Helvetica, producing
|
|
15
|
+
visually wrong output for CJK or specialist fonts without telling
|
|
16
|
+
the caller anything was wrong.
|
|
17
|
+
- No `strict:` mode for callers who want to fail loud rather than
|
|
18
|
+
render broken output.
|
|
19
|
+
- `RenderError` was a bare class with no metadata.
|
|
20
|
+
|
|
21
|
+
## Approach
|
|
22
|
+
|
|
23
|
+
1. **`RenderError` typed metadata**: now carries `missing_fonts:` —
|
|
24
|
+
an Array of `[name, path]` pairs.
|
|
25
|
+
2. **`FontMetrics::Registry.register_ttf_all(paths, strict:)`**: when
|
|
26
|
+
`strict: true`, raises `Arroolio::RenderError` listing every
|
|
27
|
+
required font whose path does not exist on disk.
|
|
28
|
+
3. **`ConfigDrivenPipeline.new(strict: true)`**: forwards to the
|
|
29
|
+
registry, so the render fails at the font-registration step before
|
|
30
|
+
producing any output rather than midway through.
|
|
31
|
+
4. **`ConfigDrivenPipeline.render(..., strict: true)`**: same at the
|
|
32
|
+
class level.
|
|
33
|
+
5. **`pipeline.strict` reader** for inspection.
|
|
34
|
+
|
|
35
|
+
## Done-When
|
|
36
|
+
|
|
37
|
+
- [x] `Arroolio::RenderError` carries structured `missing_fonts:` metadata
|
|
38
|
+
- [x] `FontMetrics::Registry.register_ttf_all` accepts `strict:` kwarg
|
|
39
|
+
- [x] `ConfigDrivenPipeline` accepts `strict:` kwarg and forwards it
|
|
40
|
+
- [x] Default behavior (strict: false) is unchanged — backwards compatible
|
|
41
|
+
- [x] Specs cover: strict mode raises with missing_fonts; non-strict
|
|
42
|
+
mode does not raise; strict reader works
|
|
43
|
+
|
|
44
|
+
## Verification
|
|
45
|
+
|
|
46
|
+
- `spec/arrolio/strict_mode_spec.rb` (4 specs)
|
|
47
|
+
- `bundle exec rake` is green
|
|
48
|
+
- Real OIML fixture still renders 28 pages with `strict: false`
|
|
49
|
+
|
|
50
|
+
## Future strict-mode expansions
|
|
51
|
+
|
|
52
|
+
The pattern is established. Future strict checks can be added with
|
|
53
|
+
minimal ceremony:
|
|
54
|
+
|
|
55
|
+
- Missing image: `RenderError(missing_images:)` when an `<image src=>`
|
|
56
|
+
cannot be resolved and `strict:` is true.
|
|
57
|
+
- Unknown style: `LayoutSpecError(unknown_style:)` when a style_id
|
|
58
|
+
referenced by the flow builder isn't in the layout_spec's registry.
|
|
59
|
+
- Adapter selector gap: `AdapterError(missing_selector:)` when the
|
|
60
|
+
adapter needs a selector that the flavor didn't declare.
|
|
61
|
+
|
|
62
|
+
These are individually TODO-worthy but follow the same template.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P2
|
|
3
|
+
impact: med
|
|
4
|
+
depends_on: [50, 61]
|
|
5
|
+
layer: render
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
`layout_spec.yml` used absolute user-local paths for fonts
|
|
13
|
+
(`/Users/mulgogi/.fontist/fonts/Times.ttf`). This works on the
|
|
14
|
+
developer's machine but breaks for any other consumer. The generated
|
|
15
|
+
OIML `layout_spec.yml` baked in my home directory.
|
|
16
|
+
|
|
17
|
+
## Approach
|
|
18
|
+
|
|
19
|
+
Implemented `Arroolio::Font::Resolver` — a portable font-resolution
|
|
20
|
+
abstraction with a fallback chain:
|
|
21
|
+
|
|
22
|
+
1. **`FontManifest`** (from `layout_spec.yml`'s `font_manifest:` block):
|
|
23
|
+
explicit family → variant path mapping declared by the flavor.
|
|
24
|
+
2. **`font_paths`** (legacy explicit paths in `layout_spec.yml`):
|
|
25
|
+
preserved for backward compatibility.
|
|
26
|
+
3. **fontist** (optional runtime dependency): if the `fontist` gem is
|
|
27
|
+
available at runtime, families are looked up via fontist's
|
|
28
|
+
manifest. Skipped silently if fontist is not installed.
|
|
29
|
+
4. **PDF standard 14 fallback**: Helvetica, Times-Roman, Courier, etc.
|
|
30
|
+
resolve to themselves (built-in PDF fonts).
|
|
31
|
+
|
|
32
|
+
Strict mode (from TODO 61) raises `RenderError` listing every
|
|
33
|
+
unresolved required font. The resolver never hard-fails when fontist
|
|
34
|
+
is absent — it just falls through to the next strategy.
|
|
35
|
+
|
|
36
|
+
`LayoutSpec` now carries a `font_manifest_config` Hash loaded from
|
|
37
|
+
`layout_spec.yml`'s `font_manifest:` block, alongside the existing
|
|
38
|
+
`header_footer_config` and `cover_logo_config`.
|
|
39
|
+
|
|
40
|
+
## Done-When
|
|
41
|
+
|
|
42
|
+
- [x] `Arroolio::Font::Resolver` class exists, autoloaded
|
|
43
|
+
- [x] `Arroolio::Font::FontManifest` class exists, autoloaded
|
|
44
|
+
- [x] `LayoutSpec#font_manifest_config` accessor exists
|
|
45
|
+
- [x] `Loader` reads `font_manifest:` block from YAML
|
|
46
|
+
- [x] Resolution chain: manifest → font_paths → fontist → standard 14
|
|
47
|
+
- [x] Strict mode raises `RenderError` with `missing_fonts:` metadata
|
|
48
|
+
- [x] Specs cover: each resolution strategy, strict mode, standard 14,
|
|
49
|
+
manifest fallback paths, missing files
|
|
50
|
+
|
|
51
|
+
## Verification
|
|
52
|
+
|
|
53
|
+
- `spec/arrolio/font/resolver_spec.rb` (16 specs)
|
|
54
|
+
- `bundle exec rake` is green (283 specs)
|
|
55
|
+
- The resolver does NOT require fontist at install time — it's a
|
|
56
|
+
runtime-optional integration via `Object.const_defined?(:Fontist)`
|
|
57
|
+
|
|
58
|
+
## Outcome
|
|
59
|
+
|
|
60
|
+
Flavors can now declare fonts by family name with optional explicit
|
|
61
|
+
paths. When a flavor gem is packaged, the `font_manifest:` block can
|
|
62
|
+
list family names; at render time the resolver picks whichever path
|
|
63
|
+
the local environment provides (manifest, font_paths, or fontist).
|
|
64
|
+
|
|
65
|
+
Adding a real fontist integration later is a one-line change in
|
|
66
|
+
`fontist_lookup` — the abstraction is in place.
|