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,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: high
|
|
4
|
+
depends_on: [52]
|
|
5
|
+
layer: adapter
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
The "generic" adapter, flow builder, and pipeline still had Metanorma
|
|
13
|
+
standoc vocabulary hardcoded throughout:
|
|
14
|
+
|
|
15
|
+
- `fmt-title`, `fmt-name`, `fmt-stem`, `fmt-preferred`, `fmt-definition`,
|
|
16
|
+
`fmt-termsource`, `biblio-tag`, `formattedref` — all element names
|
|
17
|
+
baked into Ruby.
|
|
18
|
+
- The `depth` attribute name hardcoded.
|
|
19
|
+
- `zzSTDTitle` paragraph routing dropped silently.
|
|
20
|
+
- Figure display width hardcoded to 106pt.
|
|
21
|
+
- List bullet marker hardcoded to `■ `.
|
|
22
|
+
- ToC top offset hardcoded to 40pt.
|
|
23
|
+
|
|
24
|
+
These are Metanorma/OIML conventions. A flavor with a different
|
|
25
|
+
vocabulary (DITA, DocBook, JATS) would not work without modifying
|
|
26
|
+
core — violating OCP.
|
|
27
|
+
|
|
28
|
+
## Approach
|
|
29
|
+
|
|
30
|
+
1. **`adapter_rules.yml` selectors block**: declares every XML
|
|
31
|
+
element/attribute name the adapter touches. 27 keys covering
|
|
32
|
+
heading source, list items, table cells, bibliography tags, stem
|
|
33
|
+
elements, image attributes, autonum classes, etc.
|
|
34
|
+
2. **`GenericAdapter#selectors`**: reads the block, falls back to
|
|
35
|
+
`DEFAULT_SELECTORS` (clearly labelled as Metanorma standoc
|
|
36
|
+
convention) for backward compatibility.
|
|
37
|
+
3. **`GenericFlowBuilder#default_marker`**: reads
|
|
38
|
+
`flow_rules.yml[list.defaults.ordered_marker]` and
|
|
39
|
+
`[bullet_marker]`.
|
|
40
|
+
4. **`GenericFlowBuilder#image_flowable`**: reads
|
|
41
|
+
`flow_rules.yml[image.default_natural_width]`,
|
|
42
|
+
`[default_natural_height]`, `[max_display_width]`.
|
|
43
|
+
5. **`ConfigDrivenPipeline#populate_toc`**: reads
|
|
44
|
+
`flow_rules.yml[toc.top_offset]`.
|
|
45
|
+
6. **`scripts/xsl_to_config.rb`**: emits the `selectors` block in
|
|
46
|
+
generated `adapter_rules.yml` from a `SELECTORS` constant.
|
|
47
|
+
|
|
48
|
+
## Done-When
|
|
49
|
+
|
|
50
|
+
- [x] No flavor literals (`oiml`, `zzSTDTitle`, `Arial`, `Jost`,
|
|
51
|
+
`Times New Roman`) in `lib/arrolio/generic_*.rb`,
|
|
52
|
+
`lib/arrolio/config_driven_pipeline.rb`,
|
|
53
|
+
`lib/arrolio/asset_resolver.rb`, `lib/arrolio/toc_builder.rb`,
|
|
54
|
+
`exe/arrolio2pdf`
|
|
55
|
+
- [x] No `fmt-*` references in `GenericAdapter` outside the clearly
|
|
56
|
+
labelled `DEFAULT_SELECTORS` constant
|
|
57
|
+
- [x] Figure width, list markers, ToC offset all driven by config
|
|
58
|
+
- [x] OIML pipeline still renders (25 pages, ~218KB)
|
|
59
|
+
- [x] Sample fixture renders
|
|
60
|
+
- [x] All 232 specs pass
|
|
61
|
+
|
|
62
|
+
## Verification
|
|
63
|
+
|
|
64
|
+
- `grep -rn -i 'oiml\|zzSTDTitle\|"Arial"\|"Jost"' lib/arrolio/generic_*.rb
|
|
65
|
+
lib/arrolio/config_driven_pipeline.rb lib/arrolio/asset_resolver.rb
|
|
66
|
+
lib/arrolio/toc_builder.rb exe/arrolio2pdf` → empty
|
|
67
|
+
- `bundle exec rake` → green
|
|
68
|
+
- Real OIML render via `exe/arrolio2pdf` → 25-page PDF
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: high
|
|
4
|
+
depends_on: [63]
|
|
5
|
+
layer: test
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
After extracting Metanorma vocabulary into the `selectors` block, there
|
|
13
|
+
was no spec verifying that the generic adapter actually consults the
|
|
14
|
+
selectors (vs. silently falling back to hardcoded defaults). A future
|
|
15
|
+
edit could re-introduce a hardcoded element name and no test would
|
|
16
|
+
catch it.
|
|
17
|
+
|
|
18
|
+
## Approach
|
|
19
|
+
|
|
20
|
+
1. **Custom-selector spec**: build a synthetic flavor whose
|
|
21
|
+
`adapter_rules.yml` uses NON-Metanorma element names
|
|
22
|
+
(`sec` instead of `clause`, `para` instead of `p`, `tbl` instead
|
|
23
|
+
of `table`, etc.) and verify the adapter parses a matching XML
|
|
24
|
+
document correctly. This proves the adapter has no hardcoded
|
|
25
|
+
dependency on the Metanorma vocabulary.
|
|
26
|
+
2. **Grep-enforced OCP spec**: assert no flavor literals appear in the
|
|
27
|
+
generic core. This is a structural test that fails loudly if someone
|
|
28
|
+
re-introduces `fmt-title`, `oiml`, etc. into the wrong file.
|
|
29
|
+
3. **Fixture path coverage**: both the sample fixture (Metanorma-style)
|
|
30
|
+
and the synthetic non-Metanorma fixture pass through the same
|
|
31
|
+
generic pipeline.
|
|
32
|
+
|
|
33
|
+
## Done-When
|
|
34
|
+
|
|
35
|
+
- [x] Spec renders a non-Metanorma vocabulary through the generic adapter
|
|
36
|
+
- [x] Spec asserts no flavor literals in `lib/arrolio/generic_*.rb`,
|
|
37
|
+
`lib/arrolio/config_driven_pipeline.rb`,
|
|
38
|
+
`lib/arrolio/asset_resolver.rb`, `lib/arrolio/toc_builder.rb`,
|
|
39
|
+
`exe/arrolio2pdf`
|
|
40
|
+
- [x] All specs pass
|
|
41
|
+
|
|
42
|
+
## Why
|
|
43
|
+
|
|
44
|
+
A refactor that "extracts hardcoded values" without a test enforcing
|
|
45
|
+
the extraction is just a suggestion. The grep-enforced OCP spec is the
|
|
46
|
+
permanent guardrail — any future regression fails CI immediately.
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P1
|
|
3
|
+
impact: high
|
|
4
|
+
depends_on: [50, 63]
|
|
5
|
+
layer: content
|
|
6
|
+
status: done
|
|
7
|
+
est: 3d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Status
|
|
11
|
+
|
|
12
|
+
- Phase 1 (content type classes) — **done** (PR #1)
|
|
13
|
+
- Phase 2 (adapter emission + flow builder dispatch) — **done** (PR #2)
|
|
14
|
+
- Phase 3 (footnote extraction + endnote rendering) — **done** (PR #3)
|
|
15
|
+
- Phase 4 (page-bottom footnote rendering in Engine::Paged) — **done** (PR #5)
|
|
16
|
+
|
|
17
|
+
The architectural pipeline for semantic content types and footnotes
|
|
18
|
+
is complete end-to-end:
|
|
19
|
+
|
|
20
|
+
1. Adapter extracts `Content::Footnote` from `<fmt-footnote-container>`
|
|
21
|
+
2. Adapter emits `Content::Note`, `Content::FigureGroup`,
|
|
22
|
+
`Content::TermEntry`, `Content::BibliographyItem` for grouped elements
|
|
23
|
+
3. Flow builder dispatches on the new types via `is_a?` and emits
|
|
24
|
+
`NoteFlowable`, `ImageFlowable + TextFlowable`, etc. with proper
|
|
25
|
+
grouping and hanging indents
|
|
26
|
+
4. Flow builder can render endnotes (opt-in via `flow_rules.endnotes`)
|
|
27
|
+
5. Engine collects `FootnoteMarkerFlowable` references per page
|
|
28
|
+
6. Renderer draws collected footnotes at the bottom of each page
|
|
29
|
+
|
|
30
|
+
## What remains for full TODO 60 closure
|
|
31
|
+
|
|
32
|
+
TODO 60 (footnote inline-reference fidelity) is now architecturally
|
|
33
|
+
unblocked but still needs:
|
|
34
|
+
|
|
35
|
+
- Adapter inline walker to emit `FootnoteMarkerFlowable` at `<fn>`
|
|
36
|
+
references in body text (currently the flow builder would have to
|
|
37
|
+
emit them manually from `Document#footnotes`).
|
|
38
|
+
- A real fixture with `<fn>` inline references + `<fmt-footnote-container>`
|
|
39
|
+
blocks to validate the full path.
|
|
40
|
+
|
|
41
|
+
Both are focused follow-ups, not architectural work. The TODO 66
|
|
42
|
+
design is closed.
|
|
43
|
+
|
|
44
|
+
The generic adapter currently emits `Content::Paragraph[]` for every
|
|
45
|
+
flowable-bearing element type: notes, examples, terms, bibliography
|
|
46
|
+
items, figure captions. The semantic distinction is encoded only in
|
|
47
|
+
the Paragraph's `style_id:` (`:note`, `:term`, `:bibitem`, etc.).
|
|
48
|
+
|
|
49
|
+
This has three downstream costs:
|
|
50
|
+
|
|
51
|
+
1. **Lost structure**: a `<note><p>...</p><p>...</p></note>` becomes
|
|
52
|
+
two unrelated Paragraphs. The flow builder cannot tell they belong
|
|
53
|
+
to the same note (so cannot apply hanging-indent rendering via
|
|
54
|
+
`NoteFlowable`).
|
|
55
|
+
2. **No grouping**: the renderer cannot collect footnotes/notes by
|
|
56
|
+
page (TODO 60's blocker — there's no `Content::Footnote` emission
|
|
57
|
+
from the adapter).
|
|
58
|
+
3. **Fragile style dispatch**: the flow builder keys off `style_id`
|
|
59
|
+
to decide what to do. Adding a new style requires touching the
|
|
60
|
+
builder, violating OCP.
|
|
61
|
+
|
|
62
|
+
## Root cause
|
|
63
|
+
|
|
64
|
+
`Content` knows about generic inline-block containers (Paragraph,
|
|
65
|
+
Table, List) but not about the semantic groupings those containers
|
|
66
|
+
appear in (Note, Example, TermEntry, BibliographyItem, FigureGroup).
|
|
67
|
+
That semantic layer was implicit in the legacy OIML adapter and was
|
|
68
|
+
flattened into Paragraphs during the generic-adapter extraction.
|
|
69
|
+
|
|
70
|
+
## Approach
|
|
71
|
+
|
|
72
|
+
Add semantic content types that wrap their inner Paragraphs:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
Content::Note # <note> — label + body Paragraphs
|
|
76
|
+
Content::Example # <example> — label + body Paragraphs
|
|
77
|
+
Content::TermEntry # <term> — number + preferred + definition + source
|
|
78
|
+
Content::FigureGroup # <figure> — Image + caption Paragraphs
|
|
79
|
+
Content::BibliographyItem # <bibitem> — tag + formattedref Paragraphs
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Each is a frozen value object with `children` (Array of Paragraph or
|
|
83
|
+
other Content nodes) plus type-specific fields (`label`, `number`,
|
|
84
|
+
`marker`, `caption`).
|
|
85
|
+
|
|
86
|
+
### Migration plan
|
|
87
|
+
|
|
88
|
+
1. **Phase 1**: add the new types alongside `Content::Paragraph`. They
|
|
89
|
+
carry the same `style_id` mechanism. Adapter begins emitting the
|
|
90
|
+
new types when the `selectors:` block identifies them.
|
|
91
|
+
2. **Phase 2**: `GenericFlowBuilder` recognizes the new types and
|
|
92
|
+
emits `NoteFlowable`, `ImageFlowable + TextFlowable` (figures),
|
|
93
|
+
etc. with proper grouping.
|
|
94
|
+
3. **Phase 3**: footnote support. `Content::Footnote` is already
|
|
95
|
+
defined (autoloaded from `content/footnote.rb`). Add adapter
|
|
96
|
+
extraction from `<fmt-footnote-container>` / `<fmt-fn-body>`.
|
|
97
|
+
4. **Phase 4**: page-bottom footnote collection in `Engine::Paged`.
|
|
98
|
+
Add a `footnotes` Array to `FlowContext` and a `footnote_zone`
|
|
99
|
+
region in `Output::Page`.
|
|
100
|
+
|
|
101
|
+
### Selector additions (Phase 1)
|
|
102
|
+
|
|
103
|
+
```yaml
|
|
104
|
+
selectors:
|
|
105
|
+
note_container: note # already implicit in element_mapping
|
|
106
|
+
note_label: fmt-name # already present
|
|
107
|
+
example_container: example
|
|
108
|
+
term_container: term
|
|
109
|
+
figure_container: figure
|
|
110
|
+
bibitem_container: bibitem
|
|
111
|
+
footnote_container: fmt-footnote-container
|
|
112
|
+
footnote_body: fmt-fn-body
|
|
113
|
+
footnote_marker: fn
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Backward compatibility
|
|
117
|
+
|
|
118
|
+
The generic adapter continues to emit `Content::Paragraph` for any
|
|
119
|
+
flavor that doesn't opt in to the new selector keys. Old flavors'
|
|
120
|
+
behavior is unchanged.
|
|
121
|
+
|
|
122
|
+
## Done-When (Phase 1 — content types)
|
|
123
|
+
|
|
124
|
+
- [ ] `Content::Note`, `Content::Example`, `Content::TermEntry`,
|
|
125
|
+
`Content::FigureGroup`, `Content::BibliographyItem` classes exist
|
|
126
|
+
- [ ] Each is a frozen value object with `==`/`eql?`/`hash`
|
|
127
|
+
- [ ] `Content::Document#sections` accepts the new types (or wraps them
|
|
128
|
+
in Sections transparently)
|
|
129
|
+
- [ ] Adapter emits the new types when selectors identify them
|
|
130
|
+
- [ ] Specs cover each new type's equality + freezing
|
|
131
|
+
|
|
132
|
+
## Done-When (Phase 2 — flow builder recognition)
|
|
133
|
+
|
|
134
|
+
- [ ] `GenericFlowBuilder` dispatches on the new types via `is_a?`
|
|
135
|
+
- [ ] Notes emit `Flowables::NoteFlowable` (already exists) with
|
|
136
|
+
hanging indent
|
|
137
|
+
- [ ] Examples emit a styled block with `example_body` margins
|
|
138
|
+
- [ ] Figure groups emit `ImageFlowable` + caption TextFlowable as a
|
|
139
|
+
keep-together unit
|
|
140
|
+
- [ ] Bibliography items emit as a single hanging-indent flowable
|
|
141
|
+
|
|
142
|
+
## Done-When (Phase 3 — footnote extraction)
|
|
143
|
+
|
|
144
|
+
- [ ] Adapter emits `Content::Footnote` from `<fmt-footnote-container>`
|
|
145
|
+
- [ ] Selector keys `footnote_container`, `footnote_body`,
|
|
146
|
+
`footnote_marker` defined
|
|
147
|
+
- [ ] Footnotes appear in `Content::Document#footnotes`
|
|
148
|
+
|
|
149
|
+
## Done-When (Phase 4 — page-bottom rendering)
|
|
150
|
+
|
|
151
|
+
- [ ] `FlowContext` carries a per-page `footnotes` Array
|
|
152
|
+
- [ ] `Engine::Paged` collects footnotes during the layout pass
|
|
153
|
+
- [ ] `Output::Page` carries a `footnote_zone` region
|
|
154
|
+
- [ ] `Renderer::Pdf` renders footnote lines at the page bottom
|
|
155
|
+
- [ ] A real fixture exercises the full path
|
|
156
|
+
|
|
157
|
+
## Why this unblocks TODO 60
|
|
158
|
+
|
|
159
|
+
TODO 60 is currently blocked because the architecture has no
|
|
160
|
+
mechanism for grouped content types or per-page footnote collection.
|
|
161
|
+
This TODO is the architectural work that *enables* TODO 60. Once the
|
|
162
|
+
content types exist and the flow builder recognizes them, the
|
|
163
|
+
remaining TODO 60 work is a small follow-up.
|
|
164
|
+
|
|
165
|
+
## Related
|
|
166
|
+
|
|
167
|
+
- [[50-delete-oiml-ruby-code]] — established generic adapter; this
|
|
168
|
+
TODO extends it with semantic grouping
|
|
169
|
+
- [[63-purge-hardcoded-vocabulary-from-generic-core]] — selectors
|
|
170
|
+
mechanism this TODO extends
|
|
171
|
+
- [[60-adapter-fidelity-footnotes-notes-examples]] — blocked on this
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# TODO.complete — Path to 100% mn2pdf parity
|
|
2
|
+
|
|
3
|
+
This directory tracks every piece of work needed to make Arrolio's
|
|
4
|
+
OIML output byte-equivalent (visually + textually) to mn2pdf's
|
|
5
|
+
output for the same input XML.
|
|
6
|
+
|
|
7
|
+
The reference rendering is `mn2pdf v2.55` (Java), invoked via
|
|
8
|
+
Metanorma. Reference PDF for r060/1 lives at
|
|
9
|
+
`~/src/mn/mn-samples-oiml/_site/documents/r060/1/document.pdf`
|
|
10
|
+
(28 pages, 744 KB, fonts: FuturaPT-Book/Demi/Light,
|
|
11
|
+
TimesNewRomanPSMT family, Cambria Math).
|
|
12
|
+
|
|
13
|
+
The authoritative style reference is
|
|
14
|
+
`~/src/mn/metanorma-taste/data/oiml/oiml.xsl` (767 lines).
|
|
15
|
+
|
|
16
|
+
## Front matter shape (every file)
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
---
|
|
20
|
+
priority: P0 # P0 = blocks parity; P1 = important; P2 = polish; P3 = future
|
|
21
|
+
impact: high # high = >5% similarity swing; med = 1-5%; low = <1%
|
|
22
|
+
depends_on: [] # TODO numbers
|
|
23
|
+
layer: foundation | metrics | text | flowable | engine | output | render | adapter | harness
|
|
24
|
+
status: pending # pending | in_progress | done
|
|
25
|
+
est: 1d # 1d = single focused session; 5d = multi-week module
|
|
26
|
+
---
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Critical path to 90%+ similarity
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
01 → 02 → 04 → 05 → 06 → 07 → 09 → 11 → 14 → 18 → 22
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
After 22 lands, every remaining TODO is a polish item.
|
|
36
|
+
|
|
37
|
+
## Status legend
|
|
38
|
+
|
|
39
|
+
- `pending` — not started
|
|
40
|
+
- `in_progress` — actively being worked
|
|
41
|
+
- `done` — complete, specs pass, ready for review
|
|
42
|
+
- `deferred` — intentionally out of scope for this milestone
|
|
43
|
+
|
|
44
|
+
## Verification
|
|
45
|
+
|
|
46
|
+
After each TODO: `bundle exec rake` (rspec + rubocop) must be green,
|
|
47
|
+
and `bundle exec ruby exe/oiml-diff out.pdf <reference>` similarity
|
|
48
|
+
must not regress.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
phase: 1
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: foundation
|
|
6
|
+
est: 1d
|
|
7
|
+
status: in_progress
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
The Arrolio gem skeleton already exists (gemspec, Gemfile, lib/arrolio.rb,
|
|
13
|
+
error.rb, version.rb). This TODO covers the conventions check, autoload
|
|
14
|
+
tree, and project structure that everything else depends on.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
Already in place (verify, don't redo):
|
|
19
|
+
- `lib/arrolio.rb` with autoloads for every planned module.
|
|
20
|
+
- `lib/arrolio/version.rb` (`0.1.0.alpha1`).
|
|
21
|
+
- `lib/arrolio/error.rb` (Error + LayoutError + RenderError +
|
|
22
|
+
ContentError + LayoutSpecError).
|
|
23
|
+
- `spec/spec_helper.rb` + `spec/arrolio/version_spec.rb`.
|
|
24
|
+
- `Gemfile` with `gem "pdfrb", path: "/Users/mulgogi/src/claricle/pdfrb"`.
|
|
25
|
+
|
|
26
|
+
Convention decisions to lock in here:
|
|
27
|
+
- `# frozen_string_literal: true` on every file.
|
|
28
|
+
- Immutable value objects with `freeze` in constructors where data
|
|
29
|
+
crosses a layer boundary.
|
|
30
|
+
- `autoload` only — never `require_relative`, never `require` for
|
|
31
|
+
internal code. Autoloads live in the immediate parent namespace's
|
|
32
|
+
file (create the file if it doesn't exist).
|
|
33
|
+
- No `send` to private methods, no `instance_variable_set/get`, no
|
|
34
|
+
`respond_to?` for type checks (`is_a?` only).
|
|
35
|
+
- No `double()` in specs — real instances or `Struct.new` stand-ins.
|
|
36
|
+
- No hand-rolled `to_h`/`from_h` on models.
|
|
37
|
+
|
|
38
|
+
## Done-When
|
|
39
|
+
|
|
40
|
+
- [x] `bundle exec rspec` is green on the seed specs.
|
|
41
|
+
- [x] `Arrolio::VERSION` returns `"0.1.0.alpha1"`.
|
|
42
|
+
- [x] `Arrolio::Error` descends from `StandardError`.
|
|
43
|
+
- [x] `lib/arrolio.rb` lists every planned module as an autoload.
|
|
44
|
+
- [ ] `.rubocop.yml` exists and `bundle exec rubocop` is green.
|
|
45
|
+
- [ ] `bin/console` exists for interactive exploration.
|
|
46
|
+
- [ ] CLAUDE.md documents the architecture for future Claude sessions.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
phase: 1
|
|
4
|
+
depends_on: [01]
|
|
5
|
+
layer: foundation
|
|
6
|
+
est: 2d
|
|
7
|
+
status: in_progress
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Arrolio needs a typed content tree to describe documents semantically.
|
|
13
|
+
Strings and hashes are too loose — downstream layers (Engine, Renderer)
|
|
14
|
+
must dispatch on content type via `is_a?`. The tree is medium-independent:
|
|
15
|
+
it has no notion of "page" or "PDF" anywhere.
|
|
16
|
+
|
|
17
|
+
## Approach
|
|
18
|
+
|
|
19
|
+
Files to create under `lib/arrolio/content/`:
|
|
20
|
+
|
|
21
|
+
- `document.rb` — root; holds `metadata` (Hash), `sections` (Array).
|
|
22
|
+
- `section.rb` — `title`, `level`, `number`, `id`, `children`.
|
|
23
|
+
- `paragraph.rb` — `inline_runs` (Array of InlineRun).
|
|
24
|
+
- `inline_run.rb` — `text` + `style_id` (frozen value object).
|
|
25
|
+
- `table.rb` — `Row`, `Cell`, `ColumnSpec` (defer to TODO 27 if convenient).
|
|
26
|
+
- `list.rb` — `Item` with `marker` + `content` (defer to TODO 31).
|
|
27
|
+
- `image.rb` — `src`, `alt`, `natural_size` (defer to TODO 33).
|
|
28
|
+
- `cross_reference.rb` — `target_id`, `reference_kind` (defer to TODO 41).
|
|
29
|
+
|
|
30
|
+
Conventions:
|
|
31
|
+
- Every node has a `role` Symbol (e.g. `:heading`, `:paragraph`)
|
|
32
|
+
used by dispatch tables.
|
|
33
|
+
- Frozen value objects. Equality by value (`==` and `eql?` and `hash`).
|
|
34
|
+
- Children are an Array; leaf text is a String inside `InlineRun`.
|
|
35
|
+
- No mutations after construction. Builders accumulate into Arrays,
|
|
36
|
+
then construct the immutable node in one shot.
|
|
37
|
+
|
|
38
|
+
DSL: `Document.build { section("Intro") { paragraph("Hello") } }`
|
|
39
|
+
builder lives in `lib/arrolio/content/builder.rb`.
|
|
40
|
+
|
|
41
|
+
## Done-When
|
|
42
|
+
|
|
43
|
+
- [ ] Every content class is a frozen value object with `==`, `eql?`, `hash`.
|
|
44
|
+
- [ ] `Arrolio::Content::Document.build { ... }` produces a tree.
|
|
45
|
+
- [ ] Visitors (TODO 05) can walk the tree without type-checks beyond `is_a?`.
|
|
46
|
+
- [ ] No content class references pages, PDFs, frames, or renderers.
|
|
47
|
+
- [ ] Specs cover equality, immutability, child traversal.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
phase: 1
|
|
4
|
+
depends_on: [02]
|
|
5
|
+
layer: foundation
|
|
6
|
+
est: 2d
|
|
7
|
+
status: in_progress
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Engine and Renderer both need a typed layout description to drive
|
|
13
|
+
placement. This is the Ruby equivalent of XSL-FO's
|
|
14
|
+
`fo:layout-master-set` + `fo:page-sequence-master` — but as Ruby
|
|
15
|
+
objects, no XML.
|
|
16
|
+
|
|
17
|
+
## Approach
|
|
18
|
+
|
|
19
|
+
Files under `lib/arrolio/layout_spec/`:
|
|
20
|
+
|
|
21
|
+
- `layout_spec.rb` — top container; holds `page_templates`, `styles`,
|
|
22
|
+
`flows`, `page_sequences`.
|
|
23
|
+
- `page_template.rb` — name, page_size, margins, region_extents,
|
|
24
|
+
computed `regions`.
|
|
25
|
+
- `region.rb` — name (`:body`, `:before`, `:after`, `:start`, `:end`),
|
|
26
|
+
geometry, `flow_ref`.
|
|
27
|
+
- `page_sequence_master.rb` — selection rules (`:first`, `:odd`,
|
|
28
|
+
`:even`, `:blank`, `:last`).
|
|
29
|
+
- `flow.rb` — name, source (path into Content tree), default style.
|
|
30
|
+
|
|
31
|
+
DSL: `LayoutSpec.build { page_template(:body) { ... }; style(:body, ...) }`.
|
|
32
|
+
|
|
33
|
+
Validation in constructor — raise `LayoutSpecError` on:
|
|
34
|
+
- Unknown region name.
|
|
35
|
+
- Page size that isn't a 2-element Array of positives.
|
|
36
|
+
- Margin that's negative.
|
|
37
|
+
- Style reference to a name not in the registry.
|
|
38
|
+
|
|
39
|
+
## Done-When
|
|
40
|
+
|
|
41
|
+
- [ ] `LayoutSpec.build` produces an immutable spec.
|
|
42
|
+
- [ ] `spec.page_template(:body)` returns a `PageTemplate`.
|
|
43
|
+
- [ ] `spec.style(:body)` returns a resolved `Style::Definition`.
|
|
44
|
+
- [ ] Invalid specs raise `LayoutSpecError` with a useful message and
|
|
45
|
+
a `spec_field` attribute pointing at the offending field.
|
|
46
|
+
- [ ] Specs cover happy path + every validation rule.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
phase: 1
|
|
4
|
+
depends_on: [02]
|
|
5
|
+
layer: foundation
|
|
6
|
+
est: 2d
|
|
7
|
+
status: in_progress
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Style declarations need a typed representation that supports:
|
|
13
|
+
- Inheritance (parent style → child override).
|
|
14
|
+
- Resolution by name (`:body`, `:heading_1`) or selector.
|
|
15
|
+
- Medium-aware properties (font, color, alignment, spacing).
|
|
16
|
+
- Diff/merge for span-style overrides within paragraphs.
|
|
17
|
+
|
|
18
|
+
## Approach
|
|
19
|
+
|
|
20
|
+
Files under `lib/arrolio/style/`:
|
|
21
|
+
|
|
22
|
+
- `definition.rb` — immutable struct of resolved properties
|
|
23
|
+
(font_name, font_size, fill_color, stroke_color, line_spacing,
|
|
24
|
+
align, valign, margin, padding, character_spacing, word_spacing,
|
|
25
|
+
keep_together, page_break_before, page_break_after, line_break).
|
|
26
|
+
- `registry.rb` — name → Definition map; resolves lookups;
|
|
27
|
+
supports `register(name, **props)` and `resolve(name)`.
|
|
28
|
+
- `diff.rb` — given a base Definition + a hash of overrides, produce
|
|
29
|
+
a new Definition (used for inline span styling).
|
|
30
|
+
- `table_style.rb`, `row_style.rb`, `cell_style.rb` — table-specific
|
|
31
|
+
subclasses.
|
|
32
|
+
|
|
33
|
+
Defaults:
|
|
34
|
+
- `font_name: "Helvetica"`, `font_size: 12`, `align: :left`,
|
|
35
|
+
`line_spacing: 1.2`, `line_break: :greedy`.
|
|
36
|
+
|
|
37
|
+
## Done-When
|
|
38
|
+
|
|
39
|
+
- [ ] `Style::Registry.new` accepts a hash of definitions.
|
|
40
|
+
- [ ] `registry.resolve(:body)` returns a `Definition`.
|
|
41
|
+
- [ ] `definition.with(font_size: 14)` returns a new merged Definition.
|
|
42
|
+
- [ ] Inheritance: child without `font_size` inherits parent's.
|
|
43
|
+
- [ ] Specs cover registration, resolution, merge, inheritance.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P1
|
|
3
|
+
phase: 1
|
|
4
|
+
depends_on: [02, 03]
|
|
5
|
+
layer: foundation
|
|
6
|
+
est: 1d
|
|
7
|
+
status: pending
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Engine and Renderer both walk trees of typed nodes. Without a
|
|
13
|
+
visitor pattern, every walker re-implements dispatch. A canonical
|
|
14
|
+
visitor keeps the dispatch logic in one place and lets new walkers
|
|
15
|
+
(validator, optimizer, debug-dumper) be added without touching node
|
|
16
|
+
classes (OCP).
|
|
17
|
+
|
|
18
|
+
## Approach
|
|
19
|
+
|
|
20
|
+
Files under `lib/arrolio/visitor/`:
|
|
21
|
+
|
|
22
|
+
- `base.rb` — `Visitor::Base` with `visit(node)` that dispatches on
|
|
23
|
+
`node.class` to a method named `visit_<class_name>`. Falls back to
|
|
24
|
+
`visit_unknown(node)` if no specific method.
|
|
25
|
+
- `content_visitor.rb` — mixin giving `visit_section`, `visit_paragraph`,
|
|
26
|
+
etc. defaults that recurse into children.
|
|
27
|
+
- `layout_spec_visitor.rb` — same idea for LayoutSpec nodes.
|
|
28
|
+
|
|
29
|
+
Built-in visitors:
|
|
30
|
+
- `Validator` — raises `ContentError` / `LayoutSpecError` on bad trees.
|
|
31
|
+
- `Dumper` — prints tree as YAML for debugging.
|
|
32
|
+
- `Counter` — counts nodes per role (used by harness TODO 53).
|
|
33
|
+
|
|
34
|
+
## Done-When
|
|
35
|
+
|
|
36
|
+
- [ ] `Visitor::Base.dispatch(node)` routes to `visit_<class>`.
|
|
37
|
+
- [ ] Unknown classes hit `visit_unknown` (default: no-op).
|
|
38
|
+
- [ ] `ContentVisitor` walks a `Content::Document` end-to-end.
|
|
39
|
+
- [ ] `Validator` flags a Section with non-String title.
|
|
40
|
+
- [ ] Specs cover dispatch, recursion, unknown class.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
phase: 2
|
|
4
|
+
depends_on: [01]
|
|
5
|
+
layer: metrics
|
|
6
|
+
est: 1d
|
|
7
|
+
status: in_progress
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
The 14 standard Type1 fonts (Helvetica, Times, Courier + variants,
|
|
13
|
+
Symbol, ZapfDingbats) need real glyph widths for line breaking. The
|
|
14
|
+
canonical source is Adobe Font Metrics files (AFM). Arrolio bundles
|
|
15
|
+
its own copy so it doesn't depend on Pdfrb's data dir.
|
|
16
|
+
|
|
17
|
+
## Approach
|
|
18
|
+
|
|
19
|
+
Files:
|
|
20
|
+
- `data/arrolio/afm/*.afm` — 14 AFM files (port from
|
|
21
|
+
`pdfrb/data/pdfrb/afm/`).
|
|
22
|
+
- `lib/arrolio/font/afm_parser.rb` — parses `StartFontMetrics` block,
|
|
23
|
+
`FontName`/`FullName`/`FamilyName`, `FontBBox`, `CapHeight`,
|
|
24
|
+
`XHeight`, `Ascender`, `Descender`, `StartCharMetrics`/`EndCharMetrics`,
|
|
25
|
+
and each `C N ; WX W ; N name ; B bbox ;` row.
|
|
26
|
+
|
|
27
|
+
Parser produces a frozen `AFM::Font` value object with `char_metrics`
|
|
28
|
+
hash keyed by both code and glyph name.
|
|
29
|
+
|
|
30
|
+
## Done-When
|
|
31
|
+
|
|
32
|
+
- [ ] All 14 AFM files load without error.
|
|
33
|
+
- [ ] `AFMParser.from_file("data/arrolio/afm/Helvetica.afm").font_name`
|
|
34
|
+
== `"Helvetica"`.
|
|
35
|
+
- [ ] Helvetica's `A` width is 668 (per AFM); `i` is 222; `m` is 833.
|
|
36
|
+
- [ ] Ascender/descender/cap_height/x_height parsed for all 14.
|
|
37
|
+
- [ ] Specs cover the parser on at least 3 of the 14.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
phase: 2
|
|
4
|
+
depends_on: [06]
|
|
5
|
+
layer: metrics
|
|
6
|
+
est: 1d
|
|
7
|
+
status: in_progress
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Layout callers ask "how wide is this Unicode string at this size?"
|
|
13
|
+
The AFM gives glyph widths by AdobeStandardEncoding code or glyph
|
|
14
|
+
name, not Unicode codepoint. We need Unicode → AFM resolution via
|
|
15
|
+
the Adobe Glyph List.
|
|
16
|
+
|
|
17
|
+
## Approach
|
|
18
|
+
|
|
19
|
+
Files:
|
|
20
|
+
- `data/arrolio/glyphlist.txt` — Adobe Glyph List (port from Pdfrb).
|
|
21
|
+
- `lib/arrolio/font/glyph_list.rb` — parses glyphlist.txt into a
|
|
22
|
+
glyph_name → Unicode String map; exposes a reverse map
|
|
23
|
+
(Unicode codepoint → glyph_name).
|
|
24
|
+
- `lib/arrolio/font_metrics/afm_metrics.rb` — wraps an `AFM::Font`
|
|
25
|
+
and exposes the universal metrics interface (see TODO 09):
|
|
26
|
+
- `advance_width(char)` — Unicode char → Float (or 0 if unknown).
|
|
27
|
+
- `width_of_string(str, font_size:)` — sum, scaled.
|
|
28
|
+
- `ascender(font_size:)`, `descender(font_size:)`, `cap_height(font_size:)`,
|
|
29
|
+
`x_height(font_size:)`, `line_height(font_size:, line_spacing:)`.
|
|
30
|
+
|
|
31
|
+
Lookup path: Unicode codepoint → AGL glyph name → AFM char_metrics[name].
|
|
32
|
+
|
|
33
|
+
## Done-When
|
|
34
|
+
|
|
35
|
+
- [ ] `AfmMetrics.for_name("Helvetica").width_of_string("Hello", font_size: 12)`
|
|
36
|
+
is `27.336` (matches Adobe AFM to 0.001pt).
|
|
37
|
+
- [ ] `width("i") == 2.664` and `width("m") == 9.996` at 12pt.
|
|
38
|
+
- [ ] Non-ASCII WinAnsi chars resolve: en-dash → 556, bullet → 350.
|
|
39
|
+
- [ ] Ascender / descender / cap_height / x_height match AFM.
|
|
40
|
+
- [ ] Specs cover at least 3 of the 14 fonts (Helvetica, Times-Bold,
|
|
41
|
+
Courier-Oblique).
|