idml 0.3.1 → 0.4.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 -4
- 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/TODO.pdf/83-pipeline-decomposition.md +38 -0
- data/lib/idml/elements/bookmark.rb +25 -0
- data/lib/idml/elements/character_style_range.rb +3 -0
- data/lib/idml/elements/hyperlink.rb +27 -0
- data/lib/idml/elements/hyperlink_page_destination.rb +34 -0
- data/lib/idml/elements/hyperlink_text_source.rb +31 -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 +8 -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 +78 -0
- data/lib/idml/render/hyperlink_resolver.rb +68 -0
- data/lib/idml/render/icc_profile.rb +50 -0
- data/lib/idml/render/metadata_builder.rb +73 -0
- data/lib/idml/render/pdfrb_writer.rb +10 -0
- data/lib/idml/render/pipeline.rb +36 -56
- data/lib/idml/render/renderers/table_renderer.rb +27 -3
- data/lib/idml/render/renderers/text_frame_renderer.rb +23 -17
- data/lib/idml/render/style_resolver.rb +23 -2
- data/lib/idml/render.rb +5 -0
- data/lib/idml/version.rb +1 -1
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d7defde95ef6dc98c93c16a27f09b93c17aaea161a6878b1b130b58b3d8ba9cd
|
|
4
|
+
data.tar.gz: 603a52445ffcb85e2e384e097d74810a302b045aecfaf4937af10d763c076c2d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: da8441f49894dcce6656d429357421ee101c59061738e73527b9dc574d8905adf68b8a2fb78a5cc6bd5a4b67d2f4774893d96c9608ea7e6b91d945a1189b8e20
|
|
7
|
+
data.tar.gz: 5251dd9d4c5bbb4003dc2f159a87a8d6ba1a9c1e78beacdbce37df366edf7cdd89a339b27edb470b91641db9ba387a2964e9857d39900476b57507d87470b873
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
idml (0.
|
|
4
|
+
idml (0.4.1)
|
|
5
5
|
bigdecimal
|
|
6
6
|
lutaml-model (~> 0.8.18)
|
|
7
7
|
pdfrb
|
|
@@ -11,7 +11,7 @@ PATH
|
|
|
11
11
|
PATH
|
|
12
12
|
remote: /Users/mulgogi/src/claricle/pdfrb
|
|
13
13
|
specs:
|
|
14
|
-
pdfrb (0.
|
|
14
|
+
pdfrb (0.6.0)
|
|
15
15
|
thor
|
|
16
16
|
|
|
17
17
|
GEM
|
|
@@ -183,7 +183,7 @@ CHECKSUMS
|
|
|
183
183
|
ffi (1.17.4-x86_64-darwin) sha256=aa70390523cf3235096cf64962b709b4cfbd5c082a2cb2ae714eb0fe2ccda496
|
|
184
184
|
ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d
|
|
185
185
|
ffi (1.17.4-x86_64-linux-musl) sha256=3fdf9888483de005f8ef8d1cf2d3b20d86626af206cbf780f6a6a12439a9c49e
|
|
186
|
-
idml (0.
|
|
186
|
+
idml (0.4.1)
|
|
187
187
|
json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
|
|
188
188
|
language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0
|
|
189
189
|
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
|
@@ -203,7 +203,7 @@ CHECKSUMS
|
|
|
203
203
|
paint (2.3.0) sha256=327d623e4038619d5bd99ae5db07973859cd78400c7f0329eea283cef8e83be5
|
|
204
204
|
parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
|
|
205
205
|
parser (3.3.12.0) sha256=21a6d7f755d5a24dfbdc6e6b772e4e879a52e7631a88bc5a3a134606052c9828
|
|
206
|
-
pdfrb (0.
|
|
206
|
+
pdfrb (0.6.0)
|
|
207
207
|
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
|
|
208
208
|
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
|
209
209
|
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
|
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).
|
|
@@ -1,72 +1,46 @@
|
|
|
1
1
|
# TODO PDF 79: PDF outline (bookmarks)
|
|
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
|
-
## Plan
|
|
41
|
-
|
|
42
|
-
1. **Parse bookmarks**: extend `Parts::Designmap` to expose
|
|
43
|
-
`bookmark` and `pdf_page_destination` collections (element classes
|
|
44
|
-
may need adding to `Idml::Elements`).
|
|
45
|
-
2. **Resolve destinations**: a bookmark's `Destination` references a
|
|
46
|
-
`PDFPageDestination` whose `PageDestinationPage` references a
|
|
47
|
-
spread-page. Build a Self → page_index map at pipeline start.
|
|
48
|
-
3. **Pipeline emit step**: after rendering all spreads, iterate
|
|
49
|
-
bookmarks and call
|
|
50
|
-
`writer.add_bookmark(name, page_index: page_index_of(destination))`.
|
|
51
|
-
4. **Optional nesting**: if IDML exposes a parent/child relationship
|
|
52
|
-
(it doesn't directly — bookmarks are flat), the outline stays
|
|
53
|
-
flat. Future work could derive hierarchy from heading styles.
|
|
54
|
-
|
|
55
|
-
## pdfrb dependencies
|
|
56
|
-
|
|
57
|
-
- `Pdfrb::Document::Outline#add(title, dest:, parent: nil)` — DONE in
|
|
58
|
-
pdfrb 0.4.0. `dest` accepts a page object or reference.
|
|
3
|
+
## Status: DONE
|
|
4
|
+
|
|
5
|
+
## What was implemented
|
|
6
|
+
|
|
7
|
+
`idml render` now emits PDF outline entries from IDML bookmarks. The
|
|
8
|
+
flow:
|
|
9
|
+
|
|
10
|
+
1. `<Bookmark>` and `<HyperlinkPageDestination>` elements are parsed
|
|
11
|
+
from designmap (`Parts::Designmap#bookmark`,
|
|
12
|
+
`#hyperlink_page_destination`).
|
|
13
|
+
2. `Render::BookmarkResolver` walks bookmarks, resolves
|
|
14
|
+
`Bookmark#destination` (Self) →
|
|
15
|
+
`HyperlinkPageDestination#destination_page` (Page Self) →
|
|
16
|
+
page index (via spread iteration), and yields `[title, page_index]`
|
|
17
|
+
pairs.
|
|
18
|
+
3. `Pipeline#emit_bookmarks` calls
|
|
19
|
+
`PdfrbWriter#add_bookmark(title, page_index)` for each resolved
|
|
20
|
+
entry. `PdfrbWriter` delegates to `Pdfrb::Document::Outline#add`.
|
|
21
|
+
|
|
22
|
+
## Element models added
|
|
23
|
+
|
|
24
|
+
- `Idml::Elements::Bookmark` — `<Bookmark>` element with Self, Name,
|
|
25
|
+
and Destination attributes.
|
|
26
|
+
- `Idml::Elements::HyperlinkPageDestination` (added for hyperlinks)
|
|
27
|
+
reused here.
|
|
28
|
+
|
|
29
|
+
## Verification
|
|
30
|
+
|
|
31
|
+
- `lib/idml/elements/bookmark.rb` — element model.
|
|
32
|
+
- `lib/idml/parts/designmap.rb:50` — bookmark + hyperlink_page_destination
|
|
33
|
+
attributes and element mappings.
|
|
34
|
+
- `lib/idml/render/bookmark_resolver.rb` — destination chain + page
|
|
35
|
+
lookup.
|
|
36
|
+
- `lib/idml/render/pipeline.rb:60` — `emit_bookmarks` call.
|
|
37
|
+
- `spec/idml/render/bookmark_resolver_spec.rb` — 2 specs covering
|
|
38
|
+
resolved and unresolved destinations.
|
|
59
39
|
|
|
60
40
|
## Acceptance criteria
|
|
61
41
|
|
|
62
|
-
- [
|
|
63
|
-
- [
|
|
64
|
-
- [
|
|
65
|
-
- [
|
|
66
|
-
|
|
67
|
-
## Dependencies
|
|
68
|
-
|
|
69
|
-
- Element classes `Idml::Elements::Bookmark`, `PDFPageDestination`
|
|
70
|
-
(TODO: add via rnc_to_lutaml generator).
|
|
71
|
-
- Pipeline step ordering: outline must be built after all pages are
|
|
72
|
-
registered with the writer.
|
|
42
|
+
- [x] IDML bookmarks appear as top-level PDF outline entries.
|
|
43
|
+
- [x] Clicking a bookmark navigates to the right page (page_index).
|
|
44
|
+
- [x] Bookmarks with unresolvable destinations skipped.
|
|
45
|
+
- [x] Spec covers a fixture-equivalent synthetic with two resolvable
|
|
46
|
+
and one broken bookmark.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# TODO PDF 80: Paragraph alignment via Justifier
|
|
2
|
+
|
|
3
|
+
## Status: DONE
|
|
4
|
+
|
|
5
|
+
## What was implemented
|
|
6
|
+
|
|
7
|
+
`TextFrameRenderer#engine_render` now applies paragraph alignment
|
|
8
|
+
from each `StyledRun` via the existing `TextEngine::Justifier`.
|
|
9
|
+
|
|
10
|
+
1. `StyleResolver::StyledRun` gains an `alignment` field, populated
|
|
11
|
+
from `CharacterStyleRange#justification` via
|
|
12
|
+
`StyleResolver::ALIGNMENT_MAP` (IDML enum → Justifier symbol).
|
|
13
|
+
2. `TextFrameRenderer#render_run_lines` calls
|
|
14
|
+
`Justifier.justify(line:, frame_width:, alignment:)` per line
|
|
15
|
+
after `LineBreaker.break`, then emits `canvas.text` per line at
|
|
16
|
+
`box[:x] + line.x_offset`.
|
|
17
|
+
3. IDML Justification values map cleanly:
|
|
18
|
+
- `Left`, `LeftJustified`, `ToBinding` → `:left`
|
|
19
|
+
- `Center`, `CenterJustified` → `:center`
|
|
20
|
+
- `Right`, `RightJustified` → `:right`
|
|
21
|
+
- `FullyJustified` → `:justified` (Justifier distributes slack
|
|
22
|
+
across spaces)
|
|
23
|
+
|
|
24
|
+
## Verification
|
|
25
|
+
|
|
26
|
+
- `lib/idml/render/style_resolver.rb:20` — `ALIGNMENT_MAP`.
|
|
27
|
+
- `lib/idml/render/style_resolver.rb:48` — `alignment_for(csr)`.
|
|
28
|
+
- `lib/idml/render/renderers/text_frame_renderer.rb:73` —
|
|
29
|
+
`Justifier.justify` call + per-line text emission.
|
|
30
|
+
- `spec/idml/render/render_helpers_spec.rb` — alignment field on
|
|
31
|
+
StyledRun + ALIGNMENT_MAP specs.
|
|
32
|
+
|
|
33
|
+
## Acceptance criteria
|
|
34
|
+
|
|
35
|
+
- [x] StyledRun carries alignment.
|
|
36
|
+
- [x] Center-aligned paragraphs render centered in their frame.
|
|
37
|
+
- [x] Right-aligned paragraphs render flush-right.
|
|
38
|
+
- [x] Left alignment (default) renders unchanged.
|
|
39
|
+
- [x] Fully-justified distributes slack across spaces via Justifier.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# TODO PDF 81: sRGB ICC profile + output intent for PDF/A
|
|
2
|
+
|
|
3
|
+
## Status: DONE
|
|
4
|
+
|
|
5
|
+
## What was implemented
|
|
6
|
+
|
|
7
|
+
Pipeline now embeds an sRGB ICC profile and registers a PDF/A
|
|
8
|
+
output intent when `compliance: :pdfa2a` (or any `:pdfa*`).
|
|
9
|
+
|
|
10
|
+
1. `Render::IccProfile.srgb_bytes` returns ICC bytes from the first
|
|
11
|
+
available source:
|
|
12
|
+
- `ENV["IDML_SRGB_ICC"]` (explicit override)
|
|
13
|
+
- `data/idml/srgb.icc` inside the gem tree (user-vendored)
|
|
14
|
+
- macOS system profile at
|
|
15
|
+
`/System/Library/ColorSync/Profiles/sRGB Profile.icc`
|
|
16
|
+
Returns `nil` if none available.
|
|
17
|
+
2. `Pipeline#embed_pdfa_output_intent` reads bytes and calls
|
|
18
|
+
`writer.document.output_intents.embed_icc(bytes, identifier:
|
|
19
|
+
"sRGB", condition: "sRGB IEC61966-2.1", subtype: :GTS_PDFA1)`.
|
|
20
|
+
3. Embedding is graceful — if no profile is available, the XMP
|
|
21
|
+
packet is still attached (from TODO 77) but `/OutputIntents` is
|
|
22
|
+
omitted.
|
|
23
|
+
|
|
24
|
+
## Why no bundled ICC
|
|
25
|
+
|
|
26
|
+
The idml gem stays dependency-free for binary assets. Users who
|
|
27
|
+
need PDF/A on non-macOS systems can either:
|
|
28
|
+
- Set `IDML_SRGB_ICC=/path/to/srgb.icc` in the environment, or
|
|
29
|
+
- Drop a profile at `<gem>/data/idml/srgb.icc` after install.
|
|
30
|
+
|
|
31
|
+
This avoids bundling a binary whose licensing might be ambiguous,
|
|
32
|
+
while still using well-formed ICC bytes when they're available.
|
|
33
|
+
|
|
34
|
+
## Verification
|
|
35
|
+
|
|
36
|
+
- `lib/idml/render/icc_profile.rb` — locator helper.
|
|
37
|
+
- `lib/idml/render/pipeline.rb:32` — `embed_pdfa_output_intent` call.
|
|
38
|
+
- `spec/idml/render/icc_profile_spec.rb` — 3 specs covering env var,
|
|
39
|
+
system path, and no-source fallback.
|
|
40
|
+
- Pipeline integration test: PDF/A output now includes both
|
|
41
|
+
`/Metadata` (XMP) and `/OutputIntents` referencing sRGB.
|
|
42
|
+
|
|
43
|
+
## Acceptance criteria
|
|
44
|
+
|
|
45
|
+
- [x] `data/idml/srgb.icc` path supported (user-vendored).
|
|
46
|
+
- [x] `IDML_SRGB_ICC` env var supported.
|
|
47
|
+
- [x] macOS system path probed as fallback.
|
|
48
|
+
- [x] PDF/A output has `/OutputIntents` referencing embedded ICC
|
|
49
|
+
when any source is available.
|
|
50
|
+
- [x] Non-PDF/A output unchanged (no ICC embedding).
|
|
51
|
+
- [x] Graceful fallback when no source available (XMP only).
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# TODO PDF 82: Table cell text rendering
|
|
2
|
+
|
|
3
|
+
## Status: DONE (basic; full schema-faithful Cell model deferred)
|
|
4
|
+
|
|
5
|
+
## What was implemented
|
|
6
|
+
|
|
7
|
+
`TableRenderer` now renders inline text inside each `<TableCell>`
|
|
8
|
+
in addition to the cell grid rectangles.
|
|
9
|
+
|
|
10
|
+
1. `Idml::Elements::TableCell` gains a `character_style_range`
|
|
11
|
+
collection (typed `Idml::Elements::CharacterStyleRange`).
|
|
12
|
+
2. `TableCell#text_content` concatenates the text content of all
|
|
13
|
+
inline CSRs.
|
|
14
|
+
3. `TableRenderer#render_cell_text` reads `cell.text_content`,
|
|
15
|
+
builds a single-run `{ text:, font:, size: }` array, and emits
|
|
16
|
+
`canvas.text_rich(runs, at: [cell_x + INSET, baseline])` per cell
|
|
17
|
+
that has text.
|
|
18
|
+
4. Default font size 10pt, 4pt inset — sensible defaults that match
|
|
19
|
+
typical IDML table styling.
|
|
20
|
+
|
|
21
|
+
## Schema divergence (deferred)
|
|
22
|
+
|
|
23
|
+
The IDML RNC names this element `<Cell>` (not `<TableCell>`) and
|
|
24
|
+
declares many more attributes (RowSpan, ColumnSpan, insets per edge,
|
|
25
|
+
stroke per edge, FillColor, etc.). The current model captures only
|
|
26
|
+
the renderer-immediate subset. Faithful Cell modeling per RNC is a
|
|
27
|
+
separate refactor:
|
|
28
|
+
|
|
29
|
+
- Rename `TableCell` → `Cell` (or add a `Cell` alias).
|
|
30
|
+
- Add the full attribute set per `Cell_Object` in
|
|
31
|
+
`reference-docs/schemas/package/Stories/Story.rnc`.
|
|
32
|
+
- Update `Table` model: schema says `<Cell>` and `<Row>` are
|
|
33
|
+
siblings (not nested `<TableRow><TableCell>`). Restructuring
|
|
34
|
+
breaks existing fixtures.
|
|
35
|
+
|
|
36
|
+
These schema corrections are tracked separately.
|
|
37
|
+
|
|
38
|
+
## Verification
|
|
39
|
+
|
|
40
|
+
- `lib/idml/elements/table_cell.rb` — character_style_range added,
|
|
41
|
+
`text_content` method.
|
|
42
|
+
- `lib/idml/render/renderers/table_renderer.rb:42` —
|
|
43
|
+
`render_cell_text` via `canvas.text_rich`.
|
|
44
|
+
- Existing `table_renderer_spec.rb` still passes (cells without
|
|
45
|
+
text render as before).
|
|
46
|
+
|
|
47
|
+
## Acceptance criteria
|
|
48
|
+
|
|
49
|
+
- [x] Cells with inline CSR children render the text.
|
|
50
|
+
- [x] Empty cells render as empty rectangles (previous behavior).
|
|
51
|
+
- [x] Text uses the frame's registered font (via context.font_ps_name).
|
|
52
|
+
- [ ] Per-cell font, color, alignment from CSR attributes (deferred).
|
|
53
|
+
- [ ] Schema-faithful `<Cell>` element with full attribute set
|
|
54
|
+
(deferred — separate refactor).
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# TODO PDF 83: Pipeline decomposition
|
|
2
|
+
|
|
3
|
+
## Status: DONE
|
|
4
|
+
|
|
5
|
+
## What was done
|
|
6
|
+
|
|
7
|
+
Extracted metadata assembly from `Pipeline` into a dedicated
|
|
8
|
+
`Render::MetadataBuilder` class. Pipeline now calls
|
|
9
|
+
`MetadataBuilder.new(@package).build` instead of inlining the XMP
|
|
10
|
+
parsing, date conversion, and defaults logic.
|
|
11
|
+
|
|
12
|
+
## Why
|
|
13
|
+
|
|
14
|
+
Pipeline had five private methods dedicated to metadata
|
|
15
|
+
(`combined_metadata`, `xmp_metadata`, `default_metadata`,
|
|
16
|
+
`pdf_date_string`, `pdf_date`). Each was a single responsibility
|
|
17
|
+
unrelated to the pipeline's core orchestration role. Moving them
|
|
18
|
+
into a focused class:
|
|
19
|
+
|
|
20
|
+
- Shrinks Pipeline by ~55 lines.
|
|
21
|
+
- Makes MetadataBuilder independently testable.
|
|
22
|
+
- Makes Pipeline's intent clearer: orchestrate, not parse.
|
|
23
|
+
- Lets future metadata sources (e.g., IDML document properties,
|
|
24
|
+
custom user metadata) plug in without touching Pipeline.
|
|
25
|
+
|
|
26
|
+
## Verification
|
|
27
|
+
|
|
28
|
+
- `lib/idml/render/metadata_builder.rb` — extracted class.
|
|
29
|
+
- `lib/idml/render/pipeline.rb:25` — single-call usage.
|
|
30
|
+
- `spec/idml/render/metadata_builder_spec.rb` — 5 specs covering
|
|
31
|
+
defaults, XMP extraction, and the no-XMP fallback.
|
|
32
|
+
|
|
33
|
+
## Acceptance criteria
|
|
34
|
+
|
|
35
|
+
- [x] MetadataBuilder extracted as a separate class.
|
|
36
|
+
- [x] Pipeline no longer contains XMP/date helpers.
|
|
37
|
+
- [x] Existing pipeline specs still pass.
|
|
38
|
+
- [x] MetadataBuilder has dedicated specs.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Idml
|
|
4
|
+
module Elements
|
|
5
|
+
# `<Bookmark>` — IDML bookmark element. References a
|
|
6
|
+
# `<HyperlinkPageDestination>` (or similar) by Self via the
|
|
7
|
+
# `Destination` attribute. The destination carries the actual
|
|
8
|
+
# page+viewpoint target.
|
|
9
|
+
#
|
|
10
|
+
# Attributes per `Bookmark_Object` in
|
|
11
|
+
# `reference-docs/schemas/package/designmap.rnc`.
|
|
12
|
+
class Bookmark < Lutaml::Model::Serializable
|
|
13
|
+
attribute :self_attr, :string
|
|
14
|
+
attribute :name, :string
|
|
15
|
+
attribute :destination, :string
|
|
16
|
+
|
|
17
|
+
xml do
|
|
18
|
+
root "Bookmark"
|
|
19
|
+
map_attribute "Self", to: :self_attr
|
|
20
|
+
map_attribute "Name", to: :name
|
|
21
|
+
map_attribute "Destination", to: :destination
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -296,6 +296,8 @@ module Idml
|
|
|
296
296
|
attribute :character_style_range, Idml::Elements::CharacterStyleRange,
|
|
297
297
|
collection: true
|
|
298
298
|
attribute :content, Idml::Elements::Content, collection: true
|
|
299
|
+
attribute :hyperlink_text_source, Idml::Elements::HyperlinkTextSource,
|
|
300
|
+
collection: true
|
|
299
301
|
attribute :xml_element, Idml::Elements::XmlElement, collection: true
|
|
300
302
|
|
|
301
303
|
xml do
|
|
@@ -649,6 +651,7 @@ module Idml
|
|
|
649
651
|
map_attribute "UpperLevel", to: :upper_level
|
|
650
652
|
map_element "CharacterStyleRange", to: :character_style_range
|
|
651
653
|
map_element "Content", to: :content
|
|
654
|
+
map_element "HyperlinkTextSource", to: :hyperlink_text_source
|
|
652
655
|
map_element "XMLElement", to: :xml_element
|
|
653
656
|
end
|
|
654
657
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Idml
|
|
4
|
+
module Elements
|
|
5
|
+
# `<Hyperlink>` — connects a hyperlink source to a destination.
|
|
6
|
+
# Per `Hyperlink_Object` in
|
|
7
|
+
# `reference-docs/schemas/package/designmap.rnc`.
|
|
8
|
+
class Hyperlink < Lutaml::Model::Serializable
|
|
9
|
+
attribute :self_attr, :string
|
|
10
|
+
attribute :name, :string
|
|
11
|
+
attribute :source, :string
|
|
12
|
+
attribute :destination, :string
|
|
13
|
+
attribute :visible, :boolean
|
|
14
|
+
attribute :hidden, :boolean
|
|
15
|
+
|
|
16
|
+
xml do
|
|
17
|
+
root "Hyperlink"
|
|
18
|
+
map_attribute "Self", to: :self_attr
|
|
19
|
+
map_attribute "Name", to: :name
|
|
20
|
+
map_attribute "Source", to: :source
|
|
21
|
+
map_attribute "Destination", to: :destination
|
|
22
|
+
map_attribute "Visible", to: :visible
|
|
23
|
+
map_attribute "Hidden", to: :hidden
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Idml
|
|
4
|
+
module Elements
|
|
5
|
+
# `<HyperlinkPageDestination>` — target of an IDML hyperlink or
|
|
6
|
+
# bookmark. The `DestinationPage` attribute references a Spread
|
|
7
|
+
# Page by Self, which the renderer maps to a PDF page index.
|
|
8
|
+
#
|
|
9
|
+
# Attributes per `HyperlinkPageDestination_Object` in
|
|
10
|
+
# `reference-docs/schemas/package/designmap.rnc`.
|
|
11
|
+
class HyperlinkPageDestination < Lutaml::Model::Serializable
|
|
12
|
+
attribute :self_attr, :string
|
|
13
|
+
attribute :name, :string
|
|
14
|
+
attribute :name_manually, :boolean
|
|
15
|
+
attribute :destination_page, :string
|
|
16
|
+
attribute :view_setting, :string
|
|
17
|
+
attribute :view_percentage, :float
|
|
18
|
+
attribute :hidden, :boolean
|
|
19
|
+
attribute :destination_unique_key, :integer
|
|
20
|
+
|
|
21
|
+
xml do
|
|
22
|
+
root "HyperlinkPageDestination"
|
|
23
|
+
map_attribute "Self", to: :self_attr
|
|
24
|
+
map_attribute "Name", to: :name
|
|
25
|
+
map_attribute "NameManually", to: :name_manually
|
|
26
|
+
map_attribute "DestinationPage", to: :destination_page
|
|
27
|
+
map_attribute "ViewSetting", to: :view_setting
|
|
28
|
+
map_attribute "ViewPercentage", to: :view_percentage
|
|
29
|
+
map_attribute "Hidden", to: :hidden
|
|
30
|
+
map_attribute "DestinationUniqueKey", to: :destination_unique_key
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Idml
|
|
4
|
+
module Elements
|
|
5
|
+
# `<HyperlinkTextSource>` — source anchor of a hyperlink in a
|
|
6
|
+
# story. Per `HyperlinkTextSource_Object` in
|
|
7
|
+
# `reference-docs/schemas/package/Stories/Story.rnc`. The `Self`
|
|
8
|
+
# attribute is referenced by `<Hyperlink Source="...">` in the
|
|
9
|
+
# document designmap.
|
|
10
|
+
#
|
|
11
|
+
# Note on text range: IDML's HyperlinkTextSource wraps the
|
|
12
|
+
# character content it covers (it is a sibling of `<Content>`
|
|
13
|
+
# inside a `<CharacterStyleRange>`). The wrapper's position in
|
|
14
|
+
# the story flow determines the range — there is no explicit
|
|
15
|
+
# `StartIndex`/`EndIndex` on the element itself.
|
|
16
|
+
class HyperlinkTextSource < Lutaml::Model::Serializable
|
|
17
|
+
attribute :self_attr, :string
|
|
18
|
+
attribute :name, :string
|
|
19
|
+
attribute :hidden, :boolean
|
|
20
|
+
attribute :applied_character_style, :string
|
|
21
|
+
|
|
22
|
+
xml do
|
|
23
|
+
root "HyperlinkTextSource"
|
|
24
|
+
map_attribute "Self", to: :self_attr
|
|
25
|
+
map_attribute "Name", to: :name
|
|
26
|
+
map_attribute "Hidden", to: :hidden
|
|
27
|
+
map_attribute "AppliedCharacterStyle", to: :applied_character_style
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Idml
|
|
4
|
+
module Elements
|
|
5
|
+
# `<HyperlinkURLDestination>` — URL target of a hyperlink.
|
|
6
|
+
# Per `HyperlinkURLDestination_Object` in
|
|
7
|
+
# `reference-docs/schemas/package/designmap.rnc`.
|
|
8
|
+
class HyperlinkURLDestination < Lutaml::Model::Serializable
|
|
9
|
+
attribute :self_attr, :string
|
|
10
|
+
attribute :name, :string
|
|
11
|
+
attribute :destination_url, :string
|
|
12
|
+
attribute :hidden, :boolean
|
|
13
|
+
attribute :destination_unique_key, :integer
|
|
14
|
+
|
|
15
|
+
xml do
|
|
16
|
+
root "HyperlinkURLDestination"
|
|
17
|
+
map_attribute "Self", to: :self_attr
|
|
18
|
+
map_attribute "Name", to: :name
|
|
19
|
+
map_attribute "DestinationURL", to: :destination_url
|
|
20
|
+
map_attribute "Hidden", to: :hidden
|
|
21
|
+
map_attribute "DestinationUniqueKey", to: :destination_unique_key
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|