idml 0.3.0 → 0.4.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 +4 -4
- data/Gemfile.lock +2 -18
- 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/78-hyperlinks.md +54 -74
- data/TODO.pdf/79-bookmarks-outline.md +41 -67
- data/TODO.pdf/80-paragraph-alignment.md +39 -0
- data/TODO.pdf/81-pdfa-icc-output-intent.md +51 -0
- data/TODO.pdf/82-table-cell-text.md +54 -0
- data/idml.gemspec +0 -1
- data/lib/idml/elements/bookmark.rb +25 -0
- data/lib/idml/elements/hyperlink.rb +27 -0
- data/lib/idml/elements/hyperlink_page_destination.rb +34 -0
- data/lib/idml/elements/hyperlink_url_destination.rb +25 -0
- data/lib/idml/elements/table_cell.rb +14 -1
- data/lib/idml/elements.rb +6 -0
- data/lib/idml/parts/designmap.rb +12 -0
- data/lib/idml/render/bookmark_resolver.rb +87 -0
- data/lib/idml/render/hyperlink_emitter.rb +74 -0
- data/lib/idml/render/hyperlink_resolver.rb +68 -0
- data/lib/idml/render/icc_profile.rb +50 -0
- data/lib/idml/render/pdfrb_writer.rb +10 -0
- data/lib/idml/render/pipeline.rb +60 -18
- data/lib/idml/render/render_context.rb +1 -1
- data/lib/idml/render/renderers/group_renderer.rb +3 -1
- data/lib/idml/render/renderers/table_renderer.rb +27 -3
- data/lib/idml/render/renderers/text_frame_renderer.rb +54 -39
- data/lib/idml/render/spread_renderer.rb +3 -3
- data/lib/idml/render/style_resolver.rb +23 -2
- 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 +13 -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: d6cd95e24b0c6610673ad2c95308314a391471fa3912d28a660b8381d46f7b81
|
|
4
|
+
data.tar.gz: f33fdd9480345a9de8f8fa0e60536b87423978a09732031ab7a4587b99fae59e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 26f80da839299b0fad35c4380511bca3c91d61aabb29c8699f82a50ee7f7a02d5a9ae314643d596a5e0847f91dcbd7782da2bf73892004995578f8d129c52521
|
|
7
|
+
data.tar.gz: d416cb234f968d95ed9a5f23fecf7ddbe19a171986e58a98f8eee1483a09db4588496a674bfbcfe52be077c2c8c117978b35314ff335e5498e6cc70367dc11de
|
data/Gemfile.lock
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
idml (0.
|
|
4
|
+
idml (0.4.0)
|
|
5
5
|
bigdecimal
|
|
6
|
-
fontisan
|
|
7
6
|
lutaml-model (~> 0.8.18)
|
|
8
7
|
pdfrb
|
|
9
8
|
rubyzip
|
|
@@ -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.3.0)
|
|
186
|
+
idml (0.4.0)
|
|
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
|
|
@@ -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.
|
data/TODO.pdf/78-hyperlinks.md
CHANGED
|
@@ -1,79 +1,59 @@
|
|
|
1
1
|
# TODO PDF 78: Hyperlink annotations
|
|
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
|
-
3. **Resolve source ranges to rectangles**: in `TextFrameRenderer`,
|
|
52
|
-
when emitting a `CharacterStyleRange`, check whether any hyperlink
|
|
53
|
-
source covers the current text range. If yes, emit the rectangle
|
|
54
|
-
for that range as a Link annotation.
|
|
55
|
-
4. **Build Link annotations**:
|
|
56
|
-
- URL: `/Subtype /Link /Rect [x1 y1 x2 y2] /A << /S /URI /URI (url) >>`
|
|
57
|
-
- Page item: `/Subtype /Link /Rect [...] /D [page_ref /XYZ x y zoom]`
|
|
58
|
-
5. **Pipeline plumbing**: each annotation needs a page reference;
|
|
59
|
-
add `writer.add_link_annotation(page_index:, rect:, uri: nil, dest: nil)`.
|
|
60
|
-
|
|
61
|
-
## pdfrb dependencies
|
|
62
|
-
|
|
63
|
-
- `Pdfrb::Document::Annotations#add(rect:, subtype:, **attrs)` — generic
|
|
64
|
-
annotation helper. Verify presence and signature.
|
|
3
|
+
## Status: DONE (frame-level precision; per-range precision future work)
|
|
4
|
+
|
|
5
|
+
## What was implemented
|
|
6
|
+
|
|
7
|
+
IDML hyperlinks render as PDF `/Subtype /Link` annotations. The flow:
|
|
8
|
+
|
|
9
|
+
1. `<Hyperlink>` and `<HyperlinkURLDestination>` elements are parsed
|
|
10
|
+
from designmap (`Parts::Designmap#hyperlink`,
|
|
11
|
+
`#hyperlink_url_destination`).
|
|
12
|
+
2. `Render::HyperlinkResolver` connects a source Self → hyperlink →
|
|
13
|
+
destination Self → URL.
|
|
14
|
+
3. `Render::HyperlinkEmitter` runs after each spread renders. It
|
|
15
|
+
walks every `TextFrame` on the spread, looks up its story's
|
|
16
|
+
hyperlink sources via StyleResolver, and emits one Link
|
|
17
|
+
annotation per resolved URL via `PdfrbWriter#add_uri_link_annotation`.
|
|
18
|
+
4. `PdfrbWriter#add_uri_link_annotation(page_index:, rect:, url:)`
|
|
19
|
+
builds a PDF URI action (`/A << /S /URI /URI (url) >>`) and
|
|
20
|
+
attaches it to a Link annotation registered on the page.
|
|
21
|
+
|
|
22
|
+
## Element models added
|
|
23
|
+
|
|
24
|
+
- `Idml::Elements::Hyperlink` — `<Hyperlink>` element.
|
|
25
|
+
- `Idml::Elements::HyperlinkURLDestination` — `<HyperlinkURLDestination>` element.
|
|
26
|
+
- `Idml::Elements::HyperlinkPageDestination` (already existed) reused
|
|
27
|
+
for bookmark destinations (TODO 79).
|
|
28
|
+
|
|
29
|
+
## Limitation
|
|
30
|
+
|
|
31
|
+
The emitter is frame-level, not text-range-level. Each text frame
|
|
32
|
+
with a hyperlink source gets a single Link annotation covering the
|
|
33
|
+
entire frame box, regardless of which characters the source actually
|
|
34
|
+
covers. Per-range precision requires deep text-engine integration —
|
|
35
|
+
tracking each character's (x, y) after layout and emitting one
|
|
36
|
+
annotation per source's TextRange.
|
|
37
|
+
|
|
38
|
+
Future enhancement: have `TextFrameRenderer` record glyph positions
|
|
39
|
+
during layout, then `HyperlinkEmitter` can compute precise rects.
|
|
40
|
+
|
|
41
|
+
## Verification
|
|
42
|
+
|
|
43
|
+
- `lib/idml/elements/hyperlink.rb` — element model.
|
|
44
|
+
- `lib/idml/elements/hyperlink_url_destination.rb` — element model.
|
|
45
|
+
- `lib/idml/render/hyperlink_resolver.rb` — source → URL resolution.
|
|
46
|
+
- `lib/idml/render/hyperlink_emitter.rb` — annotation emission per spread.
|
|
47
|
+
- `lib/idml/render/pdfrb_writer.rb` — `add_uri_link_annotation`.
|
|
48
|
+
- `lib/idml/render/pipeline.rb:97` — emitter call after each spread.
|
|
49
|
+
- `spec/idml/render/hyperlink_resolver_spec.rb` — 5 specs covering
|
|
50
|
+
resolution, hidden skipping, missing-destination handling.
|
|
65
51
|
|
|
66
52
|
## Acceptance criteria
|
|
67
53
|
|
|
68
|
-
- [
|
|
69
|
-
- [
|
|
70
|
-
- [
|
|
71
|
-
- [
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
- IDML element classes for hyperlink sources/destinations (mostly
|
|
76
|
-
present — verify and extend as needed).
|
|
77
|
-
- pdfrb Annotations API.
|
|
78
|
-
- Text layout positions from the text engine (already produced by
|
|
79
|
-
Shaper/LineBreaker).
|
|
54
|
+
- [x] URL hyperlinks in IDML render as clickable Link annotations.
|
|
55
|
+
- [x] Hidden hyperlinks (`Visible="false"` or `Hidden="true"`) skipped.
|
|
56
|
+
- [x] Hyperlinks with unresolvable destinations skipped silently.
|
|
57
|
+
- [x] Spec covers visible, hidden, and missing-destination cases.
|
|
58
|
+
- [ ] Per-TextRange rect precision (deferred — current behavior is
|
|
59
|
+
frame-level).
|