idml 0.2.9 → 0.3.1
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 +4 -4
- data/Gemfile.lock +4 -20
- data/TODO.pdf/63-replace-fontmetrics-with-pdfrb.md +59 -58
- data/TODO.pdf/65-pdfrb-019-integration.md +57 -29
- data/TODO.pdf/67-text-rich-multi-run.md +35 -39
- data/TODO.pdf/76-tagged-pdf-structure.md +63 -87
- data/TODO.pdf/77-pdfa-xmp-output-intent.md +51 -59
- data/TODO.pdf/78-hyperlinks.md +79 -0
- data/TODO.pdf/79-bookmarks-outline.md +72 -0
- data/idml.gemspec +0 -1
- data/lib/idml/render/image_collector.rb +107 -0
- data/lib/idml/render/page_item_renderer.rb +18 -1
- data/lib/idml/render/pdfa_packet.rb +135 -0
- data/lib/idml/render/pdfrb_writer.rb +2 -1
- data/lib/idml/render/pipeline.rb +47 -101
- data/lib/idml/render/render_context.rb +3 -1
- data/lib/idml/render/renderers/group_renderer.rb +3 -1
- data/lib/idml/render/renderers/text_frame_renderer.rb +34 -25
- data/lib/idml/render/spread_renderer.rb +9 -5
- data/lib/idml/render/structure_mapper.rb +54 -0
- data/lib/idml/render/structure_tracker.rb +55 -0
- data/lib/idml/render.rb +4 -0
- data/lib/idml/text_engine/pdfrb_font_metrics.rb +81 -0
- data/lib/idml/text_engine.rb +1 -2
- data/lib/idml/version.rb +1 -1
- metadata +8 -17
- data/lib/idml/text_engine/font_metrics.rb +0 -226
- data/lib/idml/text_engine/font_resolver.rb +0 -105
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a99eee06508972bf25be9b3c086af445dd8fab275b8c83e9e588e6fc15c5ce2c
|
|
4
|
+
data.tar.gz: 4fc3c99e4dd7be9bfd2c00ab90bf3684bfc397fb838f4504cde8eed575e7ff65
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ef32e1d3a418e26d020638aeb955a4296a5f2574c7ae2d75bc105c345b170bcf4497c3f8fc4e7201dbf159302fcb9c4f2881d6da26355e4f1027973f88ed5140
|
|
7
|
+
data.tar.gz: fb60042f7b9c4fec7cb619a7c2d747daf64229136997cd35d043ee02546facd2003f4cf6a9d52f6444549b4fa7a700371732566722dbd84a6cdac7cb10993a2b
|
data/Gemfile.lock
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
idml (0.
|
|
4
|
+
idml (0.3.1)
|
|
5
5
|
bigdecimal
|
|
6
|
-
fontisan
|
|
7
6
|
lutaml-model (~> 0.8.18)
|
|
8
7
|
pdfrb
|
|
9
8
|
rubyzip
|
|
@@ -12,7 +11,7 @@ PATH
|
|
|
12
11
|
PATH
|
|
13
12
|
remote: /Users/mulgogi/src/claricle/pdfrb
|
|
14
13
|
specs:
|
|
15
|
-
pdfrb (0.
|
|
14
|
+
pdfrb (0.5.0)
|
|
16
15
|
thor
|
|
17
16
|
|
|
18
17
|
GEM
|
|
@@ -21,8 +20,6 @@ GEM
|
|
|
21
20
|
ast (2.4.3)
|
|
22
21
|
base64 (0.3.0)
|
|
23
22
|
bigdecimal (4.1.2)
|
|
24
|
-
bindata (2.5.1)
|
|
25
|
-
brotli (0.8.0)
|
|
26
23
|
canon (0.2.12)
|
|
27
24
|
diff-lcs
|
|
28
25
|
json
|
|
@@ -44,21 +41,12 @@ GEM
|
|
|
44
41
|
ffi (1.17.4-x86_64-darwin)
|
|
45
42
|
ffi (1.17.4-x86_64-linux-gnu)
|
|
46
43
|
ffi (1.17.4-x86_64-linux-musl)
|
|
47
|
-
fontisan (0.4.45)
|
|
48
|
-
base64
|
|
49
|
-
bindata (~> 2.5)
|
|
50
|
-
brotli (~> 0.5)
|
|
51
|
-
logger
|
|
52
|
-
lutaml-model (~> 0.8)
|
|
53
|
-
nokogiri (~> 1.16)
|
|
54
|
-
thor (~> 1.3)
|
|
55
44
|
json (2.21.2)
|
|
56
45
|
language_server-protocol (3.17.0.6)
|
|
57
46
|
lint_roller (1.1.0)
|
|
58
47
|
liquid (5.13.0)
|
|
59
48
|
bigdecimal
|
|
60
49
|
strscan (>= 3.1.1)
|
|
61
|
-
logger (1.7.0)
|
|
62
50
|
lutaml-model (0.8.19)
|
|
63
51
|
base64
|
|
64
52
|
bigdecimal
|
|
@@ -183,8 +171,6 @@ CHECKSUMS
|
|
|
183
171
|
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
|
|
184
172
|
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
|
185
173
|
bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
|
|
186
|
-
bindata (2.5.1) sha256=53186a1ec2da943d4cb413583d680644eb810aacbf8902497aac8f191fad9e58
|
|
187
|
-
brotli (0.8.0) sha256=0c5a42046b3b603fb109656881147fd76064c034b7d19c1b4fcc32a093a4d55d
|
|
188
174
|
canon (0.2.12) sha256=9b87514fbf6c0f9f28d1e2746ea5837b08e24e50c30751dba27a89f86e8709af
|
|
189
175
|
concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1
|
|
190
176
|
csv (3.3.6) sha256=aba61e7e507a66f03d45cb1f3c4b6359861c3504038b422962875dce099e4456
|
|
@@ -197,13 +183,11 @@ CHECKSUMS
|
|
|
197
183
|
ffi (1.17.4-x86_64-darwin) sha256=aa70390523cf3235096cf64962b709b4cfbd5c082a2cb2ae714eb0fe2ccda496
|
|
198
184
|
ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d
|
|
199
185
|
ffi (1.17.4-x86_64-linux-musl) sha256=3fdf9888483de005f8ef8d1cf2d3b20d86626af206cbf780f6a6a12439a9c49e
|
|
200
|
-
|
|
201
|
-
idml (0.2.9)
|
|
186
|
+
idml (0.3.1)
|
|
202
187
|
json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
|
|
203
188
|
language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0
|
|
204
189
|
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
|
205
190
|
liquid (5.13.0) sha256=09d594fd96b98e19a8fd5c32aadcc6950df429b69f48c2f774c0db0ce272bf19
|
|
206
|
-
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
|
207
191
|
lutaml-model (0.8.19) sha256=470a54930b463074c4112fe0abf6708c3a3fd42c460fdb8cad527978b3ddc7ae
|
|
208
192
|
memo_wise (1.13.0) sha256=30220c38c4cef410849bc73553c58664dc2c91c6379e4a1df22aea02358b716b
|
|
209
193
|
moxml (0.1.26) sha256=f29c2a76377f014920fbb9bec8d66acc3f162fa8a4f6a855010c3d2ac1ceeb70
|
|
@@ -219,7 +203,7 @@ CHECKSUMS
|
|
|
219
203
|
paint (2.3.0) sha256=327d623e4038619d5bd99ae5db07973859cd78400c7f0329eea283cef8e83be5
|
|
220
204
|
parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
|
|
221
205
|
parser (3.3.12.0) sha256=21a6d7f755d5a24dfbdc6e6b772e4e879a52e7631a88bc5a3a134606052c9828
|
|
222
|
-
pdfrb (0.
|
|
206
|
+
pdfrb (0.5.0)
|
|
223
207
|
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
|
|
224
208
|
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
|
225
209
|
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
|
|
@@ -1,60 +1,61 @@
|
|
|
1
1
|
# TODO PDF 63: Replace FontMetrics with pdfrb measurement API
|
|
2
2
|
|
|
3
|
-
## Status:
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
`
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
3
|
+
## Status: DONE
|
|
4
|
+
|
|
5
|
+
## What was implemented
|
|
6
|
+
|
|
7
|
+
`Idml::TextEngine::FontMetrics` (200+ lines of TTF binary parsing via
|
|
8
|
+
Fontisan) and `Idml::TextEngine::FontResolver` have been removed. In
|
|
9
|
+
their place:
|
|
10
|
+
|
|
11
|
+
- **`Idml::TextEngine::PdfrbFontMetrics`** — adapter that exposes
|
|
12
|
+
the same measurement surface (`glyph_width`, `measure_text`,
|
|
13
|
+
`units_per_em`, `ascent`, `descent`) by delegating to pdfrb's
|
|
14
|
+
`Fonts#glyph_width` and `Fonts#metrics_for`. pdfrb parses TTF
|
|
15
|
+
tables (cmap, hmtx, head, hhea) via `Pdfrb::Font::TrueType::File`
|
|
16
|
+
and exposes real per-glyph advance widths for any registered font.
|
|
17
|
+
- **`Pdfrb::FontResolver`** — used directly by Pipeline for
|
|
18
|
+
PostScriptName → file path resolution. Returns paths as strings,
|
|
19
|
+
no FontMetrics wrapper.
|
|
20
|
+
|
|
21
|
+
## What was removed
|
|
22
|
+
|
|
23
|
+
- `lib/idml/text_engine/font_metrics.rb` (200+ lines of Fontisan parsing).
|
|
24
|
+
- `lib/idml/text_engine/font_resolver.rb` (Fontisan-coupled resolver).
|
|
25
|
+
- `fontisan` gemspec dependency.
|
|
26
|
+
- `fontisan` from `spec/anti_patterns_spec.rb` external require allowlist.
|
|
27
|
+
- Specs that tested FontMetrics caching and FontResolver resolution.
|
|
28
|
+
Remaining specs (Shaper, LineBreaker, Justifier) now use
|
|
29
|
+
`PdfrbFontMetrics` for their font fixture.
|
|
30
|
+
|
|
31
|
+
## Pipeline integration
|
|
32
|
+
|
|
33
|
+
1. `Pipeline#register_font(writer)` finds the font file via
|
|
34
|
+
`Pdfrb::FontResolver#find_by_ps_name` and registers it with
|
|
35
|
+
`pdfrb.fonts.add(path)`, returning a Symbol resource.
|
|
36
|
+
2. `Pipeline#build_font_metrics(writer, resource)` wraps the
|
|
37
|
+
resource in a `PdfrbFontMetrics` instance.
|
|
38
|
+
3. The metrics object is threaded through `SpreadRenderer` →
|
|
39
|
+
`RenderContext#font_metrics` → `TextFrameRenderer#engine_render`.
|
|
40
|
+
4. Shaper and LineBreaker consume `font.units_per_em` and
|
|
41
|
+
`font.glyph_width(codepoint)` exactly as before — the duck-typed
|
|
42
|
+
PdfrbFontMetrics satisfies the same interface.
|
|
43
|
+
|
|
44
|
+
## Verification
|
|
45
|
+
|
|
46
|
+
- `lib/idml/text_engine/pdfrb_font_metrics.rb` — adapter.
|
|
47
|
+
- `lib/idml/render/pipeline.rb` — font_resolver + font_metrics wiring.
|
|
48
|
+
- `spec/idml/text_engine/pdfrb_font_metrics_spec.rb` — 13 specs.
|
|
49
|
+
- `spec/idml/text_engine/text_engine_spec.rb` — Shaper/LineBreaker/
|
|
50
|
+
Justifier specs use pdfrb metrics.
|
|
51
|
+
- All `bundle exec rake` green (2500+ examples).
|
|
52
|
+
- Anti-pattern spec green with `fontisan` removed from allowlist.
|
|
53
|
+
|
|
54
|
+
## Acceptance criteria
|
|
55
|
+
|
|
56
|
+
- [x] Pipeline registers fonts via `pdfrb.fonts.add(path)`.
|
|
57
|
+
- [x] Shaper/LineBreaker call pdfrb's measurement API via
|
|
58
|
+
PdfrbFontMetrics, not Fontisan.
|
|
59
|
+
- [x] `fontisan` removed from gemspec dependencies.
|
|
60
|
+
- [x] `FontMetrics` and `FontResolver` deleted from `lib/`.
|
|
61
|
+
- [x] Specs use PdfrbFontMetrics fixtures, not Fontisan.
|
|
@@ -1,39 +1,67 @@
|
|
|
1
1
|
# TODO PDF 65: pdfrb feature integration
|
|
2
2
|
|
|
3
|
-
## Status:
|
|
3
|
+
## Status: DONE
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
All proposals landed in pdfrb 0.4.0+ are now integrated into the idml
|
|
6
|
+
gem's render pipeline. Summary of what's wired up:
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
for batched single-font multi-line text (TODO 27).
|
|
9
|
-
2. **Real PDF gradient shadings**: `RectangleRenderer` uses pdfrb's
|
|
10
|
-
`Shadings#add_axial` and `Shadings#add_radial` (TODOs 49, 66, 68).
|
|
11
|
-
3. **Canvas#with_transparency**: `Blending.wrap` applies IDML
|
|
12
|
-
`BlendingSetting` opacity + blend modes (TODO 69).
|
|
13
|
-
4. **Stroke-style setters**: `StrokeStyle.apply` calls
|
|
14
|
-
`line_cap=`/`line_join=`/`miter_limit=`/`dash_pattern=` from
|
|
15
|
-
IDML `EndCap`/`EndJoin`/`MiterLimit`/`StrokeDashAndGap` (TODO 70/72).
|
|
16
|
-
5. **Font subsetting**: `Pipeline` calls `Fonts#subset_fonts!` before
|
|
17
|
-
write (TODO 52).
|
|
18
|
-
6. **Placement module**: shared `Render::Placement.box` (TODO 71).
|
|
8
|
+
## Implemented features
|
|
19
9
|
|
|
20
|
-
|
|
10
|
+
1. **Canvas#text_lines** (TODO 27): `TextFrameRenderer#engine_render`
|
|
11
|
+
emits one `text_lines` call per styled run after shaping/line-
|
|
12
|
+
breaking. Each run's glyphs render as a batched multi-line block.
|
|
13
|
+
2. **Canvas#text_rich** (TODO 67): `TextFrameRenderer#simple_render`
|
|
14
|
+
emits one `text_rich` call per frame in the no-metrics fallback
|
|
15
|
+
path. pdfrb's measurement API handles run advance.
|
|
16
|
+
3. **Real PDF gradient shadings** (TODOs 49, 66, 68): `RectangleRenderer`
|
|
17
|
+
dispatches to `Shadings#add_axial` or `add_radial` based on
|
|
18
|
+
`Gradient.type`, then `Canvas#fill_shading`.
|
|
19
|
+
4. **Canvas#with_transparency** (TODO 69): `Blending.wrap` applies
|
|
20
|
+
IDML `BlendingSetting` opacity + blend modes to all shape renderers.
|
|
21
|
+
5. **Stroke-style setters** (TODO 70/72): `StrokeStyle.apply` calls
|
|
22
|
+
`Canvas#line_cap=`/`line_join=`/`miter_limit=`/`dash_pattern=` from
|
|
23
|
+
IDML `EndCap`/`EndJoin`/`MiterLimit`/`StrokeDashAndGap`.
|
|
24
|
+
6. **Font subsetting** (TODO 52): `Pipeline` calls `Fonts#subset_fonts!`
|
|
25
|
+
before write. Each TrueType font's FontFile2 is rewritten with only
|
|
26
|
+
the used glyphs.
|
|
27
|
+
7. **Tagged PDF structure** (TODO 76): `StructureTracker` +
|
|
28
|
+
`StructureMapper` + `PageItemRenderer.wrap_tagged` emit a real
|
|
29
|
+
`/StructTreeRoot` with per-item structure elements.
|
|
30
|
+
8. **PDF/A XMP** (TODO 77): `PdfaPacket` builds an XMP packet declaring
|
|
31
|
+
`pdfaid:part=2`, `pdfaid:conformance=A`, attaches as
|
|
32
|
+
`/Catalog/Metadata` stream when `compliance:` is set.
|
|
33
|
+
9. **XMP metadata extraction** (TODO 75): `Parts::XmpMeta` parses
|
|
34
|
+
`META-INF/metadata.xml` (XMP packet) using Lutaml namespace
|
|
35
|
+
composition; Pipeline threads dc:title/creator/description/subject
|
|
36
|
+
plus xmp:CreatorTool/CreateDate/ModifyDate into the Info dict.
|
|
37
|
+
10. **Per-glyph width measurement** (TODO 63): `TextEngine::PdfrbFontMetrics`
|
|
38
|
+
delegates to pdfrb's `Fonts#glyph_width` (real TTF parsing via
|
|
39
|
+
`Pdfrb::Font::TrueType::File`). Fontisan dependency removed.
|
|
40
|
+
11. **Font resolution** (TODO 40): Pipeline resolves document fonts
|
|
41
|
+
via `Pdfrb::FontResolver#find_by_ps_name`, falls back to default.
|
|
21
42
|
|
|
22
|
-
|
|
43
|
+
## Deferred / out of scope
|
|
23
44
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
45
|
+
- **PDF/A ICC output intent** (TODO 77 partial) — sRGB ICC profile
|
|
46
|
+
not vendored. XMP packet alone satisfies "XMP required" rule but
|
|
47
|
+
not the "output intent" rule.
|
|
48
|
+
- **Hyperlinks** (TODO 78) — design doc only.
|
|
49
|
+
- **Bookmarks/outline** (TODO 79) — design doc only.
|
|
50
|
+
- **Stroke style references** (TODO 70 limitation) — only inline
|
|
51
|
+
`StrokeDashAndGap` attribute is parsed; named `StrokeStyle` self-IDs
|
|
52
|
+
in Resources/Graphic.xml not yet resolved.
|
|
29
53
|
|
|
30
54
|
## Acceptance criteria
|
|
31
55
|
|
|
32
|
-
- [x] TextFrameRenderer uses `canvas.text_lines`
|
|
33
|
-
|
|
34
|
-
- [x]
|
|
35
|
-
- [x]
|
|
36
|
-
- [x]
|
|
37
|
-
- [x]
|
|
38
|
-
- [
|
|
39
|
-
- [
|
|
56
|
+
- [x] TextFrameRenderer uses `canvas.text_lines` (engine path) and
|
|
57
|
+
`canvas.text_rich` (fallback path).
|
|
58
|
+
- [x] Real PDF gradient shadings via pdfrb.
|
|
59
|
+
- [x] Transparency and blend modes.
|
|
60
|
+
- [x] Stroke styling.
|
|
61
|
+
- [x] Font subsetting.
|
|
62
|
+
- [x] Tagged PDF structure.
|
|
63
|
+
- [x] PDF/A XMP packet.
|
|
64
|
+
- [x] XMP metadata extraction.
|
|
65
|
+
- [x] FontMetrics replaced with pdfrb measurement.
|
|
66
|
+
- [x] FontResolver replaced with `Pdfrb::FontResolver`.
|
|
67
|
+
- [x] `fontisan` removed from gemspec.
|
|
@@ -1,54 +1,50 @@
|
|
|
1
1
|
# TODO PDF 67: Multi-run text batching via Canvas#text_rich
|
|
2
2
|
|
|
3
|
-
## Status:
|
|
3
|
+
## Status: DONE
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## What was implemented
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
`TextFrameRenderer#simple_render` (the fallback path when no
|
|
8
|
+
`FontMetrics` is available) now emits a single `Canvas#text_rich`
|
|
9
|
+
call per text frame, batched across all runs. pdfrb's `text_rich`
|
|
10
|
+
emits one `BT`/`ET` block and advances the text matrix between runs
|
|
11
|
+
via `Fonts#measure_text` — which now returns real per-glyph widths
|
|
12
|
+
for TTF/OTF fonts.
|
|
10
13
|
|
|
11
|
-
##
|
|
14
|
+
## Architecture
|
|
12
15
|
|
|
13
|
-
`
|
|
14
|
-
text block per frame instead of N. But its run-advance relies on
|
|
15
|
-
`Pdfrb::Document::Fonts#measure_text`, which in pdfrb 0.4.0 still
|
|
16
|
-
returns the stub `length * 0.5 * size` for TTF/OTF fonts (only
|
|
17
|
-
Standard 14 AFM fonts get real measurement). Without accurate advance,
|
|
18
|
-
runs overwrite each other on the line.
|
|
16
|
+
`TextFrameRenderer#render_text` chooses between two paths:
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
- **`engine_render`** — used when `context.font_metrics` is present.
|
|
19
|
+
Walks each `StyledRun` through `Shaper` and `LineBreaker` for
|
|
20
|
+
word-wrap, then emits `canvas.text_lines` per run.
|
|
21
|
+
- **`simple_render`** — fallback when metrics are absent. Builds a
|
|
22
|
+
`{ text:, font:, size: }` array and emits one `canvas.text_rich`
|
|
23
|
+
call. pdfrb's measurement handles run advance.
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
Both paths use the same `frame_box` geometry (via `Placement.box`).
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
def measure_text(text, font:, size:)
|
|
26
|
-
return 0 unless text && size
|
|
27
|
-
metrics = @afm_metrics[font]
|
|
28
|
-
return text.to_s.length * size.to_f * 0.5 unless metrics # STUB
|
|
29
|
-
...
|
|
30
|
-
end
|
|
31
|
-
```
|
|
27
|
+
## Why two paths
|
|
32
28
|
|
|
33
|
-
|
|
34
|
-
the
|
|
35
|
-
|
|
36
|
-
`
|
|
29
|
+
The engine path performs IDML-faithful word-wrap and line breaking
|
|
30
|
+
using the text engine (Shaper/LineBreaker). The simple path skips
|
|
31
|
+
layout but still produces correct visual output via pdfrb's native
|
|
32
|
+
measurement — useful when `context.font_metrics` is nil (e.g., font
|
|
33
|
+
registration failed and we fell back to Helvetica with no metrics).
|
|
37
34
|
|
|
38
|
-
|
|
35
|
+
As pdfrb's measurement API stabilises, a future refactor could merge
|
|
36
|
+
the two paths and let pdfrb handle both layout and emission.
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
2. Replace `simple_render` body with `canvas.text_rich(runs, at: [x, y])`.
|
|
42
|
-
3. Verify rendered PDF: runs no longer overwrite, multi-color lines work.
|
|
43
|
-
4. Combine with TODO 63 to drop Fontisan for measurement.
|
|
38
|
+
## Verification
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
40
|
+
- `lib/idml/render/renderers/text_frame_renderer.rb:97` — `simple_render`.
|
|
41
|
+
- `spec/idml/render/render_pdfrb_pipeline_spec.rb` — integration
|
|
42
|
+
spec verifies BT/ET emission.
|
|
43
|
+
- `spec/idml/render/text_frame_renderer_spec.rb` — 3 specs covering
|
|
44
|
+
no-story skip, non-chain-head skip, end-to-end BT/ET emission.
|
|
50
45
|
|
|
51
|
-
##
|
|
46
|
+
## Acceptance criteria
|
|
52
47
|
|
|
53
|
-
-
|
|
54
|
-
|
|
48
|
+
- [x] `TextFrameRenderer#simple_render` uses `canvas.text_rich`.
|
|
49
|
+
- [x] Single BT/ET block per frame in fallback path.
|
|
50
|
+
- [x] Render spec verifies text emission end-to-end.
|
|
@@ -1,92 +1,68 @@
|
|
|
1
1
|
# TODO PDF 76: Tagged PDF structure tree
|
|
2
2
|
|
|
3
|
-
## Status:
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
- `RectangleRenderer`/`PolygonRenderer` with `ContentType="GraphicType"`
|
|
61
|
-
and an image child → `Figure` with `Alt` from IDML `<Image Alt="...">`.
|
|
62
|
-
- `RectangleRenderer`/`PolygonRenderer` without image → `Path` (or
|
|
63
|
-
`Sect` if the item is a container).
|
|
64
|
-
- `TextFrameRenderer` → `P` (one per paragraph run).
|
|
65
|
-
- `GroupRenderer` → `Sect` (wraps its children's elements).
|
|
66
|
-
- `TableRenderer` → `Table`, `TR`, `TH`, `TD` per row/cell.
|
|
67
|
-
4. **Artifact marking**: Page furniture (master-spread items,
|
|
68
|
-
non-content decorations) is wrapped in `artifact(:background)`.
|
|
69
|
-
5. **Pipeline threading**: Pipeline passes a `structure: true` flag
|
|
70
|
-
through `RenderContext`. Renderers only emit structure when the
|
|
71
|
-
flag is set.
|
|
72
|
-
6. **Build**: Pipeline calls `writer.build_structure` at the end —
|
|
73
|
-
this stitches the per-element registrations into a `/StructTreeRoot`.
|
|
3
|
+
## Status: DONE
|
|
4
|
+
|
|
5
|
+
## What was implemented
|
|
6
|
+
|
|
7
|
+
`idml render --tagged sample.idml -o out.pdf` now emits a real PDF
|
|
8
|
+
structure tree. Each visible page item becomes a structure element,
|
|
9
|
+
wrapped in a marked-content sequence (BDC/EMC) with an MCID that
|
|
10
|
+
maps back to the element.
|
|
11
|
+
|
|
12
|
+
Architecture:
|
|
13
|
+
|
|
14
|
+
- `Render::StructureTracker` — per-pipeline object that allocates
|
|
15
|
+
per-page MCIDs and buffers element registrations. `enabled?` is
|
|
16
|
+
false when `tagged:` was not requested; everything is a no-op then.
|
|
17
|
+
- `Render::StructureMapper` — pure function from item class to PDF
|
|
18
|
+
structure type:
|
|
19
|
+
- `TextFrame` → `:P`
|
|
20
|
+
- `Group` → `:Sect`
|
|
21
|
+
- `Table` → `:Table`
|
|
22
|
+
- `Rectangle`/`Polygon` with image → `:Figure` (carries `Alt` from
|
|
23
|
+
the item's `Name`)
|
|
24
|
+
- `Rectangle`/`Polygon` without image → `:Path`
|
|
25
|
+
- `GraphicLine` → `:Path`
|
|
26
|
+
- `PageItemRenderer` — when `context.structure.enabled?`, it computes
|
|
27
|
+
the type, allocates an MCID, registers the entry, and wraps the
|
|
28
|
+
renderer's draw in `canvas.tagged(type, mcid: mcid)`. Unknown item
|
|
29
|
+
classes (no mapping) bypass the wrap and render normally.
|
|
30
|
+
- `RenderContext` carries `structure` and `page_index`.
|
|
31
|
+
- `Pipeline` constructs one `StructureTracker` per run, threads it
|
|
32
|
+
through `SpreadRenderer` → `RenderContext`, and flushes entries to
|
|
33
|
+
the writer after every spread renders. Then calls
|
|
34
|
+
`writer.build_structure` to finalise the tree.
|
|
35
|
+
|
|
36
|
+
## Limitations
|
|
37
|
+
|
|
38
|
+
- Structure is currently flat — every visible page item is a top-level
|
|
39
|
+
child of `/StructTreeRoot`. PDF/UA nesting (Group → Sect, Table →
|
|
40
|
+
TR/TD) is not yet emitted. pdfrb's `add_child(parent, type)` exists
|
|
41
|
+
for nesting; future work.
|
|
42
|
+
- Master-spread items are tagged as content, not as artifacts. PDF/UA
|
|
43
|
+
prefers `canvas.artifact(:background)` for page furniture; future
|
|
44
|
+
work.
|
|
45
|
+
- TextFrame emits a single `:P` element, not one per paragraph run.
|
|
46
|
+
IDML text frames can contain multiple paragraphs; the structure tree
|
|
47
|
+
should ideally have one `:P` per paragraph.
|
|
48
|
+
|
|
49
|
+
## Verification
|
|
50
|
+
|
|
51
|
+
- `lib/idml/render/structure_tracker.rb` — MCID allocation + flush.
|
|
52
|
+
- `lib/idml/render/structure_mapper.rb` — item → type map.
|
|
53
|
+
- `lib/idml/render/page_item_renderer.rb:19` — `wrap_tagged` integration.
|
|
54
|
+
- `lib/idml/render/pipeline.rb:38` — tracker construction + flush.
|
|
55
|
+
- `spec/idml/render/structure_tracker_spec.rb` — 5 specs.
|
|
56
|
+
- `spec/idml/render/structure_mapper_spec.rb` — 9 specs.
|
|
57
|
+
- `spec/idml/render/render_pdfrb_pipeline_spec.rb:141` — integration
|
|
58
|
+
spec asserts `/StructTreeRoot`, `/MarkInfo`, `/StructElem`, BDC/EMC
|
|
59
|
+
counts; untagged variant asserts no tree.
|
|
74
60
|
|
|
75
61
|
## Acceptance criteria
|
|
76
62
|
|
|
77
|
-
- [
|
|
78
|
-
whose `/StructTreeRoot` has
|
|
79
|
-
- [
|
|
80
|
-
right type (Figure
|
|
81
|
-
- [
|
|
82
|
-
- [
|
|
83
|
-
elements matching the rendered items.
|
|
84
|
-
- [ ] Items filtered out by `LayerFilter` do not get structure
|
|
85
|
-
entries.
|
|
86
|
-
|
|
87
|
-
## Dependencies
|
|
88
|
-
|
|
89
|
-
- pdfrb `Canvas#tagged` and `Document::Structure#add_element` (DONE
|
|
90
|
-
in 0.4.0).
|
|
91
|
-
- Per-page MCID counter — small addition to `RenderContext`.
|
|
92
|
-
- Per-renderer structure-type mapping table.
|
|
63
|
+
- [x] `idml render --tagged sample.idml -o out.pdf` produces a PDF
|
|
64
|
+
whose `/StructTreeRoot` has elements.
|
|
65
|
+
- [x] Each visible page item maps to a structure element with the
|
|
66
|
+
right type (`:Figure`, `:P`, `:Path`, `:Sect`, `:Table`).
|
|
67
|
+
- [x] Items filtered out by `LayerFilter` do not get structure entries.
|
|
68
|
+
- [x] Spec covers enabled/disabled paths and item-type mapping.
|