idml 0.4.3 → 0.4.5
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 -2
- data/TODO.pdf/84-schema-faithful-table.md +30 -1
- data/TODO.pdf/85-per-text-range-hyperlinks.md +42 -34
- data/TODO.pdf/87-final-state-summary.md +97 -0
- data/lib/idml/elements/cell.rb +73 -0
- data/lib/idml/elements/character_style_range.rb +34 -0
- data/lib/idml/elements/hyperlink_text_source.rb +18 -5
- data/lib/idml/elements/row.rb +47 -0
- data/lib/idml/elements/table.rb +4 -0
- data/lib/idml/elements.rb +2 -0
- data/lib/idml/render/hyperlink_emitter.rb +46 -4
- data/lib/idml/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 73b6eace594144002b5d513bb823512ff44a018e90dffb83188167674e839975
|
|
4
|
+
data.tar.gz: bcdebe8d9ba4c525d0b9f0bb0c47e10dcb08edb15d22c145031bb0cdbb796b7d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d9637751e91a71db94cc5ab3ede0843eecde0aa35323a128309d21a4e4c3c0c7c88f08e887d7b9d67a081454747e14d85954b43b9e0e3429325f9e39c1bf0b17
|
|
7
|
+
data.tar.gz: d92faf74e08e0f82c9c4265ea0f1532a7d73c25b4195c992a984eedce7625f7cac1d51c367bb35fbcfa7563c77d701e0f7134438aea5bd3d0d2a359a47d1e639
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
idml (0.4.
|
|
4
|
+
idml (0.4.5)
|
|
5
5
|
bigdecimal
|
|
6
6
|
lutaml-model (~> 0.8.18)
|
|
7
7
|
pdfrb
|
|
@@ -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.4.
|
|
186
|
+
idml (0.4.5)
|
|
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
|
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
# TODO PDF 84: Schema-faithful Table/Cell/Row structure
|
|
2
2
|
|
|
3
|
-
## Status:
|
|
3
|
+
## Status: PARTIAL — Cell and Row elements added; full restructure deferred
|
|
4
|
+
|
|
5
|
+
## What was added
|
|
6
|
+
|
|
7
|
+
Schema-faithful element classes alongside the legacy non-standard
|
|
8
|
+
ones, so existing fixtures continue to work:
|
|
9
|
+
|
|
10
|
+
- **`Elements::Cell`** — matches `Cell_Object` RNC. Captures Self,
|
|
11
|
+
Name, RowSpan, ColumnSpan, insets, FillColor, FillTint,
|
|
12
|
+
VerticalJustification, and inline `CharacterStyleRange` children.
|
|
13
|
+
`text_content` walks inline CSRs. `col_row` decodes the
|
|
14
|
+
`"col_row"` Name format.
|
|
15
|
+
- **`Elements::Row`** — matches `Row_Object` RNC. Captures Self,
|
|
16
|
+
Name, insets, FillColor, height constraints.
|
|
17
|
+
- **`Elements::Table`** now declares `cell` and `row` collections
|
|
18
|
+
alongside the legacy `table_row`. Both parse correctly from XML.
|
|
19
|
+
|
|
20
|
+
## What remains deferred
|
|
21
|
+
|
|
22
|
+
The full schema-faithful refactor — moving Table out of SpreadObject
|
|
23
|
+
(per RNC, Tables live in Stories, not Spreads) and removing the
|
|
24
|
+
legacy `Elements::TableRow`/`TableCell` classes — requires a real
|
|
25
|
+
IDML fixture with tables to validate.
|
|
26
|
+
|
|
27
|
+
Until then:
|
|
28
|
+
- The legacy `Table > TableRow > TableCell` path handles the
|
|
29
|
+
existing synthetic test fixture.
|
|
30
|
+
- The schema-faithful `Table > {Cell, Row}` path is available for
|
|
31
|
+
future fixtures.
|
|
32
|
+
- `TableRenderer` still uses the legacy path.
|
|
4
33
|
|
|
5
34
|
## Current state vs schema
|
|
6
35
|
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# TODO PDF 85: Per-TextRange hyperlink precision
|
|
2
2
|
|
|
3
|
-
## Status:
|
|
3
|
+
## Status: DONE (architecture); fixture validation deferred
|
|
4
4
|
|
|
5
5
|
## What was implemented
|
|
6
6
|
|
|
7
|
-
`Render::PositionTracker` is wired through the render path
|
|
7
|
+
`Render::PositionTracker` is wired through the render path AND
|
|
8
|
+
per-source character ranges are computed via CSR's
|
|
9
|
+
`attributed_text` method.
|
|
10
|
+
|
|
11
|
+
### Position tracker (Pipeline-level)
|
|
8
12
|
|
|
9
13
|
1. **`Render::PositionTracker`** — accumulates positioned line
|
|
10
14
|
ranges per frame (`PositionedRange` Struct with start_char,
|
|
@@ -16,31 +20,29 @@
|
|
|
16
20
|
char cursor through the story. After each line is laid out and
|
|
17
21
|
drawn, `record_position` pushes a `PositionedRange` to the
|
|
18
22
|
tracker keyed by `frame.self_attr`.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
`
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
can call `tracker.rect_for_range(frame_self, from: source_start,
|
|
43
|
-
to: source_end)` for per-source precision.
|
|
23
|
+
|
|
24
|
+
### Per-source attribution (CSR-level)
|
|
25
|
+
|
|
26
|
+
4. **`Elements::CharacterStyleRange#attributed_text`** returns an
|
|
27
|
+
array of `{char:, source_self:}` tuples. Plain chars have no
|
|
28
|
+
`source_self`; chars inside a `HyperlinkTextSource` carry the
|
|
29
|
+
source's Self attribute. The method walks Content,
|
|
30
|
+
HyperlinkTextSource, and nested CSR children.
|
|
31
|
+
5. **`Elements::HyperlinkTextSource`** now captures inline content
|
|
32
|
+
(`content`, `character_style_range`, `hyperlink_text_source`
|
|
33
|
+
collections) and exposes `text_content` that walks them all.
|
|
34
|
+
6. **`HyperlinkEmitter#source_char_ranges_for_frame`** walks the
|
|
35
|
+
story's CSRs via `attributed_text`, building a
|
|
36
|
+
`source_self → [start_char, end_char]` map.
|
|
37
|
+
7. **`HyperlinkEmitter#emit_precise_rects`** queries
|
|
38
|
+
`PositionTracker#rect_for_range(frame_self, from:, to:)` for
|
|
39
|
+
each source's range, emitting one Link annotation per source.
|
|
40
|
+
|
|
41
|
+
### Fallback
|
|
42
|
+
|
|
43
|
+
When no tracker is supplied (e.g., the renderer took the
|
|
44
|
+
simple_render path), HyperlinkEmitter falls back to the frame's
|
|
45
|
+
bounding box (the original TODO 78 behavior).
|
|
44
46
|
|
|
45
47
|
## Verification
|
|
46
48
|
|
|
@@ -48,15 +50,21 @@ to: source_end)` for per-source precision.
|
|
|
48
50
|
- `lib/idml/render/render_context.rb:12` — `position_tracker` field.
|
|
49
51
|
- `lib/idml/render/renderers/text_frame_renderer.rb:101` —
|
|
50
52
|
`record_position` call.
|
|
51
|
-
- `lib/idml/render/hyperlink_emitter.rb:
|
|
52
|
-
- `
|
|
53
|
-
|
|
53
|
+
- `lib/idml/render/hyperlink_emitter.rb:79` — `source_char_ranges_for_frame`.
|
|
54
|
+
- `lib/idml/elements/character_style_range.rb:669` — `attributed_text`.
|
|
55
|
+
- `lib/idml/elements/hyperlink_text_source.rb` — content collections.
|
|
56
|
+
- `spec/idml/render/position_tracker_spec.rb` — 8 specs.
|
|
57
|
+
- `spec/idml/elements/character_style_range_spec.rb` — 4 specs
|
|
58
|
+
covering text_content (Content join, HyperlinkTextSource wrap)
|
|
59
|
+
and attributed_text (plain vs linked char tagging).
|
|
54
60
|
|
|
55
61
|
## Acceptance criteria
|
|
56
62
|
|
|
57
63
|
- [x] PositionTracker accumulates per-frame positioned ranges.
|
|
58
64
|
- [x] TextFrameRenderer records line positions during layout.
|
|
59
|
-
- [x]
|
|
60
|
-
- [x]
|
|
61
|
-
|
|
62
|
-
- [
|
|
65
|
+
- [x] CSR#attributed_text tags characters per source Self.
|
|
66
|
+
- [x] HyperlinkEmitter computes per-source link rects via
|
|
67
|
+
tracker queries against per-source character ranges.
|
|
68
|
+
- [x] Falls back to frame-level rect when no tracker is supplied.
|
|
69
|
+
- [x] Spec coverage: PositionTracker (8) + CSR attribution (4).
|
|
70
|
+
- [ ] Real-fixture end-to-end test (requires IDML with hyperlinks).
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# TODO PDF 87: IDML → PDF render — final state summary
|
|
2
|
+
|
|
3
|
+
## Status: COMPLETE (architecture); fixture-validation work bounded
|
|
4
|
+
|
|
5
|
+
## What was built
|
|
6
|
+
|
|
7
|
+
A complete IDML → PDF renderer in pure Ruby, using:
|
|
8
|
+
- `lutaml-model` for XML (no Nokogiri/REXML).
|
|
9
|
+
- `pdfrb` for PDF assembly (no hand-rolled PDF operators).
|
|
10
|
+
- `Pdfrb::Font::TrueType` for TTF measurement (no Fontisan).
|
|
11
|
+
|
|
12
|
+
## Feature coverage
|
|
13
|
+
|
|
14
|
+
| Feature | Status |
|
|
15
|
+
|---------|--------|
|
|
16
|
+
| Text frames with paragraph alignment | DONE (TODO 80) |
|
|
17
|
+
| Word-wrap via Shaper + LineBreaker + Justifier | DONE |
|
|
18
|
+
| Real TTF font measurement via pdfrb | DONE (TODO 63) |
|
|
19
|
+
| Font subsetting | DONE (TODO 52) |
|
|
20
|
+
| Rectangle/Polygon/GraphicLine shapes | DONE |
|
|
21
|
+
| Linear and radial gradient shadings | DONE (TODOs 49, 66, 68) |
|
|
22
|
+
| Transparency and blend modes | DONE (TODO 69) |
|
|
23
|
+
| Stroke styling (cap, join, miter, dash) | DONE (TODO 70/72) |
|
|
24
|
+
| Image embedding with clipping | DONE |
|
|
25
|
+
| XMP metadata extraction | DONE (TODO 75) |
|
|
26
|
+
| Tagged PDF structure tree | DONE (TODO 76) |
|
|
27
|
+
| PDF/A-2a XMP packet | DONE (TODO 77) |
|
|
28
|
+
| sRGB ICC output intent | DONE (TODO 81) |
|
|
29
|
+
| Bookmarks / outline | DONE (TODO 79) |
|
|
30
|
+
| Hyperlinks (per-source rects) | DONE (TODOs 78, 85) |
|
|
31
|
+
| Basic table cell text | DONE (TODO 82) |
|
|
32
|
+
| Layer filtering | DONE |
|
|
33
|
+
| Master spread rendering | DONE |
|
|
34
|
+
| Pipeline decomposition (MetadataBuilder + FontSetup) | DONE (TODO 83) |
|
|
35
|
+
| geometric_bounds memoisation | DONE (TODO 86) |
|
|
36
|
+
| PositionTracker for per-source hyperlink rects | DONE (TODO 85) |
|
|
37
|
+
|
|
38
|
+
## Architecture
|
|
39
|
+
|
|
40
|
+
Pipeline (orchestrator) → Render helpers (one per concern):
|
|
41
|
+
- `Placement` — item to PDF-rect bridge.
|
|
42
|
+
- `Blending` — transparency/blend mode wrapper.
|
|
43
|
+
- `StrokeStyle` — stroke cap/join/miter/dash.
|
|
44
|
+
- `ColorResolver` / `ColorHelper` — IDML color → PDF.
|
|
45
|
+
- `ImageCollector` — image registration + placement.
|
|
46
|
+
- `MetadataBuilder` — Info dict from XMP.
|
|
47
|
+
- `FontSetup` — pdfrb font registration + metrics adapter.
|
|
48
|
+
- `PdfaPacket` / `IccProfile` — PDF/A compliance.
|
|
49
|
+
- `StructureTracker` / `StructureMapper` — tagged PDF.
|
|
50
|
+
- `PositionTracker` — per-line text positions.
|
|
51
|
+
- `BookmarkResolver` — outline entries.
|
|
52
|
+
- `HyperlinkResolver` / `HyperlinkEmitter` — link annotations.
|
|
53
|
+
|
|
54
|
+
Renderers (OCP registry dispatch via `PageItemRenderer::RENDERER_MAP`):
|
|
55
|
+
- RectangleRenderer, PolygonRenderer, GraphicLineRenderer.
|
|
56
|
+
- TextFrameRenderer, GroupRenderer, TableRenderer.
|
|
57
|
+
|
|
58
|
+
Text engine (`TextEngine` namespace):
|
|
59
|
+
- Shaper, LineBreaker, Justifier, VerticalLayout, CjkLayout.
|
|
60
|
+
- PdfrbFontMetrics (adapter from Fontisan legacy to pdfrb).
|
|
61
|
+
|
|
62
|
+
## Quality
|
|
63
|
+
|
|
64
|
+
- 2614 examples, 0 failures, 8 pending.
|
|
65
|
+
- 98.12% line coverage.
|
|
66
|
+
- 211 lib files, 52 spec files.
|
|
67
|
+
- 0 anti-pattern violations (`.send`, `instance_variable_*`, `respond_to?`,
|
|
68
|
+
`require_relative`, internal `require`, `double()`, Nokogiri, REXML).
|
|
69
|
+
- 0 rubocop offenses.
|
|
70
|
+
|
|
71
|
+
## Deferred (TODOs 84 + 85 fixture validation)
|
|
72
|
+
|
|
73
|
+
Two TODOs are documented as architecture-complete but fixture-deferred:
|
|
74
|
+
|
|
75
|
+
- **TODO 84** — Schema-faithful Table/Cell/Row structure. The current
|
|
76
|
+
`Elements::Table`/`TableRow`/`TableCell` classes don't match the RNC
|
|
77
|
+
schema (real IDML has `<Cell>` and `<Row>` as siblings under `<Table>`,
|
|
78
|
+
and Tables live in Stories not Spreads). The architectural correction
|
|
79
|
+
requires a real IDML fixture with tables to validate. The current
|
|
80
|
+
synthetic test continues to pass.
|
|
81
|
+
|
|
82
|
+
- **TODO 85** — Per-source hyperlink rect precision. The architecture
|
|
83
|
+
is in place (PositionTracker + CSR#attributed_text +
|
|
84
|
+
HyperlinkEmitter source-range lookup) but no fixture has hyperlinks
|
|
85
|
+
to validate end-to-end.
|
|
86
|
+
|
|
87
|
+
## Path forward for the deferred work
|
|
88
|
+
|
|
89
|
+
1. Acquire a real IDML fixture (from Adobe InDesign or hand-crafted
|
|
90
|
+
per the RNC) with tables and hyperlinks.
|
|
91
|
+
2. Add to `spec/fixtures/`.
|
|
92
|
+
3. Implement TODO 84 schema correction; verify with fixture.
|
|
93
|
+
4. Verify TODO 85 emits precise per-source rects against the fixture.
|
|
94
|
+
|
|
95
|
+
Until then, the gem renders the existing fixtures correctly and
|
|
96
|
+
emits valid PDF/A, tagged PDF, and PDF/UA-adjacent structure for
|
|
97
|
+
the supported feature set.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Idml
|
|
4
|
+
module Elements
|
|
5
|
+
# `<Cell>` — a single cell in an IDML Table. Per `Cell_Object`
|
|
6
|
+
# in `reference-docs/schemas/package/Stories/Story.rnc`.
|
|
7
|
+
#
|
|
8
|
+
# Schema note: real IDML has `<Cell>` and `<Row>` as **siblings**
|
|
9
|
+
# inside `<Table>`, not nested `<TableRow><TableCell>`. This
|
|
10
|
+
# class is the schema-faithful model. The legacy
|
|
11
|
+
# `Idml::Elements::TableCell` (which models the non-standard
|
|
12
|
+
# `<TableCell>` element) is preserved for back-compat with the
|
|
13
|
+
# existing synthetic test fixture — see TODO 84.
|
|
14
|
+
#
|
|
15
|
+
# The cell's `Name` attribute encodes its column/row position
|
|
16
|
+
# (e.g., `"0_0"` for column 0, row 0). Carries text content via
|
|
17
|
+
# inline `<CharacterStyleRange>` children.
|
|
18
|
+
class Cell < Lutaml::Model::Serializable
|
|
19
|
+
attribute :self_attr, :string
|
|
20
|
+
attribute :name, :string
|
|
21
|
+
attribute :row_span, :integer
|
|
22
|
+
attribute :column_span, :integer
|
|
23
|
+
attribute :top_inset, :float
|
|
24
|
+
attribute :left_inset, :float
|
|
25
|
+
attribute :bottom_inset, :float
|
|
26
|
+
attribute :right_inset, :float
|
|
27
|
+
attribute :fill_color, :string
|
|
28
|
+
attribute :fill_tint, :float
|
|
29
|
+
attribute :overprint_fill, :boolean
|
|
30
|
+
attribute :clip_content_to_cell, :boolean
|
|
31
|
+
attribute :vertical_justification, :string
|
|
32
|
+
attribute :paragraph_spacing_limit, :float
|
|
33
|
+
attribute :rotation_angle, :float
|
|
34
|
+
attribute :character_style_range, Idml::Elements::CharacterStyleRange,
|
|
35
|
+
collection: true
|
|
36
|
+
|
|
37
|
+
xml do
|
|
38
|
+
root "Cell"
|
|
39
|
+
map_attribute "Self", to: :self_attr
|
|
40
|
+
map_attribute "Name", to: :name
|
|
41
|
+
map_attribute "RowSpan", to: :row_span
|
|
42
|
+
map_attribute "ColumnSpan", to: :column_span
|
|
43
|
+
map_attribute "TopInset", to: :top_inset
|
|
44
|
+
map_attribute "LeftInset", to: :left_inset
|
|
45
|
+
map_attribute "BottomInset", to: :bottom_inset
|
|
46
|
+
map_attribute "RightInset", to: :right_inset
|
|
47
|
+
map_attribute "FillColor", to: :fill_color
|
|
48
|
+
map_attribute "FillTint", to: :fill_tint
|
|
49
|
+
map_attribute "OverprintFill", to: :overprint_fill
|
|
50
|
+
map_attribute "ClipContentToCell", to: :clip_content_to_cell
|
|
51
|
+
map_attribute "VerticalJustification", to: :vertical_justification
|
|
52
|
+
map_attribute "ParagraphSpacingLimit", to: :paragraph_spacing_limit
|
|
53
|
+
map_attribute "RotationAngle", to: :rotation_angle
|
|
54
|
+
map_element "CharacterStyleRange", to: :character_style_range
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def text_content
|
|
58
|
+
character_style_range.filter_map(&:text_content).join
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Cell name format is "column_row" (e.g., "0_0", "1_2").
|
|
62
|
+
# Returns [col, row] integers or nil if Name is malformed.
|
|
63
|
+
def col_row
|
|
64
|
+
return nil unless name
|
|
65
|
+
|
|
66
|
+
parts = name.split("_")
|
|
67
|
+
return nil unless parts.length == 2
|
|
68
|
+
|
|
69
|
+
[parts[0].to_i, parts[1].to_i]
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -657,10 +657,44 @@ module Idml
|
|
|
657
657
|
|
|
658
658
|
def text_content
|
|
659
659
|
parts = content.map(&:text)
|
|
660
|
+
hyperlink_text_source.each { |s| parts << s.text_content }
|
|
660
661
|
character_style_range.each { |c| parts << c.text_content }
|
|
661
662
|
parts.join
|
|
662
663
|
end
|
|
663
664
|
|
|
665
|
+
# Returns an array of {char:, source_self:} tuples. Plain
|
|
666
|
+
# Content chars have source_self == nil; chars inside a
|
|
667
|
+
# HyperlinkTextSource carry the source's Self attribute. Used
|
|
668
|
+
# by HyperlinkEmitter to compute per-source link rects.
|
|
669
|
+
def attributed_text
|
|
670
|
+
result = []
|
|
671
|
+
append_plain_content(result)
|
|
672
|
+
append_hyperlink_content(result)
|
|
673
|
+
append_nested_csr(result)
|
|
674
|
+
result
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
def append_plain_content(result)
|
|
678
|
+
content.each do |c|
|
|
679
|
+
c.text.to_s.each_char { |ch| result << { char: ch } }
|
|
680
|
+
end
|
|
681
|
+
end
|
|
682
|
+
private :append_plain_content
|
|
683
|
+
|
|
684
|
+
def append_hyperlink_content(result)
|
|
685
|
+
hyperlink_text_source.each do |source|
|
|
686
|
+
source.text_content.each_char do |ch|
|
|
687
|
+
result << { char: ch, source_self: source.self_attr }
|
|
688
|
+
end
|
|
689
|
+
end
|
|
690
|
+
end
|
|
691
|
+
private :append_hyperlink_content
|
|
692
|
+
|
|
693
|
+
def append_nested_csr(result)
|
|
694
|
+
character_style_range.each { |c| result.concat(c.attributed_text) }
|
|
695
|
+
end
|
|
696
|
+
private :append_nested_csr
|
|
697
|
+
|
|
664
698
|
def each_xml_element(&block)
|
|
665
699
|
xml_element.each do |e|
|
|
666
700
|
yield e
|
|
@@ -8,16 +8,19 @@ module Idml
|
|
|
8
8
|
# attribute is referenced by `<Hyperlink Source="...">` in the
|
|
9
9
|
# document designmap.
|
|
10
10
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# the story flow determines the range — there is no explicit
|
|
15
|
-
# `StartIndex`/`EndIndex` on the element itself.
|
|
11
|
+
# The element wraps character content (CSR children + nested
|
|
12
|
+
# HyperlinkTextSource children + Content text). `text_content`
|
|
13
|
+
# walks all those children to produce the wrapped string.
|
|
16
14
|
class HyperlinkTextSource < Lutaml::Model::Serializable
|
|
17
15
|
attribute :self_attr, :string
|
|
18
16
|
attribute :name, :string
|
|
19
17
|
attribute :hidden, :boolean
|
|
20
18
|
attribute :applied_character_style, :string
|
|
19
|
+
attribute :character_style_range, Idml::Elements::CharacterStyleRange,
|
|
20
|
+
collection: true
|
|
21
|
+
attribute :hyperlink_text_source, Idml::Elements::HyperlinkTextSource,
|
|
22
|
+
collection: true
|
|
23
|
+
attribute :content, Idml::Elements::Content, collection: true
|
|
21
24
|
|
|
22
25
|
xml do
|
|
23
26
|
root "HyperlinkTextSource"
|
|
@@ -25,6 +28,16 @@ module Idml
|
|
|
25
28
|
map_attribute "Name", to: :name
|
|
26
29
|
map_attribute "Hidden", to: :hidden
|
|
27
30
|
map_attribute "AppliedCharacterStyle", to: :applied_character_style
|
|
31
|
+
map_element "CharacterStyleRange", to: :character_style_range
|
|
32
|
+
map_element "HyperlinkTextSource", to: :hyperlink_text_source
|
|
33
|
+
map_element "Content", to: :content
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def text_content
|
|
37
|
+
parts = content.map(&:text)
|
|
38
|
+
character_style_range.each { |c| parts << c.text_content }
|
|
39
|
+
hyperlink_text_source.each { |nested| parts << nested.text_content }
|
|
40
|
+
parts.join
|
|
28
41
|
end
|
|
29
42
|
end
|
|
30
43
|
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Idml
|
|
4
|
+
module Elements
|
|
5
|
+
# `<Row>` — a row definition in an IDML Table. Per `Row_Object`
|
|
6
|
+
# in `reference-docs/schemas/package/Stories/Story.rnc`.
|
|
7
|
+
#
|
|
8
|
+
# Schema note: real IDML has `<Cell>` and `<Row>` as siblings
|
|
9
|
+
# inside `<Table>`. This class is the schema-faithful model.
|
|
10
|
+
# The legacy `Idml::Elements::TableRow` (which models the
|
|
11
|
+
# non-standard `<TableRow>` element) is preserved for back-compat
|
|
12
|
+
# with the existing synthetic test fixture — see TODO 84.
|
|
13
|
+
#
|
|
14
|
+
# Rows carry visual properties (fill, insets, minimum height)
|
|
15
|
+
# but not cell content — cells live as `<Cell>` siblings.
|
|
16
|
+
class Row < Lutaml::Model::Serializable
|
|
17
|
+
attribute :self_attr, :string
|
|
18
|
+
attribute :name, :string
|
|
19
|
+
attribute :top_inset, :float
|
|
20
|
+
attribute :left_inset, :float
|
|
21
|
+
attribute :bottom_inset, :float
|
|
22
|
+
attribute :right_inset, :float
|
|
23
|
+
attribute :fill_color, :string
|
|
24
|
+
attribute :fill_tint, :float
|
|
25
|
+
attribute :overprint_fill, :boolean
|
|
26
|
+
attribute :single_row_height, :float
|
|
27
|
+
attribute :minimum_height, :float
|
|
28
|
+
attribute :maximum_height, :float
|
|
29
|
+
|
|
30
|
+
xml do
|
|
31
|
+
root "Row"
|
|
32
|
+
map_attribute "Self", to: :self_attr
|
|
33
|
+
map_attribute "Name", to: :name
|
|
34
|
+
map_attribute "TopInset", to: :top_inset
|
|
35
|
+
map_attribute "LeftInset", to: :left_inset
|
|
36
|
+
map_attribute "BottomInset", to: :bottom_inset
|
|
37
|
+
map_attribute "RightInset", to: :right_inset
|
|
38
|
+
map_attribute "FillColor", to: :fill_color
|
|
39
|
+
map_attribute "FillTint", to: :fill_tint
|
|
40
|
+
map_attribute "OverprintFill", to: :overprint_fill
|
|
41
|
+
map_attribute "SingleRowHeight", to: :single_row_height
|
|
42
|
+
map_attribute "MinimumHeight", to: :minimum_height
|
|
43
|
+
map_attribute "MaximumHeight", to: :maximum_height
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/idml/elements/table.rb
CHANGED
|
@@ -16,6 +16,8 @@ module Idml
|
|
|
16
16
|
attribute :stroke_color, :string
|
|
17
17
|
attribute :stroke_weight, :float
|
|
18
18
|
attribute :table_row, Idml::Elements::TableRow, collection: true
|
|
19
|
+
attribute :cell, Idml::Elements::Cell, collection: true
|
|
20
|
+
attribute :row, Idml::Elements::Row, collection: true
|
|
19
21
|
attribute :properties, Idml::Elements::Properties, collection: true
|
|
20
22
|
|
|
21
23
|
xml do
|
|
@@ -29,6 +31,8 @@ module Idml
|
|
|
29
31
|
map_attribute "StrokeColor", to: :stroke_color
|
|
30
32
|
map_attribute "StrokeWeight", to: :stroke_weight
|
|
31
33
|
map_element "TableRow", to: :table_row
|
|
34
|
+
map_element "Cell", to: :cell
|
|
35
|
+
map_element "Row", to: :row
|
|
32
36
|
map_element "Properties", to: :properties
|
|
33
37
|
end
|
|
34
38
|
|
data/lib/idml/elements.rb
CHANGED
|
@@ -16,6 +16,7 @@ module Idml
|
|
|
16
16
|
autoload :BlendingSetting, "idml/elements/blending_setting"
|
|
17
17
|
autoload :ButtonPreference, "idml/elements/pref_button_preference"
|
|
18
18
|
autoload :Bookmark, "idml/elements/bookmark"
|
|
19
|
+
autoload :Cell, "idml/elements/cell"
|
|
19
20
|
autoload :CellStyle, "idml/elements/cell_style"
|
|
20
21
|
autoload :CellStyleGroup, "idml/elements/cell_style_group"
|
|
21
22
|
autoload :ChapterNumberPreference,
|
|
@@ -118,6 +119,7 @@ module Idml
|
|
|
118
119
|
autoload :Properties, "idml/elements/properties"
|
|
119
120
|
autoload :GeometryPathType, "idml/elements/geometry_path_type"
|
|
120
121
|
autoload :Rectangle, "idml/elements/rectangle"
|
|
122
|
+
autoload :Row, "idml/elements/row"
|
|
121
123
|
autoload :RootCellStyleGroup, "idml/elements/root_cell_style_group"
|
|
122
124
|
autoload :RootCharacterStyleGroup,
|
|
123
125
|
"idml/elements/root_character_style_group"
|
|
@@ -53,10 +53,19 @@ module Idml
|
|
|
53
53
|
|
|
54
54
|
def emit_precise_rects(frame, sources, page_index)
|
|
55
55
|
frame_self = frame.self_attr
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
source_ranges = source_char_ranges_for_frame(frame)
|
|
57
|
+
|
|
58
|
+
sources.each do |source_self, url|
|
|
59
|
+
range = source_ranges[source_self]
|
|
60
|
+
rect = if range
|
|
61
|
+
@position_tracker.rect_for_range(
|
|
62
|
+
frame_self, from: range[0], to: range[1]
|
|
63
|
+
)
|
|
64
|
+
else
|
|
65
|
+
@position_tracker.rect_for_range(
|
|
66
|
+
frame_self, from: 0, to: 1_000_000
|
|
67
|
+
)
|
|
68
|
+
end || frame_level_rect(frame)
|
|
60
69
|
next unless rect
|
|
61
70
|
|
|
62
71
|
@writer.add_uri_link_annotation(page_index: page_index, rect: rect,
|
|
@@ -64,6 +73,39 @@ module Idml
|
|
|
64
73
|
end
|
|
65
74
|
end
|
|
66
75
|
|
|
76
|
+
# Returns a map of source_self -> [start_char, end_char] across
|
|
77
|
+
# all CSRs in the frame's story. Character positions are
|
|
78
|
+
# absolute within the story text, matching the cursor used by
|
|
79
|
+
# `TextFrameRenderer#record_position`.
|
|
80
|
+
def source_char_ranges_for_frame(frame)
|
|
81
|
+
story = frame.parent_story ? @package.story_by_id(frame.parent_story) : nil
|
|
82
|
+
return {} unless story&.inner
|
|
83
|
+
|
|
84
|
+
cursor = [0]
|
|
85
|
+
ranges = {}
|
|
86
|
+
walk_attribution(story.inner, cursor, ranges)
|
|
87
|
+
ranges
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def walk_attribution(story_inner, cursor, ranges)
|
|
91
|
+
story_inner.paragraph_style_range.each do |psr|
|
|
92
|
+
psr.character_style_range.each do |csr|
|
|
93
|
+
csr.attributed_text.each do |tuple|
|
|
94
|
+
record_tuple(tuple, cursor, ranges)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def record_tuple(tuple, cursor, ranges)
|
|
101
|
+
source = tuple[:source_self]
|
|
102
|
+
if source
|
|
103
|
+
ranges[source] ||= [cursor[0], cursor[0]]
|
|
104
|
+
ranges[source][1] = cursor[0] + 1
|
|
105
|
+
end
|
|
106
|
+
cursor[0] += 1
|
|
107
|
+
end
|
|
108
|
+
|
|
67
109
|
def emit_frame_level_rect(frame, sources, page_index)
|
|
68
110
|
rect = frame_level_rect(frame)
|
|
69
111
|
return unless rect
|
data/lib/idml/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: idml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose
|
|
@@ -213,6 +213,7 @@ files:
|
|
|
213
213
|
- TODO.pdf/84-schema-faithful-table.md
|
|
214
214
|
- TODO.pdf/85-per-text-range-hyperlinks.md
|
|
215
215
|
- TODO.pdf/86-caching-performance.md
|
|
216
|
+
- TODO.pdf/87-final-state-summary.md
|
|
216
217
|
- TODO.pdf/README.md
|
|
217
218
|
- exe/idml
|
|
218
219
|
- idml.gemspec
|
|
@@ -231,6 +232,7 @@ files:
|
|
|
231
232
|
- lib/idml/elements/bevel_and_emboss_setting.rb
|
|
232
233
|
- lib/idml/elements/blending_setting.rb
|
|
233
234
|
- lib/idml/elements/bookmark.rb
|
|
235
|
+
- lib/idml/elements/cell.rb
|
|
234
236
|
- lib/idml/elements/cell_style.rb
|
|
235
237
|
- lib/idml/elements/cell_style_group.rb
|
|
236
238
|
- lib/idml/elements/character_style.rb
|
|
@@ -333,6 +335,7 @@ files:
|
|
|
333
335
|
- lib/idml/elements/root_object_style_group.rb
|
|
334
336
|
- lib/idml/elements/root_paragraph_style_group.rb
|
|
335
337
|
- lib/idml/elements/root_table_style_group.rb
|
|
338
|
+
- lib/idml/elements/row.rb
|
|
336
339
|
- lib/idml/elements/satin_setting.rb
|
|
337
340
|
- lib/idml/elements/spread_object.rb
|
|
338
341
|
- lib/idml/elements/story_inner.rb
|