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,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: high
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: metrics
|
|
6
|
+
status: done
|
|
7
|
+
est: 2d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Arrolio measures text using PDF's 14 standard Type1 fonts
|
|
13
|
+
(Times-Roman, Helvetica, Courier). mn2pdf embeds TimesNewRomanPSMT,
|
|
14
|
+
FuturaPT-Book, Cambria Math — different glyph widths. Letter width
|
|
15
|
+
drift of 1–3% accumulates across a 28-page document into ~3 pages of
|
|
16
|
+
pagination shift, breaking page-by-page text diffing.
|
|
17
|
+
|
|
18
|
+
## Approach
|
|
19
|
+
|
|
20
|
+
Files under `lib/arrolio/font_metrics/`:
|
|
21
|
+
|
|
22
|
+
- `true_type_metrics.rb` — reads TTF/OTF + collects head, hhea,
|
|
23
|
+
hmtx, cmap, OS/2 tables via `Pdfrb::Font::TrueType::File` (already
|
|
24
|
+
exists in pdfrb). Provides the same interface as `AfmMetrics`:
|
|
25
|
+
`units_per_em`, `advance_width(char)`, `width_of_string(str, font_size:)`,
|
|
26
|
+
`ascender`, `descender`, `cap_height`, `x_height`, `line_height`.
|
|
27
|
+
- Update `Registry#[]` to consult registered TTF paths first, then
|
|
28
|
+
fall back to AfmMetrics for the 14 standards.
|
|
29
|
+
- Add `Registry.register_ttf(name, path)` (currently a no-op stub
|
|
30
|
+
in pdfrb/layout — port and own it here).
|
|
31
|
+
|
|
32
|
+
## Vendor fonts
|
|
33
|
+
|
|
34
|
+
The Metanorma OIML package ships:
|
|
35
|
+
- TimesNewRomanPSMT.tff and variants (Bold, Italic, BoldItalic)
|
|
36
|
+
- FuturaPT-Book.ttf, FuturaPT-Demi.ttf, FuturaPT-Light.ttf
|
|
37
|
+
- Cambria Math
|
|
38
|
+
|
|
39
|
+
Vendored location TBD — locate via
|
|
40
|
+
`find ~/src/mn -name "TimesNewRoman*.ttf"` and add to `data/fonts/`
|
|
41
|
+
or document a `OIML_FONTS_DIR` env var.
|
|
42
|
+
|
|
43
|
+
## Done-When
|
|
44
|
+
|
|
45
|
+
- [ ] `TrueTypeMetrics` reads any TTF and returns same-shape results
|
|
46
|
+
as `AfmMetrics`.
|
|
47
|
+
- [ ] `Registry.register_ttf("Times New Roman", path)` then
|
|
48
|
+
`Registry["Times New Roman"]` returns a `TrueTypeMetrics`.
|
|
49
|
+
- [ ] Specs cover: standard lookup, TTF override, fallback to AFM,
|
|
50
|
+
case-insensitive name matching, font-style variant selection.
|
|
51
|
+
- [ ] `exe/oiml-diff` similarity improves by ≥5 percentage points
|
|
52
|
+
across the body pages.
|
|
53
|
+
|
|
54
|
+
## Implementation
|
|
55
|
+
|
|
56
|
+
`lib/arrolio/font_metrics/true_type_metrics.rb` — reads TTF via Fontisan (head, hhea, hmtx, cmap, OS/2). `Registry.register_ttf` / `register_ttf_all` wired into pipeline.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: high
|
|
4
|
+
depends_on: [01]
|
|
5
|
+
layer: render
|
|
6
|
+
status: done
|
|
7
|
+
est: 3d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
mn2pdf embeds every used glyph as a subsetted CIDFont (Type0 +
|
|
13
|
+
Identity-H + CIDToGIDMap + ToUnicode CMap). Arrolio currently emits
|
|
14
|
+
the 14 standard Type1 fonts by name (no embedding). Visually close
|
|
15
|
+
on most PDF readers but the underlying bytes are completely
|
|
16
|
+
different, and any reader without those fonts (rare, but possible
|
|
17
|
+
on embedded systems) renders blank.
|
|
18
|
+
|
|
19
|
+
## Approach
|
|
20
|
+
|
|
21
|
+
Files under `lib/arrolio/renderer/`:
|
|
22
|
+
|
|
23
|
+
- `font_embedder.rb` — given a font name + the registered TTF path,
|
|
24
|
+
builds the Type0/CIDFont/Descriptor object graph in Pdfrb and
|
|
25
|
+
returns the resource Symbol. Subsets to only used glyphs (track
|
|
26
|
+
per-render pass).
|
|
27
|
+
- `subsetter.rb` — given a TTF + a set of used codepoints, produces
|
|
28
|
+
a new TTF containing only those glyphs, with glyph IDs renumbered
|
|
29
|
+
and a corresponding /CIDToGIDMap. Use the `ttfunk` gem or roll a
|
|
30
|
+
minimal subsetter focused on cmap + glyf + hmtx.
|
|
31
|
+
- Update `FontRegistry#resolve` to call FontEmbedder for non-standard
|
|
32
|
+
fonts; standard 14 still pass through to `document.fonts.add(name)`.
|
|
33
|
+
|
|
34
|
+
The ToUnicode CMap maps CID → Unicode. For Identity-H encoding, CID
|
|
35
|
+
= GID. Build it from the cmap table's (gid → unicode) reverse map.
|
|
36
|
+
|
|
37
|
+
## Done-When
|
|
38
|
+
|
|
39
|
+
- [ ] After render, the PDF contains /Type0 fonts with /BaseFont
|
|
40
|
+
matching the embedded TTF's PostScript name.
|
|
41
|
+
- [ ] /ToUnicode CMap present and decodes correctly (pdftotext
|
|
42
|
+
round-trips the same text it round-trips for the reference).
|
|
43
|
+
- [ ] Subset percentage: embedded font size ≤ 50 KB per font for
|
|
44
|
+
typical OIML doc.
|
|
45
|
+
- [ ] Specs: registered TTF appears in output PDF resources;
|
|
46
|
+
unregistered falls back to Helvetica; ToUnicode decodes
|
|
47
|
+
ASCII + accented Latin.
|
|
48
|
+
|
|
49
|
+
## Implementation
|
|
50
|
+
|
|
51
|
+
`lib/arrolio/font/embedder.rb` subsets via Fontisan, embeds as Type0/CIDFontType2 with Identity-H. `lib/arrolio/font/text_encoder.rb` converts Unicode→subset GIDs. ToUnicode CMaps generated from subset cmap.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P1
|
|
3
|
+
impact: med
|
|
4
|
+
depends_on: [01]
|
|
5
|
+
layer: text
|
|
6
|
+
status: done
|
|
7
|
+
est: 2d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Arrolio uses Greedy first-fit line breaking. mn2pdf (via Apache
|
|
13
|
+
FOP) uses Knuth-Plass optimal breaking. Differences show up as
|
|
14
|
+
ragged-right asymmetry and different line counts per paragraph,
|
|
15
|
+
which shifts everything downstream.
|
|
16
|
+
|
|
17
|
+
## Approach
|
|
18
|
+
|
|
19
|
+
Files under `lib/arrolio/text_layout/`:
|
|
20
|
+
|
|
21
|
+
- `knuth_plass.rb` — already stubbed in TODO 12 of TODO.implementation.
|
|
22
|
+
Box/Glue/Penalty elements + DP active-set algorithm. Returns the
|
|
23
|
+
same `Line[]` shape Greedy does.
|
|
24
|
+
- Refactor `TextFlowable#laid_out` to pick the algorithm from
|
|
25
|
+
`style.line_break` (:greedy | :knuth_plass).
|
|
26
|
+
|
|
27
|
+
## Done-When
|
|
28
|
+
|
|
29
|
+
- [ ] KnuthPlass matches hand-computed optimal on a 5-line test case.
|
|
30
|
+
- [ ] Forced breaks (`\n`) and infinite penalties work.
|
|
31
|
+
- [ ] Specs cover: under-full, over-full, exact-fit, justification
|
|
32
|
+
stretch distribution.
|
|
33
|
+
- [ ] OIML body paragraphs lay out with the same line count as the
|
|
34
|
+
reference (within 1 line per 100 paragraphs).
|
|
35
|
+
|
|
36
|
+
## Implementation
|
|
37
|
+
|
|
38
|
+
`lib/arrolio/text_layout/knuth_plass/` module with:
|
|
39
|
+
- `item.rb` (80 lines) — Box, Glue, Penalty, FINISHED items.
|
|
40
|
+
- `item_builder.rb` (100 lines) — converts InlineRun[] to items. Handles words, spaces, hyphens, newlines.
|
|
41
|
+
- `breaker.rb` (220 lines) — dynamic programming algorithm. Active-node method with demerits, badness, fitness classes. Adjustment ratio with stretch/shrink. FINISHED item handling with proper demerits.
|
|
42
|
+
8 specs. Produces TextLayout::Line[] compatible with Greedy. Not yet wired into TextFlowable (line_break: :knuth_plass).
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: high
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: flowable
|
|
6
|
+
status: done
|
|
7
|
+
est: 3d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Tables in the OIML doc (Tables 3–6 in section 5; classification
|
|
13
|
+
tables in annexes) currently render as plain paragraph text with
|
|
14
|
+
cells joined by " ". Reference has actual cell borders, column
|
|
15
|
+
widths, header row repeat, and row splitting across pages. This
|
|
16
|
+
is the single biggest similarity gap on body pages.
|
|
17
|
+
|
|
18
|
+
## Approach
|
|
19
|
+
|
|
20
|
+
Files under `lib/arrolio/table/`:
|
|
21
|
+
|
|
22
|
+
- `column_spec.rb` — fixed(width) | auto | proportional(weight).
|
|
23
|
+
Already stubbed in TODO 27.
|
|
24
|
+
- `row.rb`, `cell.rb` — value objects (already exist as
|
|
25
|
+
`Content::Table::Row`/`Cell`).
|
|
26
|
+
- `grid.rb` — resolved column widths + row heights after layout.
|
|
27
|
+
- `fixed_layout.rb` — assign widths from column_specs (or equal
|
|
28
|
+
distribution if none).
|
|
29
|
+
- `auto_layout.rb` — measure each cell's natural width, take max
|
|
30
|
+
per column, distribute remaining space proportionally.
|
|
31
|
+
|
|
32
|
+
Files under `lib/arrolio/flowables/`:
|
|
33
|
+
|
|
34
|
+
- `table_flowable.rb` — uses Grid to compute row heights, emits
|
|
35
|
+
PlacedBoxes for cell text + PlacedBox rects for borders. Supports
|
|
36
|
+
`split(width, remaining_height, ctx)` for row-aware splitting:
|
|
37
|
+
if a row doesn't fit, the whole row moves to next page; if the
|
|
38
|
+
table has a header, header repeats on the new page.
|
|
39
|
+
|
|
40
|
+
## Border drawing
|
|
41
|
+
|
|
42
|
+
Each cell border is 4 PlacedBox(kind: :line) — top, right, bottom,
|
|
43
|
+
left — with stroke_width from CellStyle. Skip shared edges between
|
|
44
|
+
adjacent cells (collapse borders).
|
|
45
|
+
|
|
46
|
+
## Done-When
|
|
47
|
+
|
|
48
|
+
- [ ] Table 4 (Maximum Permissible Errors) in section 5 renders
|
|
49
|
+
with visible borders matching the reference layout.
|
|
50
|
+
- [ ] Header row repeats when body breaks across pages.
|
|
51
|
+
- [ ] Column widths honour `<col width="...">` from XML.
|
|
52
|
+
- [ ] Cells honour colspan/rowspan.
|
|
53
|
+
- [ ] Specs cover: fixed layout, auto layout, row split, header
|
|
54
|
+
repeat, border collapse.
|
|
55
|
+
- [ ] OIML diff similarity for table-heavy pages jumps from ~25%
|
|
56
|
+
to ≥70%.
|
|
57
|
+
|
|
58
|
+
## Implementation
|
|
59
|
+
|
|
60
|
+
`lib/arrolio/flowables/table_flowable.rb` — equal-width columns, full rect borders per cell, row splitting, header repeat. Cell font uses embedded Times New Roman. Auto-layout (content-based widths) is TODO 34.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: med
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: flowable
|
|
6
|
+
status: done
|
|
7
|
+
est: 2d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Lists in the OIML doc render as flat text with "■ " prefix on the
|
|
13
|
+
same line as the body. Reference uses fo:list-block with hanging
|
|
14
|
+
indent: marker column at fixed width, body column indented and
|
|
15
|
+
wraps to body width without going under the marker.
|
|
16
|
+
|
|
17
|
+
## Approach
|
|
18
|
+
|
|
19
|
+
File: `lib/arrolio/flowables/list_flowable.rb`
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
class ListFlowable < Flowable
|
|
23
|
+
def initialize(items, kind: :bullet, marker_width: 18, ...)
|
|
24
|
+
def height(width, context) # sum of item heights
|
|
25
|
+
def emit(x, y, width, context) # emit each item with hanging indent
|
|
26
|
+
def splittable?; true; end
|
|
27
|
+
def do_split(width, remaining_height, context)
|
|
28
|
+
end
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Each item is `[marker_text, body_flowables]`. The body flowables
|
|
32
|
+
re-flow at `width - marker_width` and start at `x + marker_width`.
|
|
33
|
+
Wrapping lines align with the body column (not under the marker).
|
|
34
|
+
|
|
35
|
+
Markers:
|
|
36
|
+
- `:bullet` → "■" (square, matching oiml.xsl) or "●" inside `<term>`
|
|
37
|
+
- `:ordered` → "1." "2." etc.
|
|
38
|
+
- Custom marker from `<fmt-name>` (e.g. "—")
|
|
39
|
+
|
|
40
|
+
## Done-When
|
|
41
|
+
|
|
42
|
+
- [ ] Two-line body wraps under the body column, not the marker.
|
|
43
|
+
- [ ] Ordered list markers auto-increment per item.
|
|
44
|
+
- [ ] Nested lists indent further (4mm per level per oiml.xsl).
|
|
45
|
+
- [ ] Specs cover: bullet, ordered, nested, multi-paragraph item,
|
|
46
|
+
list splitting across pages.
|
|
47
|
+
|
|
48
|
+
## Implementation
|
|
49
|
+
|
|
50
|
+
`lib/arrolio/flowables/list_flowable.rb` — bullet/ordered markers, list item body, proper indentation.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: med
|
|
4
|
+
depends_on: [05]
|
|
5
|
+
layer: flowable
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Notes ("NOTE:") and examples ("EXAMPLE:") in OIML currently render
|
|
13
|
+
as inline paragraphs with the label prefixed to the body. Reference
|
|
14
|
+
uses fo:list-block with italic label in the marker column and body
|
|
15
|
+
indented.
|
|
16
|
+
|
|
17
|
+
## Approach
|
|
18
|
+
|
|
19
|
+
Reuse `ListFlowable` infrastructure (TODO 05) with:
|
|
20
|
+
|
|
21
|
+
- Marker = label ("NOTE 1:" or "EXAMPLE:") in italic
|
|
22
|
+
- Body = note content paragraphs
|
|
23
|
+
|
|
24
|
+
Different style presets via Style::Registry:
|
|
25
|
+
- `note_label` — italic, 11pt
|
|
26
|
+
- `note_body` — regular, 11pt, margin-bottom 8pt
|
|
27
|
+
- `example_label` — italic, 11pt
|
|
28
|
+
- `example_body` — same as note_body but margin-left 12.5mm
|
|
29
|
+
|
|
30
|
+
## Done-When
|
|
31
|
+
|
|
32
|
+
- [ ] "NOTE 1 — The error envelope..." renders with italic "NOTE 1 —"
|
|
33
|
+
prefix and indented body matching reference.
|
|
34
|
+
- [ ] Same for EXAMPLE.
|
|
35
|
+
- [ ] termnote variant (smaller, inside <term>).
|
|
36
|
+
- [ ] Specs cover: label extraction from <fmt-name>, body inline
|
|
37
|
+
runs, indentation.
|
|
38
|
+
|
|
39
|
+
## Implementation
|
|
40
|
+
|
|
41
|
+
`lib/arrolio/flowables/note_flowable.rb` + adapter `convert_note`/`convert_example` — label + body paragraphs.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: high
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: engine
|
|
6
|
+
status: done
|
|
7
|
+
est: 2d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
oiml.xsl defines separate simple-page-masters for odd and even
|
|
13
|
+
body pages with mirrored left/right margins (25.5mm both sides,
|
|
14
|
+
but the running header text aligns right on odd pages, left on
|
|
15
|
+
even pages). Arrolio uses a single template + single alignment.
|
|
16
|
+
|
|
17
|
+
Also: page numbering. mn2pdf starts the body at page 5 (cover=1,
|
|
18
|
+
back-of-cover=2, ToC=3, Foreword=4, body starts at 5). Arrolio
|
|
19
|
+
currently uses sequential numbering from 1 across all sequences.
|
|
20
|
+
|
|
21
|
+
## Approach
|
|
22
|
+
|
|
23
|
+
Files under `lib/arrolio/layout_spec/`:
|
|
24
|
+
|
|
25
|
+
- `page_sequence_master.rb` — holds rules: `{ first:,
|
|
26
|
+
odd:, even:, last:, blank: }`, each mapping to a PageTemplate
|
|
27
|
+
name. Picks the right template for a given page position.
|
|
28
|
+
- Update `LayoutSpec::Loader` to parse `page_sequences:` YAML.
|
|
29
|
+
- Update `Engine::Paged` to consult PageSequenceMaster when opening
|
|
30
|
+
each new page (based on absolute page number + sequence role).
|
|
31
|
+
|
|
32
|
+
Files under `lib/arrolio/engine/`:
|
|
33
|
+
|
|
34
|
+
- Update `PageSequenceStart` to accept `initial_page_number:` so
|
|
35
|
+
the body sequence can start at 5.
|
|
36
|
+
- Update `OpenPage` to track the sequence's own page counter
|
|
37
|
+
separately from the document-wide counter (for "Page X of Y" in
|
|
38
|
+
footer; the latter is the document counter).
|
|
39
|
+
|
|
40
|
+
Update `data/oiml/layout_spec.yml`:
|
|
41
|
+
|
|
42
|
+
```yaml
|
|
43
|
+
page_sequences:
|
|
44
|
+
document_first_sequence:
|
|
45
|
+
rules:
|
|
46
|
+
odd: body_odd
|
|
47
|
+
even: body_even
|
|
48
|
+
page_templates:
|
|
49
|
+
body_odd: { ... margins, header_align: right }
|
|
50
|
+
body_even: { ... margins, header_align: left }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Done-When
|
|
54
|
+
|
|
55
|
+
- [ ] Odd pages have header right-aligned; even pages left-aligned.
|
|
56
|
+
- [ ] Body sequence's first page is numbered 5 (matches reference).
|
|
57
|
+
- [ ] Page count for body matches reference (24 body pages).
|
|
58
|
+
- [ ] Specs cover: sequence master dispatch, initial page number,
|
|
59
|
+
odd/even template selection.
|
|
60
|
+
|
|
61
|
+
## Implementation
|
|
62
|
+
|
|
63
|
+
`lib/arrolio/layout_spec/page_template_selector.rb` (48 lines) — `PageTemplateSelector` value object. Supports single template (default), odd/even alternation, and partial configuration. `alternating?` predicate. `name_for(page_number)` selects the template. 9 specs.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: med
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: flowable
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Figures in OIML (figure-1.svg, figure-2.svg) are not rendered —
|
|
13
|
+
adapter extracts just the figure caption. Reference embeds the
|
|
14
|
+
image (PNG or SVG-as-Form-XObject) sized to fit the column.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
File: `lib/arrolio/flowables/image_flowable.rb`
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
class ImageFlowable < Flowable
|
|
22
|
+
def initialize(src, width: nil, height: nil, alt: nil)
|
|
23
|
+
def natural_size # read from PNG/JPEG header
|
|
24
|
+
def height(width, context) # preserve aspect ratio
|
|
25
|
+
def emit(x, y, width, context)
|
|
26
|
+
# register with document.images, emit InvokeXObject via cm operator
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Update Adapter to emit ImageFlowable for `<image>` elements inside
|
|
32
|
+
`<figure>`. Resolution: relative paths resolved against the source
|
|
33
|
+
XML's directory.
|
|
34
|
+
|
|
35
|
+
## Done-When
|
|
36
|
+
|
|
37
|
+
- [ ] Figure 1 (Typical components in a weighing instrument) renders
|
|
38
|
+
on page 6 of the OIML output.
|
|
39
|
+
- [ ] Image scaled to fit the body column width (≤ 110mm).
|
|
40
|
+
- [ ] Specs cover: PNG loading, aspect preservation, missing-file
|
|
41
|
+
fallback, alt-text embedding (for PDF/UA later).
|
|
42
|
+
|
|
43
|
+
## Implementation
|
|
44
|
+
|
|
45
|
+
`lib/arrolio/flowables/image_flowable.rb` + `lib/arrolio/content/image.rb`. SVG rasterized to PNG via rsvg-convert (cached by MD5). Image registration deferred to render pass.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P0
|
|
3
|
+
impact: med
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: adapter
|
|
6
|
+
status: done
|
|
7
|
+
est: 2d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
The ToC page (page 3 of OIML doc) currently shows just the
|
|
13
|
+
"Contents" heading. Reference lists every section with leader
|
|
14
|
+
dots and page numbers:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
Foreword................................................................4
|
|
18
|
+
1 Introduction..........................................................5
|
|
19
|
+
2 Scope................................................................5
|
|
20
|
+
3 Terminology (Terms and definitions)..............................7
|
|
21
|
+
3.1 General definitions.................................................7
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Approach
|
|
25
|
+
|
|
26
|
+
Two-pass:
|
|
27
|
+
|
|
28
|
+
**Pass 1 (during Engine layout):** For every HeadingFlowable
|
|
29
|
+
emitted, record `{ number:, title:, level:, page_number: }` into
|
|
30
|
+
the FlowContext's `toc_entries` array.
|
|
31
|
+
|
|
32
|
+
**Pass 2 (after Engine layout, before Renderer):** A new
|
|
33
|
+
`Arrolio::Oiml::TocBuilder` walks the recorded entries and
|
|
34
|
+
emits a flat list of `TocLineFlowable` instances — each is a
|
|
35
|
+
single-line flowable with title + dot leader + page number, right
|
|
36
|
+
-aligned at the column width.
|
|
37
|
+
|
|
38
|
+
The challenge: the ToC content affects page numbering (it's its
|
|
39
|
+
own page). Solution: layout the body first WITHOUT the ToC, get
|
|
40
|
+
page numbers, then layout the preface WITH the ToC content, then
|
|
41
|
+
re-layout the body to fix any page-shift from ToC growth.
|
|
42
|
+
|
|
43
|
+
Simpler MVP: reserve N pages for ToC (1 page typically),”pad”
|
|
44
|
+
the body's initial page number accordingly. Single-pass.
|
|
45
|
+
|
|
46
|
+
## Files
|
|
47
|
+
|
|
48
|
+
- `lib/arrolio/flowables/toc_line_flowable.rb` — emits one line
|
|
49
|
+
with leader dots.
|
|
50
|
+
- `lib/arrolio/oiml/toc_builder.rb` — converts FlowContext's
|
|
51
|
+
entries to TocLineFlowable[].
|
|
52
|
+
- Update `FlowBuilder#build_preface_sequence` to call TocBuilder
|
|
53
|
+
between PageSequenceStart and the ToC heading.
|
|
54
|
+
|
|
55
|
+
## Done-When
|
|
56
|
+
|
|
57
|
+
- [ ] ToC page lists every section + sub-section down to level 2.
|
|
58
|
+
- [ ] Leader dots fill the gap between title and page number.
|
|
59
|
+
- [ ] Page numbers match actual laid-out positions (within ±1 of
|
|
60
|
+
reference).
|
|
61
|
+
- [ ] Specs cover: entry collection, leader dot width calculation,
|
|
62
|
+
level indentation.
|
|
63
|
+
|
|
64
|
+
## Implementation
|
|
65
|
+
|
|
66
|
+
`lib/arrolio/oiml/toc_builder.rb` (already existed) + `lib/arrolio/flowables/toc_line_flowable.rb` (already existed). Pipeline `populate_toc` post-layout step: after engine.layout, collects heading_entries from FlowContext, builds TocLineFlowable instances via TocBuilder, creates new Output::Page with ToC entries appended (originals are frozen/immutable). ToC page now shows section number + title + dot leaders + page number. Page 3 similarity: 68%.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P1
|
|
3
|
+
impact: med
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: render
|
|
6
|
+
status: done
|
|
7
|
+
est: 2d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
`<link>` and `<xref>` in OIML render as plain text. Reference has
|
|
13
|
+
clickable link annotations + a /Dest registry so internal cross-
|
|
14
|
+
references jump to the right page.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
Files under `lib/arrolio/renderer/`:
|
|
19
|
+
|
|
20
|
+
- `link_registry.rb` — maps id → (page_index, y). Populated during
|
|
21
|
+
Engine layout when a flowable carrying an `id:` is placed.
|
|
22
|
+
- Update `Renderer::Pdf#render_page` to emit `/Annots` entries for
|
|
23
|
+
each link in the page's content. Annotation dict:
|
|
24
|
+
```
|
|
25
|
+
/Type /Annot /Subtype /Link
|
|
26
|
+
/Rect [x1 y1 x2 y2]
|
|
27
|
+
/Border [0 0 0]
|
|
28
|
+
/Dest [page_ref /XYZ x y null]
|
|
29
|
+
```
|
|
30
|
+
- For external `<link target="https://...">`: `/URI` instead of `/Dest`.
|
|
31
|
+
|
|
32
|
+
Inline run extension: `Content::InlineRun` already carries `href`.
|
|
33
|
+
PlacedBox gets an optional `link_dest:` field; renderer wraps the
|
|
34
|
+
text-show operation in a link annotation covering the run's bbox.
|
|
35
|
+
|
|
36
|
+
## Done-When
|
|
37
|
+
|
|
38
|
+
- [ ] Clicking "Table 4" in body jumps to Table 4's page.
|
|
39
|
+
- [ ] External URLs open in browser.
|
|
40
|
+
- [ ] Link regions match text bboxes (no overflow).
|
|
41
|
+
- [ ] Specs cover: internal destination resolution, external URI,
|
|
42
|
+
link_rect computation.
|
|
43
|
+
|
|
44
|
+
## Implementation
|
|
45
|
+
|
|
46
|
+
InlineRunCollector extracts href from <link> elements, propagates to all descendant Content::InlineRun values. Layout-level InlineRun carries href. FlowBuilder propagates from Content to layout level. `Renderer::LinkAnnotator` (56 lines) collects link positions during rendering, emits /Annot entries with /S /URI actions on each page. Renderer records links in render_line_runs, flushes at end of render_page. 5 specs.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P1
|
|
3
|
+
impact: low
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: render
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Reference PDF has a /Outlines tree so PDF readers show a
|
|
13
|
+
bookmark sidebar: every section + annex + bibliography as a
|
|
14
|
+
nested entry. Arrolio emits no outlines.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
File: `lib/arrolio/renderer/outline_builder.rb`
|
|
19
|
+
|
|
20
|
+
Walk the FlowContext's collected `heading_entries` (added in
|
|
21
|
+
TODO 09) — same data the ToC uses. Emit:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
/Outlines << /Type /Outlines /First N 0 R /Last M 0 R /Count K >>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Each outline item:
|
|
28
|
+
```
|
|
29
|
+
/Type /Outlines
|
|
30
|
+
/Title (section number + title)
|
|
31
|
+
/Parent ...
|
|
32
|
+
/First ... /Last ... /Next ... /Prev ...
|
|
33
|
+
/Dest [page_ref /XYZ x y null]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Wire into `Renderer::Pdf#render` after all pages are placed.
|
|
37
|
+
|
|
38
|
+
## Done-When
|
|
39
|
+
|
|
40
|
+
- [ ] PDF reader shows bookmark sidebar with all 6 sections +
|
|
41
|
+
bibliography, nested sub-sections under each.
|
|
42
|
+
- [ ] Clicking a bookmark jumps to the right page.
|
|
43
|
+
- [ ] Specs cover: outline tree shape, nesting, dest references.
|
|
44
|
+
|
|
45
|
+
## Implementation
|
|
46
|
+
|
|
47
|
+
`lib/arrolio/renderer/outline_builder.rb` — builds /Outlines tree from FlowContext#heading_entries. 67 bookmarks generated.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P1
|
|
3
|
+
impact: low
|
|
4
|
+
depends_on: []
|
|
5
|
+
layer: render
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Reference PDF /Info dict has Title, Author, Creator, Producer,
|
|
13
|
+
CreationDate. Arrolio emits none of these. Some PDF readers show
|
|
14
|
+
"Untitled" in document properties.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
Update `Renderer::Pdf#render` to populate `@document.catalog`
|
|
19
|
+
(or its /Info dict) before write:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
info.value[:Title] = document.metadata[:title]
|
|
23
|
+
info.value[:Author] = "International Organization of Legal Metrology"
|
|
24
|
+
info.value[:Creator] = "Arrolio + Pdfrb"
|
|
25
|
+
info.value[:Producer] = "Arrolio + Pdfrb"
|
|
26
|
+
info.value[:CreationDate] = "D:YYYYMMDDHHmmSS+00'00'"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Source data: from `<bibdata>` in the XML — already extracted into
|
|
30
|
+
`Content::Document#metadata`. Author from `<contributor>` where
|
|
31
|
+
role = author.
|
|
32
|
+
|
|
33
|
+
## Done-When
|
|
34
|
+
|
|
35
|
+
- [ ] `pdfinfo out.pdf` shows Title = full English title.
|
|
36
|
+
- [ ] Author = "International Organization of Legal Metrology".
|
|
37
|
+
- [ ] CreationDate format matches PDF spec.
|
|
38
|
+
- [ ] Specs cover: Info dict construction, missing-field handling.
|
|
39
|
+
|
|
40
|
+
## Implementation
|
|
41
|
+
|
|
42
|
+
`Renderer::Pdf#apply_metadata` sets Title, Author, Creator, Producer on the /Info dict.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
priority: P2
|
|
3
|
+
impact: low
|
|
4
|
+
depends_on: [12]
|
|
5
|
+
layer: render
|
|
6
|
+
status: done
|
|
7
|
+
est: 1d
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
mn2pdf emits an XMP packet with PDF/A-1b or PDF/A-2 metadata
|
|
13
|
+
embedded as a stream. Required for long-term archival and some
|
|
14
|
+
workflow tools.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
File: `lib/arrolio/renderer/xmp.rb`
|
|
19
|
+
|
|
20
|
+
Build the XMP XML packet from document metadata:
|
|
21
|
+
|
|
22
|
+
```xml
|
|
23
|
+
<x:xmpmeta xmlns:x="adobe:ns:meta/">
|
|
24
|
+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
|
25
|
+
<rdf:Description xmlns:dc="..." dc:title="..."/>
|
|
26
|
+
<rdf:Description xmlns:pdf="..." pdf:Producer="..."/>
|
|
27
|
+
<rdf:Description xmlns:xmp="..." xmp:CreatorTool="..."/>
|
|
28
|
+
</rdf:RDF>
|
|
29
|
+
</x:xmpmeta>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Embed as an uncompressed stream, referenced from `/Metadata` on
|
|
33
|
+
the catalog dict.
|
|
34
|
+
|
|
35
|
+
## Done-When
|
|
36
|
+
|
|
37
|
+
- [ ] `exiftool out.pdf` shows XMP tags.
|
|
38
|
+
- [ ] Title, Author, Producer match Info dict.
|
|
39
|
+
- [ ] Specs cover: XML well-formedness, namespace correctness.
|
|
40
|
+
|
|
41
|
+
## Implementation
|
|
42
|
+
|
|
43
|
+
`lib/arrolio/renderer/xmp_builder.rb` (95 lines) — `XmpBuilder` generates RDF/XML XMP packet with dc:title, dc:creator, pdf:Producer, xmp:CreatorTool. `Renderer::Pdf#attach_xmp_metadata` emits it as /Metadata stream on catalog. PDF now shows "Metadata Stream: yes". 9 specs.
|