idml 0.4.1 → 0.4.2
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/83-pipeline-decomposition.md +38 -22
- data/TODO.pdf/84-schema-faithful-table.md +80 -0
- data/TODO.pdf/85-per-text-range-hyperlinks.md +93 -0
- data/TODO.pdf/86-caching-performance.md +71 -0
- data/lib/idml/render/font_setup.rb +77 -0
- data/lib/idml/render/pipeline.rb +31 -69
- data/lib/idml/render.rb +1 -0
- data/lib/idml/version.rb +1 -1
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 79791db7118dac992a6dbfc780f143bc545cc1d6b7c408fac91fe9eb75de8d32
|
|
4
|
+
data.tar.gz: f98eb3f41100e1b7f62c51511f33aa774a5272e14fb4bcc7012dff50c88336d3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 34b95aeea5bf217fec6a88f8ae7a8a03c399001c0f4735bc11b0485afe27dbf9012fd481c8e6005f68f17fcc2bf850a63d03a3d23249bd570a57739ab92af1ed
|
|
7
|
+
data.tar.gz: 1f462441786c0a807f3bb0ddb01ff0c1c4a2e9a8e736fbd9e18b7666c2fe1c57c917dae3770b051ec716a25f4202cba5eef86a96bdca9630263ff5f385527367
|
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.2)
|
|
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.2)
|
|
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
|
|
@@ -4,35 +4,51 @@
|
|
|
4
4
|
|
|
5
5
|
## What was done
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
Two extractions from `Pipeline` into dedicated classes:
|
|
8
|
+
|
|
9
|
+
1. **`Render::MetadataBuilder`** — metadata assembly (combined
|
|
10
|
+
defaults + XMP-extracted fields, ISO 8601 → PDF date conversion).
|
|
11
|
+
2. **`Render::FontSetup`** — document font resolution via
|
|
12
|
+
`Pdfrb::FontResolver`, registration with pdfrb, and
|
|
13
|
+
`PdfrbFontMetrics` adapter construction.
|
|
14
|
+
|
|
15
|
+
Pipeline now does pure orchestration: build writer → set metadata →
|
|
16
|
+
apply compliance → set up structure → resolve font → render spreads
|
|
17
|
+
→ flush structure → emit bookmarks → subset → write. No parsing,
|
|
18
|
+
no date math, no font-path lookup.
|
|
11
19
|
|
|
12
20
|
## Why
|
|
13
21
|
|
|
14
|
-
Pipeline had
|
|
15
|
-
|
|
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:
|
|
22
|
+
Pipeline had 9 private methods across metadata, font setup, and
|
|
23
|
+
rendering concerns. Each concern is now its own class:
|
|
19
24
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
+
- `MetadataBuilder` — Info dict assembly.
|
|
26
|
+
- `FontSetup` — font file lookup + registration + metrics adapter.
|
|
27
|
+
- `PdfaPacket` — XMP packet + Catalog /Metadata attachment.
|
|
28
|
+
- `IccProfile` — locate sRGB ICC bytes.
|
|
29
|
+
- `BookmarkResolver` — destination chain + page index.
|
|
30
|
+
- `HyperlinkResolver` — source → URL lookup.
|
|
31
|
+
- `HyperlinkEmitter` — Link annotation emission.
|
|
32
|
+
- `StructureTracker` — MCID allocation + element registration.
|
|
33
|
+
- `StructureMapper` — item → PDF structure type.
|
|
34
|
+
- `ImageCollector` — image registration + placement.
|
|
35
|
+
|
|
36
|
+
Pipeline shrunk from ~225 lines (v0.3.0) to ~135 lines (v0.4.2).
|
|
25
37
|
|
|
26
38
|
## Verification
|
|
27
39
|
|
|
28
|
-
- `lib/idml/render/metadata_builder.rb` — extracted
|
|
29
|
-
- `lib/idml/render/
|
|
30
|
-
- `
|
|
31
|
-
|
|
40
|
+
- `lib/idml/render/metadata_builder.rb` — extracted.
|
|
41
|
+
- `lib/idml/render/font_setup.rb` — extracted.
|
|
42
|
+
- `lib/idml/render/pipeline.rb` — orchestration only.
|
|
43
|
+
- `spec/idml/render/metadata_builder_spec.rb` — 5 specs.
|
|
44
|
+
- `spec/idml/render/font_setup_spec.rb` — 4 specs.
|
|
45
|
+
- `spec/idml/render/placement_spec.rb` — added for completeness.
|
|
32
46
|
|
|
33
47
|
## Acceptance criteria
|
|
34
48
|
|
|
35
|
-
- [x] MetadataBuilder extracted
|
|
36
|
-
- [x]
|
|
37
|
-
- [x]
|
|
38
|
-
- [x]
|
|
49
|
+
- [x] MetadataBuilder extracted.
|
|
50
|
+
- [x] FontSetup extracted.
|
|
51
|
+
- [x] Pipeline no longer contains metadata/font helpers.
|
|
52
|
+
- [x] Each extracted class has dedicated specs.
|
|
53
|
+
- [x] All existing pipeline specs still pass.
|
|
54
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# TODO PDF 84: Schema-faithful Table/Cell/Row structure
|
|
2
|
+
|
|
3
|
+
## Status: DEFERRED (architectural correction; no fixtures to validate)
|
|
4
|
+
|
|
5
|
+
## Current state vs schema
|
|
6
|
+
|
|
7
|
+
The existing `Idml::Elements::Table`, `TableRow`, `TableCell` classes
|
|
8
|
+
do not match the IDML RNC schema. Three structural divergences:
|
|
9
|
+
|
|
10
|
+
### 1. Table lives in Stories, not Spreads
|
|
11
|
+
|
|
12
|
+
Per `reference-docs/schemas/package/Stories/Story.rnc`, `Table_Object`
|
|
13
|
+
appears inside `Story_Object` (as a child of `ParagraphStyleRange` or
|
|
14
|
+
`CharacterStyleRange`). The current `Idml::Elements::SpreadObject`
|
|
15
|
+
incorrectly declares `attribute :table, ... collection: true`, and
|
|
16
|
+
`PageItemRenderer` dispatches spread-level Tables to `TableRenderer`.
|
|
17
|
+
|
|
18
|
+
In real IDML, Tables appear inside Stories. A TextFrame's story
|
|
19
|
+
contains the table; the renderer should walk the story to find
|
|
20
|
+
tables, not the spread.
|
|
21
|
+
|
|
22
|
+
### 2. Element names: `Cell` and `Row`, not `TableCell` and `TableRow`
|
|
23
|
+
|
|
24
|
+
Per RNC, the element names are:
|
|
25
|
+
- `element Cell` (`Cell_Object`)
|
|
26
|
+
- `element Row` (`Row_Object`)
|
|
27
|
+
|
|
28
|
+
The current model uses `TableCell` and `TableRow` as both class names
|
|
29
|
+
and XML element roots — neither matches the schema.
|
|
30
|
+
|
|
31
|
+
### 3. Cell and Row are siblings under Table, not nested
|
|
32
|
+
|
|
33
|
+
Per RNC, `Table_Object` contains `Cell_Object* & Row_Object*` as
|
|
34
|
+
**siblings**, not nested `<Row><Cell>` structure:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
Table_Object = element Table {
|
|
38
|
+
...
|
|
39
|
+
Cell_Object*,
|
|
40
|
+
Row_Object*
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The current model uses `Table > TableRow > TableCell` nesting, which
|
|
45
|
+
won't parse real IDML.
|
|
46
|
+
|
|
47
|
+
## Plan
|
|
48
|
+
|
|
49
|
+
1. Add new `Elements::Cell` (matching `Cell_Object` RNC, ~80 attrs).
|
|
50
|
+
2. Add new `Elements::Row` (matching `Row_Object` RNC).
|
|
51
|
+
3. Restructure `Elements::Table` to declare both `cell` and `row`
|
|
52
|
+
sibling collections.
|
|
53
|
+
4. Remove `Elements::TableCell` and `Elements::TableRow` (broken).
|
|
54
|
+
5. Remove `:table` from `Elements::SpreadObject`; Tables come via
|
|
55
|
+
Stories only.
|
|
56
|
+
6. Update `TableRenderer` to:
|
|
57
|
+
- Dispatch from `TextFrameRenderer` when a story contains a Table.
|
|
58
|
+
- Render via `cell` and `row` siblings, using cell `Name` attr
|
|
59
|
+
(e.g., `"0_0"`) to determine column/row position.
|
|
60
|
+
7. Update `PageItemRenderer::RENDERER_MAP` to remove Table (no
|
|
61
|
+
longer a page item).
|
|
62
|
+
8. Update specs.
|
|
63
|
+
|
|
64
|
+
## Why deferred
|
|
65
|
+
|
|
66
|
+
- No fixture in `spec/fixtures/` contains a Table, so the current
|
|
67
|
+
broken model has never been validated against real IDML.
|
|
68
|
+
- The correction touches multiple subsystems (elements, parts,
|
|
69
|
+
renderers, dispatch) and is best done as a focused refactor with
|
|
70
|
+
real-fixture regression tests.
|
|
71
|
+
- Adding fixtures requires either Adobe InDesign (commercial) or a
|
|
72
|
+
hand-crafted IDML ZIP matching the schema.
|
|
73
|
+
|
|
74
|
+
## Acceptance criteria
|
|
75
|
+
|
|
76
|
+
- [ ] `Elements::Cell` matches `Cell_Object` RNC.
|
|
77
|
+
- [ ] `Elements::Row` matches `Row_Object` RNC.
|
|
78
|
+
- [ ] `Elements::Table` has sibling `cell` and `row` collections.
|
|
79
|
+
- [ ] Tables dispatch from `TextFrameRenderer`, not spread iteration.
|
|
80
|
+
- [ ] Real-fixture regression test with a known Table IDML.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# TODO PDF 85: Per-TextRange hyperlink precision
|
|
2
|
+
|
|
3
|
+
## Status: DEFERRED (requires text-engine position tracking)
|
|
4
|
+
|
|
5
|
+
## Current state
|
|
6
|
+
|
|
7
|
+
`HyperlinkEmitter` (TODO 78) emits one Link annotation per text
|
|
8
|
+
frame whose story contains a hyperlink source. The annotation
|
|
9
|
+
covers the entire frame's bounding box, not just the linked
|
|
10
|
+
characters.
|
|
11
|
+
|
|
12
|
+
## What per-TextRange precision requires
|
|
13
|
+
|
|
14
|
+
For each hyperlink source, compute the rect that covers exactly
|
|
15
|
+
the source's characters on the page.
|
|
16
|
+
|
|
17
|
+
### Position tracking
|
|
18
|
+
|
|
19
|
+
The text engine (`Shaper`, `LineBreaker`, `Justifier`) already
|
|
20
|
+
produces positioned lines via `VerticalLayout`, but
|
|
21
|
+
`TextFrameRenderer` does not surface per-character positions
|
|
22
|
+
outside its own scope. To compute per-source rects we need:
|
|
23
|
+
|
|
24
|
+
1. **Absolute character index per CSR** — track cumulative char
|
|
25
|
+
position as CSRs are processed within a story.
|
|
26
|
+
2. **Per-glyph (x, y) after layout** — Shaper gives widths,
|
|
27
|
+
LineBreaker gives line assignments, Justifier gives x_offset,
|
|
28
|
+
but no structured "positioned glyph" output is exposed today.
|
|
29
|
+
3. **Source range lookup** — map `HyperlinkTextSource#Self` to a
|
|
30
|
+
`[start_char, end_char]` range. Today HyperlinkTextSource is
|
|
31
|
+
modelled as a sibling element of `Content` inside CSR; the
|
|
32
|
+
range is implicit (the source wraps its own content).
|
|
33
|
+
|
|
34
|
+
### Emitter change
|
|
35
|
+
|
|
36
|
+
`HyperlinkEmitter` currently does:
|
|
37
|
+
```ruby
|
|
38
|
+
box = Placement.box(frame, @page_height)
|
|
39
|
+
@writer.add_uri_link_annotation(page_index:, rect: rect_for(box), url:)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Per-range version needs the positioned glyph data:
|
|
43
|
+
```ruby
|
|
44
|
+
positions = context.positioned_glyphs_for_frame(frame.self_attr)
|
|
45
|
+
range = source_range_for(hyperlink_source)
|
|
46
|
+
rect = bounding_rect_for_range(positions, range)
|
|
47
|
+
@writer.add_uri_link_annotation(page_index:, rect:, url:)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Threading
|
|
51
|
+
|
|
52
|
+
Positioned glyph data must travel from `TextFrameRenderer` (which
|
|
53
|
+
runs the layout engine) to `HyperlinkEmitter` (which runs after
|
|
54
|
+
the page renders). Options:
|
|
55
|
+
|
|
56
|
+
- **A. Side channel via RenderContext**: add `positioned_glyphs`
|
|
57
|
+
hash to RenderContext, keyed by frame Self. Renderers populate
|
|
58
|
+
it; emitter reads it.
|
|
59
|
+
- **B. Two-pass render**: re-run layout in the emitter to compute
|
|
60
|
+
positions. Wasteful but keeps emitter self-contained.
|
|
61
|
+
- **C. Position tracker object**: similar to StructureTracker —
|
|
62
|
+
Pipeline constructs it, threads through RenderContext, queries
|
|
63
|
+
after rendering.
|
|
64
|
+
|
|
65
|
+
Option C is the cleanest (matches StructureTracker pattern from
|
|
66
|
+
TODO 76).
|
|
67
|
+
|
|
68
|
+
## Plan
|
|
69
|
+
|
|
70
|
+
1. Add `Render::PositionTracker` — accumulates per-frame positioned
|
|
71
|
+
ranges: `[{ frame_self:, start_char:, end_char:, x:, y:, width:,
|
|
72
|
+
height: }, ...]`.
|
|
73
|
+
2. Add `position_tracker` to `RenderContext`.
|
|
74
|
+
3. In `TextFrameRenderer#render_run_lines`, after layout, push each
|
|
75
|
+
line's positioned range to the tracker.
|
|
76
|
+
4. In `HyperlinkEmitter#emit_for_frame`, query the tracker for
|
|
77
|
+
ranges that overlap each hyperlink source's `[start_char,
|
|
78
|
+
end_char]`. Compute the bounding rect from those ranges.
|
|
79
|
+
5. Emit one Link annotation per source.
|
|
80
|
+
|
|
81
|
+
## Why deferred
|
|
82
|
+
|
|
83
|
+
- No fixture has hyperlinks → can't validate per-range precision.
|
|
84
|
+
- Position tracker adds another stateful object to the render path.
|
|
85
|
+
- Frame-level precision is "good enough" for visible link presence;
|
|
86
|
+
per-range is a polish improvement.
|
|
87
|
+
|
|
88
|
+
## Acceptance criteria
|
|
89
|
+
|
|
90
|
+
- [ ] PositionTracker accumulates per-frame positioned ranges.
|
|
91
|
+
- [ ] HyperlinkEmitter queries tracker for source ranges.
|
|
92
|
+
- [ ] Each hyperlink source gets its own Link annotation rect.
|
|
93
|
+
- [ ] Spec covers multi-source story with overlapping ranges.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# TODO PDF 86: Caching and performance
|
|
2
|
+
|
|
3
|
+
## Status: PARTIAL — most hot paths cached; one risk noted
|
|
4
|
+
|
|
5
|
+
## What is cached today
|
|
6
|
+
|
|
7
|
+
- `ColorResolver#resolve` — per-color-name cache (`@cache`).
|
|
8
|
+
- `PdfrbFontMetrics#metrics_data` — read-through to pdfrb's
|
|
9
|
+
`Fonts#metrics_for` which has its own cache.
|
|
10
|
+
- `Pipeline#font_resolver` — lazily memoised via `||=`.
|
|
11
|
+
- `MetadataBuilder#xmp_packet` — memoised via `@xmp_packet ||=`.
|
|
12
|
+
- `PdfrbWriter#@image_cache` — URI → name cache for images.
|
|
13
|
+
- `Pdfrb::Document` internals — fonts, pages, structure all have
|
|
14
|
+
their own caches.
|
|
15
|
+
|
|
16
|
+
## What is not cached
|
|
17
|
+
|
|
18
|
+
### `geometric_bounds` on element instances
|
|
19
|
+
|
|
20
|
+
Each call to `Placement.box(item, page_height)` calls
|
|
21
|
+
`item.geometric_bounds`, which walks `Properties → PathGeometry →
|
|
22
|
+
bounding_box`. For each rendered item, this is called:
|
|
23
|
+
|
|
24
|
+
- 1× in the renderer (via `Placement.box`).
|
|
25
|
+
- 1× in `ImageCollector#clip_box_for` (for items that have images).
|
|
26
|
+
- 1× in `HyperlinkEmitter#emit_for_frame` (for text frames).
|
|
27
|
+
|
|
28
|
+
So 2–3 walks per item. Memoising at the element level would save
|
|
29
|
+
the duplicate walks.
|
|
30
|
+
|
|
31
|
+
### Risk: Lutaml model instance variables
|
|
32
|
+
|
|
33
|
+
Lutaml::Model::Serializable instances are populated via the
|
|
34
|
+
framework's own `instance_variable_set` during deserialization.
|
|
35
|
+
Adding `@geometric_bounds ||= ...` could conflict if Lutaml ever
|
|
36
|
+
adds an attribute of the same name, or if the framework resets
|
|
37
|
+
instance state during round-trip.
|
|
38
|
+
|
|
39
|
+
The safer pattern is to memoise at the helper layer (Placement)
|
|
40
|
+
using `Object#object_id` as the cache key, but that introduces
|
|
41
|
+
memory pressure (unbounded hash growth).
|
|
42
|
+
|
|
43
|
+
### Decision
|
|
44
|
+
|
|
45
|
+
Defer until profiling shows it matters. Current render of the
|
|
46
|
+
sample-with-image fixture (~60KB PDF, multiple shapes/text frames)
|
|
47
|
+
completes in ~250ms, which is fast enough for typical use.
|
|
48
|
+
|
|
49
|
+
## Other performance notes
|
|
50
|
+
|
|
51
|
+
- **SpreadRenderer iterates spread items 3×**: once for
|
|
52
|
+
`ImageCollector`, once for rendering, once for `HyperlinkEmitter`.
|
|
53
|
+
Could be combined but couples concerns.
|
|
54
|
+
- **No lazy part loading**: opening a Package reads all parts
|
|
55
|
+
eagerly. For large IDML files this could be slow; not yet a
|
|
56
|
+
measured problem.
|
|
57
|
+
|
|
58
|
+
## Plan (if profiling shows it matters)
|
|
59
|
+
|
|
60
|
+
1. Memoise `geometric_bounds` on `Elements::Rectangle`, `Polygon`,
|
|
61
|
+
`GraphicLine`, `TextFrame`, `Table` via `||=` with a
|
|
62
|
+
non-conflicting ivar name (e.g., `@_bounds_cache`).
|
|
63
|
+
2. Add a spec that asserts the cache is populated after first
|
|
64
|
+
access.
|
|
65
|
+
3. Re-run render benchmarks.
|
|
66
|
+
|
|
67
|
+
## Acceptance criteria
|
|
68
|
+
|
|
69
|
+
- [ ] Profile render of a large fixture and identify hot paths.
|
|
70
|
+
- [ ] If `geometric_bounds` shows up, memoise safely.
|
|
71
|
+
- [ ] Spec asserts memoisation doesn't break round-trip.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Idml
|
|
4
|
+
module Render
|
|
5
|
+
# Resolves the document font and wraps it in a
|
|
6
|
+
# `TextEngine::PdfrbFontMetrics` adapter. Encapsulates:
|
|
7
|
+
#
|
|
8
|
+
# 1. Finding the IDML document's first non-missing font file
|
|
9
|
+
# via `Pdfrb::FontResolver#find_by_ps_name`.
|
|
10
|
+
# 2. Registering that file with pdfrb's Fonts collection.
|
|
11
|
+
# 3. Wrapping the resulting Symbol resource in
|
|
12
|
+
# `PdfrbFontMetrics` for the text engine.
|
|
13
|
+
#
|
|
14
|
+
# Extracted from `Pipeline` for SRP — Pipeline orchestrates,
|
|
15
|
+
# this object owns the font-setup dance.
|
|
16
|
+
class FontSetup
|
|
17
|
+
DEFAULT_FONT = Render::DEFAULT_FONT
|
|
18
|
+
|
|
19
|
+
def initialize(package:, font_search_paths: nil)
|
|
20
|
+
@package = package
|
|
21
|
+
@font_search_paths = font_search_paths
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Registers the resolved font with `writer` and returns the
|
|
25
|
+
# Symbol resource. Falls back to `Render::DEFAULT_FONT`
|
|
26
|
+
# (`"Helvetica"`) when the document has no resolvable font.
|
|
27
|
+
def register(writer)
|
|
28
|
+
path = resolve_document_font_path
|
|
29
|
+
return DEFAULT_FONT unless path
|
|
30
|
+
|
|
31
|
+
writer.register_font(path)
|
|
32
|
+
rescue StandardError
|
|
33
|
+
DEFAULT_FONT
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Wraps a registered font resource in a PdfrbFontMetrics
|
|
37
|
+
# adapter. Returns nil when the resource is the default
|
|
38
|
+
# fallback (no real metrics available).
|
|
39
|
+
def metrics_for(writer, font_resource)
|
|
40
|
+
return nil if font_resource == DEFAULT_FONT
|
|
41
|
+
|
|
42
|
+
TextEngine::PdfrbFontMetrics.new(writer.document.fonts, font_resource)
|
|
43
|
+
rescue StandardError
|
|
44
|
+
nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def resolve_document_font_path
|
|
50
|
+
return nil unless @package&.fonts
|
|
51
|
+
|
|
52
|
+
@package.fonts.font_family.each do |family|
|
|
53
|
+
path = find_font_file(family)
|
|
54
|
+
return path if path
|
|
55
|
+
end
|
|
56
|
+
nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def find_font_file(family)
|
|
60
|
+
family.font.each do |font|
|
|
61
|
+
next unless font.post_script_name
|
|
62
|
+
next if font.status == "Missing"
|
|
63
|
+
|
|
64
|
+
path = resolver.find_by_ps_name(font.post_script_name)
|
|
65
|
+
return path if path
|
|
66
|
+
end
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def resolver
|
|
71
|
+
@resolver ||= Pdfrb::FontResolver.new(
|
|
72
|
+
search_paths: @font_search_paths || Pdfrb::FontResolver::DEFAULT_SEARCH_PATHS,
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
data/lib/idml/render/pipeline.rb
CHANGED
|
@@ -3,8 +3,15 @@
|
|
|
3
3
|
module Idml
|
|
4
4
|
module Render
|
|
5
5
|
# Top-level pipeline: IDML Package → PDF file using pdfrb.
|
|
6
|
-
# All PDF assembly is delegated to
|
|
7
|
-
#
|
|
6
|
+
# All PDF assembly is delegated to pdfrb; this class orchestrates
|
|
7
|
+
# the high-level flow:
|
|
8
|
+
#
|
|
9
|
+
# 1. Build writer + set metadata.
|
|
10
|
+
# 2. Apply compliance (PDF/A XMP + ICC) if requested.
|
|
11
|
+
# 3. Set up structure tracker for tagged PDF.
|
|
12
|
+
# 4. Resolve document font.
|
|
13
|
+
# 5. For each spread: collect images, render pages, emit hyperlinks.
|
|
14
|
+
# 6. Flush structure, emit bookmarks, subset fonts, write.
|
|
8
15
|
class Pipeline
|
|
9
16
|
DEFAULT_WIDTH = 612
|
|
10
17
|
DEFAULT_HEIGHT = 792
|
|
@@ -24,23 +31,21 @@ module Idml
|
|
|
24
31
|
metadata = MetadataBuilder.new(@package).build
|
|
25
32
|
writer.set_info(metadata)
|
|
26
33
|
writer.enable_tagged if @tagged
|
|
27
|
-
if pdfa_requested?
|
|
28
|
-
|
|
29
|
-
embed_pdfa_output_intent(writer)
|
|
30
|
-
end
|
|
34
|
+
apply_compliance(writer, metadata) if pdfa_requested?
|
|
35
|
+
|
|
31
36
|
structure = StructureTracker.new(enabled: @tagged)
|
|
32
37
|
layer_filter = LayerFilter.from_designmap(@package.designmap)
|
|
33
38
|
font_ref_resolver = FontReferenceResolver.build(@package)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
font_setup = FontSetup.new(package: @package,
|
|
40
|
+
font_search_paths: @font_search_paths)
|
|
41
|
+
font_resource = font_setup.register(writer)
|
|
42
|
+
font_metrics = font_setup.metrics_for(writer, font_resource)
|
|
38
43
|
|
|
44
|
+
page_index = -1
|
|
39
45
|
@package.spreads.each do |spread|
|
|
40
|
-
page_index =
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
structure, page_index)
|
|
46
|
+
page_index = render_spread(writer, spread, layer_filter,
|
|
47
|
+
font_ref_resolver, font_resource,
|
|
48
|
+
font_metrics, structure, page_index)
|
|
44
49
|
end
|
|
45
50
|
|
|
46
51
|
structure.flush(writer)
|
|
@@ -53,7 +58,8 @@ module Idml
|
|
|
53
58
|
|
|
54
59
|
private
|
|
55
60
|
|
|
56
|
-
def
|
|
61
|
+
def apply_compliance(writer, metadata)
|
|
62
|
+
PdfaPacket.attach(writer.document, metadata)
|
|
57
63
|
bytes = IccProfile.srgb_bytes
|
|
58
64
|
return unless bytes
|
|
59
65
|
|
|
@@ -67,24 +73,24 @@ module Idml
|
|
|
67
73
|
nil
|
|
68
74
|
end
|
|
69
75
|
|
|
76
|
+
def pdfa_requested?
|
|
77
|
+
@compliance&.to_s&.start_with?("pdfa")
|
|
78
|
+
end
|
|
79
|
+
|
|
70
80
|
def emit_bookmarks(writer)
|
|
71
81
|
BookmarkResolver.new(@package).each do |title, page_index|
|
|
72
82
|
writer.add_bookmark(title, page_index)
|
|
73
83
|
end
|
|
74
84
|
end
|
|
75
85
|
|
|
76
|
-
def
|
|
77
|
-
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def render_spread_pages(writer, spread, base_dir, layer_filter,
|
|
81
|
-
font_ref_resolver, font_resource, font_metrics,
|
|
82
|
-
structure, page_offset)
|
|
86
|
+
def render_spread(writer, spread, layer_filter, font_ref_resolver,
|
|
87
|
+
font_resource, font_metrics, structure, page_offset)
|
|
83
88
|
pages = spread.spread.flat_map(&:page)
|
|
84
|
-
image_refs = ImageCollector.new(writer: writer,
|
|
89
|
+
image_refs = ImageCollector.new(writer: writer,
|
|
90
|
+
base_dir: File.dirname(@package.path),
|
|
85
91
|
page_height: DEFAULT_HEIGHT).collect(spread)
|
|
86
92
|
renderer = build_renderer(layer_filter, font_ref_resolver,
|
|
87
|
-
font_resource, font_metrics, structure
|
|
93
|
+
font_resource, font_metrics, structure)
|
|
88
94
|
current = page_offset
|
|
89
95
|
|
|
90
96
|
pages.each do |page|
|
|
@@ -110,7 +116,7 @@ module Idml
|
|
|
110
116
|
end
|
|
111
117
|
|
|
112
118
|
def build_renderer(layer_filter, font_ref_resolver, font_resource,
|
|
113
|
-
font_metrics, structure
|
|
119
|
+
font_metrics, structure)
|
|
114
120
|
SpreadRenderer.new(
|
|
115
121
|
font_metrics: font_metrics,
|
|
116
122
|
font_ps_name: font_resource,
|
|
@@ -125,50 +131,6 @@ module Idml
|
|
|
125
131
|
{ width: page.width || DEFAULT_WIDTH,
|
|
126
132
|
height: page.height || DEFAULT_HEIGHT }
|
|
127
133
|
end
|
|
128
|
-
|
|
129
|
-
def build_font_metrics(writer, font_resource)
|
|
130
|
-
return nil if font_resource == Render::DEFAULT_FONT
|
|
131
|
-
|
|
132
|
-
TextEngine::PdfrbFontMetrics.new(writer.document.fonts, font_resource)
|
|
133
|
-
rescue StandardError
|
|
134
|
-
nil
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def register_font(writer)
|
|
138
|
-
path = resolve_document_font_path
|
|
139
|
-
return Render::DEFAULT_FONT unless path
|
|
140
|
-
|
|
141
|
-
writer.register_font(path)
|
|
142
|
-
rescue StandardError
|
|
143
|
-
Render::DEFAULT_FONT
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def font_resolver
|
|
147
|
-
@font_resolver ||= Pdfrb::FontResolver.new(
|
|
148
|
-
search_paths: @font_search_paths || Pdfrb::FontResolver::DEFAULT_SEARCH_PATHS,
|
|
149
|
-
)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def resolve_document_font_path
|
|
153
|
-
return nil unless @package&.fonts
|
|
154
|
-
|
|
155
|
-
@package.fonts.font_family.each do |family|
|
|
156
|
-
path = find_font_file(family)
|
|
157
|
-
return path if path
|
|
158
|
-
end
|
|
159
|
-
nil
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def find_font_file(family)
|
|
163
|
-
family.font.each do |font|
|
|
164
|
-
next unless font.post_script_name
|
|
165
|
-
next if font.status == "Missing"
|
|
166
|
-
|
|
167
|
-
path = font_resolver.find_by_ps_name(font.post_script_name)
|
|
168
|
-
return path if path
|
|
169
|
-
end
|
|
170
|
-
nil
|
|
171
|
-
end
|
|
172
134
|
end
|
|
173
135
|
end
|
|
174
136
|
end
|
data/lib/idml/render.rb
CHANGED
|
@@ -24,6 +24,7 @@ module Idml
|
|
|
24
24
|
autoload :HyperlinkResolver, "#{__dir__}/render/hyperlink_resolver"
|
|
25
25
|
autoload :HyperlinkEmitter, "#{__dir__}/render/hyperlink_emitter"
|
|
26
26
|
autoload :MetadataBuilder, "#{__dir__}/render/metadata_builder"
|
|
27
|
+
autoload :FontSetup, "#{__dir__}/render/font_setup"
|
|
27
28
|
autoload :StoryThreader, "#{__dir__}/render/story_threader"
|
|
28
29
|
autoload :PdfrbWriter, "#{__dir__}/render/pdfrb_writer"
|
|
29
30
|
autoload :SpreadRenderer, "#{__dir__}/render/spread_renderer"
|
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.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose
|
|
@@ -210,6 +210,9 @@ files:
|
|
|
210
210
|
- TODO.pdf/81-pdfa-icc-output-intent.md
|
|
211
211
|
- TODO.pdf/82-table-cell-text.md
|
|
212
212
|
- TODO.pdf/83-pipeline-decomposition.md
|
|
213
|
+
- TODO.pdf/84-schema-faithful-table.md
|
|
214
|
+
- TODO.pdf/85-per-text-range-hyperlinks.md
|
|
215
|
+
- TODO.pdf/86-caching-performance.md
|
|
213
216
|
- TODO.pdf/README.md
|
|
214
217
|
- exe/idml
|
|
215
218
|
- idml.gemspec
|
|
@@ -385,6 +388,7 @@ files:
|
|
|
385
388
|
- lib/idml/render/color_helper.rb
|
|
386
389
|
- lib/idml/render/color_resolver.rb
|
|
387
390
|
- lib/idml/render/font_reference_resolver.rb
|
|
391
|
+
- lib/idml/render/font_setup.rb
|
|
388
392
|
- lib/idml/render/gradient_resolver.rb
|
|
389
393
|
- lib/idml/render/hyperlink_emitter.rb
|
|
390
394
|
- lib/idml/render/hyperlink_resolver.rb
|