idml 0.2.5 → 0.2.7
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/40-font-ps-name-resolution.md +40 -28
- data/TODO.pdf/52-font-subsetting.md +34 -4
- data/TODO.pdf/55-dead-code-deprecation.md +15 -21
- data/TODO.pdf/63-replace-fontmetrics-with-pdfrb.md +60 -18
- data/TODO.pdf/65-pdfrb-019-integration.md +16 -16
- data/TODO.pdf/67-text-rich-multi-run.md +51 -0
- data/TODO.pdf/68-radial-gradient-shadings.md +36 -0
- data/TODO.pdf/69-transparency-blend-modes.md +84 -0
- data/TODO.pdf/70-stroke-styling.md +32 -0
- data/TODO.pdf/71-shared-placement-module.md +41 -0
- data/TODO.pdf/72-stroke-styling.md +55 -0
- data/TODO.pdf/73-gradient-resolver-cleanup.md +48 -0
- data/TODO.pdf/74-renderer-spec-coverage.md +53 -0
- data/lib/idml/elements/graphic_line.rb +10 -0
- data/lib/idml/elements/polygon.rb +10 -0
- data/lib/idml/elements/rectangle.rb +10 -0
- data/lib/idml/elements/text_frame.rb +2 -0
- data/lib/idml/elements/transparency_setting.rb +25 -0
- data/lib/idml/render/blending.rb +60 -0
- data/lib/idml/render/gradient_resolver.rb +10 -102
- data/lib/idml/render/placement.rb +31 -0
- data/lib/idml/render/renderers/graphic_line_renderer.rb +10 -8
- data/lib/idml/render/renderers/polygon_renderer.rb +9 -7
- data/lib/idml/render/renderers/rectangle_renderer.rb +43 -35
- data/lib/idml/render/renderers/table_renderer.rb +1 -1
- data/lib/idml/render/renderers/text_frame_renderer.rb +1 -9
- data/lib/idml/render/stroke_style.rb +97 -0
- data/lib/idml/render.rb +3 -0
- data/lib/idml/version.rb +1 -1
- metadata +12 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6fcb60002b6973119b04d5aef70ec4ae5541c2da5da1d6fb42fcb19288fec86d
|
|
4
|
+
data.tar.gz: 86fb08cdace467cf97fedd0cd82bd65820ca1977da5a83255db40ba239273799
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 38cab95250785926124189618d16e7d63190ffa63824296530292c8c264bc8913fe6038ed39fbd85ca84925252399cdf609cfda54b64530f20d77bb3ac6c7fed
|
|
7
|
+
data.tar.gz: bed359cf5365c4a2658f379c7d2a84213c545688993cbe86406bcbf88609b59a992ee46cc96eb21c77ec9142036e7b57bd2c561356502051d2cb15bd71c94b78
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
idml (0.2.
|
|
4
|
+
idml (0.2.7)
|
|
5
5
|
bigdecimal
|
|
6
6
|
fontisan
|
|
7
7
|
lutaml-model (~> 0.8.18)
|
|
@@ -198,7 +198,7 @@ CHECKSUMS
|
|
|
198
198
|
ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d
|
|
199
199
|
ffi (1.17.4-x86_64-linux-musl) sha256=3fdf9888483de005f8ef8d1cf2d3b20d86626af206cbf780f6a6a12439a9c49e
|
|
200
200
|
fontisan (0.4.45) sha256=2337ca0205f806647b24b8cac57aa34f3769473026ae6f52dc36bde0feed6c62
|
|
201
|
-
idml (0.2.
|
|
201
|
+
idml (0.2.7)
|
|
202
202
|
json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
|
|
203
203
|
language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0
|
|
204
204
|
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
|
@@ -1,30 +1,42 @@
|
|
|
1
1
|
# TODO PDF 40: Font PS name resolution from Fonts.xml
|
|
2
2
|
|
|
3
|
-
##
|
|
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
|
-
|
|
3
|
+
## Status: DONE
|
|
4
|
+
|
|
5
|
+
## What was implemented
|
|
6
|
+
|
|
7
|
+
The pipeline now resolves document fonts from `Resources/Fonts.xml` via
|
|
8
|
+
PostScriptName lookup, falling back to the default font when the file
|
|
9
|
+
isn't found on disk:
|
|
10
|
+
|
|
11
|
+
1. `Idml::TextEngine::FontResolver#resolve_by_ps_name(ps_name)`
|
|
12
|
+
walks system font directories and matches on each candidate font's
|
|
13
|
+
internal PostScript name (read by Fontisan).
|
|
14
|
+
2. `Idml::Render::Pipeline#resolve_document_font_path` iterates
|
|
15
|
+
`Parts::Fonts#font_family` → `FontFamily#font`, skips entries with
|
|
16
|
+
`Status="Missing"`, and returns the first match.
|
|
17
|
+
3. `Pipeline#register_font` rescues any resolver failure and returns
|
|
18
|
+
`Render::DEFAULT_FONT` (`"Helvetica"`) so rendering never crashes
|
|
19
|
+
when a font file is absent.
|
|
20
|
+
4. The chosen font name is threaded through `RenderContext` to every
|
|
21
|
+
renderer via `font_ps_name`.
|
|
22
|
+
|
|
23
|
+
## Verification
|
|
24
|
+
|
|
25
|
+
- `lib/idml/render/pipeline.rb:170` — `resolve_document_font_path`
|
|
26
|
+
- `lib/idml/render/pipeline.rb:185` — `resolve_by_ps_name` call
|
|
27
|
+
- `lib/idml/text_engine/font_resolver.rb:38` — PS-name search
|
|
28
|
+
|
|
29
|
+
## Acceptance criteria
|
|
30
|
+
|
|
31
|
+
- [x] Parts::Fonts typed model parses FontFamily → Font entries.
|
|
32
|
+
- [x] FontFamily and Font element models expose PostScriptName.
|
|
33
|
+
- [x] PostScriptName-based search added to FontResolver.
|
|
34
|
+
- [x] Pipeline passes document font references to the resolver.
|
|
35
|
+
- [x] Falls back to default font when document font can't be found.
|
|
36
|
+
|
|
37
|
+
## Out of scope
|
|
38
|
+
|
|
39
|
+
Per-run font selection (each `CharacterStyleRange#applied_font`
|
|
40
|
+
mapped to its own registered font) requires TODO 67's multi-font
|
|
41
|
+
`text_rich` integration and is tracked there. This TODO covers the
|
|
42
|
+
document-default font, which is what every renderer uses today.
|
|
@@ -1,12 +1,44 @@
|
|
|
1
1
|
# TODO PDF 52: Font subsetting via pdfrb
|
|
2
2
|
|
|
3
|
+
## Status: BLOCKED (pdfrb has no subsetting API)
|
|
4
|
+
|
|
3
5
|
## Goal
|
|
4
6
|
|
|
5
7
|
Subset embedded TrueType fonts to include only the glyphs actually used
|
|
6
8
|
in the document. This dramatically reduces PDF file size (from full font
|
|
7
9
|
~500KB to subset ~5-20KB per font).
|
|
8
10
|
|
|
9
|
-
##
|
|
11
|
+
## Blocker
|
|
12
|
+
|
|
13
|
+
pdfrb 0.4.0 exposes no subsetting API:
|
|
14
|
+
|
|
15
|
+
- `Pdfrb::Document::Fonts#add` accepts `**opts` but ignores `subset:`.
|
|
16
|
+
- No `used_codepoints` collector.
|
|
17
|
+
- `glyph_width` returns stub 500 for TrueType — the per-glyph data
|
|
18
|
+
needed to build a subset is unavailable.
|
|
19
|
+
|
|
20
|
+
Without per-glyph metrics, subsetting cannot be implemented in the
|
|
21
|
+
caller either. This is also the same blocker that blocks TODO 63
|
|
22
|
+
(replace FontMetrics with pdfrb).
|
|
23
|
+
|
|
24
|
+
## Path forward
|
|
25
|
+
|
|
26
|
+
Either:
|
|
27
|
+
|
|
28
|
+
1. pdfrb adds a subsetting API that consumes a `used_codepoints:` set
|
|
29
|
+
and emits a subsetted FontFile2 at write time, OR
|
|
30
|
+
2. pdfrb grows real TTF table parsing (head/hmtx/cmap) so the caller
|
|
31
|
+
can collect used codepoints, build the subset, and pass the bytes
|
|
32
|
+
back to `Fonts#add` as pre-subsetted data.
|
|
33
|
+
|
|
34
|
+
Once pdfrb unblocks, the idml side needs to:
|
|
35
|
+
|
|
36
|
+
1. Pipeline collects all Unicode codepoints across all stories.
|
|
37
|
+
2. Pipeline passes `used_codepoints: set` to `Fonts#add(subset: true, ...)`.
|
|
38
|
+
3. Spec renders text with known characters and verifies the embedded
|
|
39
|
+
font only contains those glyphs.
|
|
40
|
+
|
|
41
|
+
## Acceptance criteria (after pdfrb unblocks)
|
|
10
42
|
|
|
11
43
|
- [ ] Pipeline collects all Unicode codepoints used across all stories.
|
|
12
44
|
- [ ] For each embedded font, only the used glyphs are included.
|
|
@@ -17,6 +49,4 @@ in the document. This dramatically reduces PDF file size (from full font
|
|
|
17
49
|
|
|
18
50
|
## Dependencies
|
|
19
51
|
|
|
20
|
-
- pdfrb
|
|
21
|
-
FontFile2 embedding may require pdfrb enhancement).
|
|
22
|
-
- TODO 27 (text engine for glyph collection).
|
|
52
|
+
- pdfrb subsetting API (NOT YET AVAILABLE in 0.4.0).
|
|
@@ -1,30 +1,24 @@
|
|
|
1
1
|
# TODO PDF 55: Dead code deprecation markers
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Status: DONE (modules already removed)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
superseded by pdfrb but cannot be deleted per the project's
|
|
7
|
-
"never delete source files" policy. Add deprecation comments and
|
|
8
|
-
update autoloads to lazy-load only when explicitly referenced.
|
|
5
|
+
## What was done
|
|
9
6
|
|
|
10
|
-
|
|
7
|
+
The old hand-rolled modules have already been deleted from the codebase
|
|
8
|
+
during the pdfrb migration (TODO 51 + TODO 62). No `Idml::Render::PdfWriter`,
|
|
9
|
+
`Idml::Render::FontEmbedder`, `Idml::Render::Color`, `Idml::Render::Path`,
|
|
10
|
+
or `Idml::Render::Text` references remain in `lib/` or `spec/`.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- `Idml::Render::Path` — replaced by Canvas drawing methods
|
|
16
|
-
- `Idml::Render::Text` — replaced by Canvas text method
|
|
12
|
+
The original goal — deprecation comments on dead modules — is moot:
|
|
13
|
+
there are no dead modules to deprecate. This TODO is closed without
|
|
14
|
+
further work.
|
|
17
15
|
|
|
18
|
-
##
|
|
16
|
+
## Verification
|
|
19
17
|
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
- [ ] No active code path (Pipeline, SpreadRenderer, renderers) references
|
|
23
|
-
deprecated modules.
|
|
24
|
-
- [ ] Tests for deprecated modules are tagged with `:deprecated` or
|
|
25
|
-
moved to a separate spec file.
|
|
26
|
-
- [ ] `render.rb` autoloads keep the old modules for backward compatibility.
|
|
18
|
+
`grep -rn "PdfWriter|FontEmbedder|Render::Color\b|Render::Path\b|Render::Text\b" lib/ spec/`
|
|
19
|
+
returns zero matches.
|
|
27
20
|
|
|
28
|
-
##
|
|
21
|
+
## Acceptance criteria
|
|
29
22
|
|
|
30
|
-
-
|
|
23
|
+
- [x] No live code references the old hand-rolled modules.
|
|
24
|
+
- [x] `bundle exec rake` is green (2314 examples, 0 failures).
|
|
@@ -1,31 +1,73 @@
|
|
|
1
1
|
# TODO PDF 63: Replace FontMetrics with pdfrb measurement API
|
|
2
2
|
|
|
3
|
+
## Status: BLOCKED (pdfrb measurement APIs are stubs)
|
|
4
|
+
|
|
3
5
|
## Goal
|
|
4
6
|
|
|
5
7
|
Replace `Idml::TextEngine::FontMetrics` (200+ lines of TTF binary parsing
|
|
6
|
-
via Fontisan) with pdfrb
|
|
7
|
-
|
|
8
|
+
via Fontisan) with pdfrb's native `Fonts#measure_text`, `#glyph_width`,
|
|
9
|
+
and `#metrics_for`. Eliminates the Fontisan dependency for text layout.
|
|
10
|
+
|
|
11
|
+
## Blocker
|
|
12
|
+
|
|
13
|
+
pdfrb 0.4.0 ships the API surface but the implementations are stubs
|
|
14
|
+
that return constant values, not real per-glyph widths:
|
|
15
|
+
|
|
16
|
+
`/Users/mulgogi/src/claricle/pdfrb/lib/pdfrb/document/fonts.rb`:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
def measure_text(text, font:, size:)
|
|
20
|
+
return 0 unless text
|
|
21
|
+
# TODO: use font metrics for per-glyph width lookup
|
|
22
|
+
text.to_s.length * (size || 0).to_f * 0.5
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def glyph_width(_char, _resource)
|
|
26
|
+
500
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def metrics_for(_resource)
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The Shaper and LineBreaker depend on accurate per-glyph widths for
|
|
35
|
+
correct word-wrap. With stub data, line breaks land at the wrong
|
|
36
|
+
positions and run advance (needed for TODO 67's `text_rich`) is wrong.
|
|
37
|
+
|
|
38
|
+
Fontisan correctly parses head/hhea/hmtx/cmap tables for any TTF/OTF
|
|
39
|
+
and exposes per-glyph advance widths. Until pdfrb either grows real
|
|
40
|
+
TTF parsing or accepts externally-provided metrics (see
|
|
41
|
+
`/Users/mulgogi/src/claricle/pdfrb/PROPOSAL.external-font-metrics.md`),
|
|
42
|
+
Fontisan stays.
|
|
43
|
+
|
|
44
|
+
## Path forward
|
|
8
45
|
|
|
9
|
-
|
|
46
|
+
Proposal at `~/src/claricle/pdfrb/PROPOSAL.external-font-metrics.md`
|
|
47
|
+
suggests letting `Fonts#add` accept `widths:`/`metrics:`/`encoding:`
|
|
48
|
+
hashes from the caller, so pdfrb does not need to parse TTF tables
|
|
49
|
+
itself. The idml gem would then build those hashes from FontMetrics
|
|
50
|
+
and pass them in — keeping Fontisan as the parser while pdfrb handles
|
|
51
|
+
only PDF assembly. This is the Unix-philosophy split.
|
|
10
52
|
|
|
11
|
-
|
|
12
|
-
- `document.fonts.measure_text(text, font:, size:)` → width in points
|
|
13
|
-
- `document.fonts.metrics_for(font)` → { ascent:, descent:, cap_height:, ... }
|
|
53
|
+
Once that proposal lands:
|
|
14
54
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
55
|
+
1. `PdfrbWriter#register_font_with_metrics(path, widths:, metrics:, ...)`.
|
|
56
|
+
2. Pipeline resolves fonts via FontResolver (Fontisan) and passes the
|
|
57
|
+
resulting metrics to pdfrb.
|
|
58
|
+
3. Internal `FontMetrics` may still exist as the Fontisan adapter, but
|
|
59
|
+
Shaper/LineBreaker no longer need to read it — they go through
|
|
60
|
+
`pdfrb.measure_text`.
|
|
18
61
|
|
|
19
|
-
##
|
|
62
|
+
## Acceptance criteria (after pdfrb unblocks)
|
|
20
63
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
3. Remove `fontisan` from gemspec dependencies.
|
|
26
|
-
4. Remove `text_engine/font_metrics.rb` (200+ lines).
|
|
64
|
+
- [ ] Pipeline registers fonts with externally-provided metrics.
|
|
65
|
+
- [ ] Shaper/LineBreaker call pdfrb's measurement API, not FontMetrics.
|
|
66
|
+
- [ ] `fontisan` remains a dependency for parsing, but no longer feeds
|
|
67
|
+
Shaper/LineBreaker directly.
|
|
27
68
|
|
|
28
69
|
## Dependencies
|
|
29
70
|
|
|
30
|
-
- pdfrb 0.
|
|
31
|
-
|
|
71
|
+
- pdfrb 0.4.0's `Fonts#measure_text`/`#glyph_width`/`#metrics_for`
|
|
72
|
+
with real implementations OR
|
|
73
|
+
- pdfrb accepts the external-metrics proposal linked above.
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
# TODO PDF 65: pdfrb 0.19.0 feature integration
|
|
2
2
|
|
|
3
|
-
## Status: PARTIALLY DONE
|
|
3
|
+
## Status: PARTIALLY DONE (remainder blocked)
|
|
4
4
|
|
|
5
5
|
## What was implemented
|
|
6
6
|
|
|
7
|
-
1. **Canvas#text_lines**: TextFrameRenderer uses `canvas.text_lines`
|
|
7
|
+
1. **Canvas#text_lines**: `TextFrameRenderer` uses `canvas.text_lines`
|
|
8
8
|
instead of N separate `canvas.text` calls per line. Single batch
|
|
9
9
|
call for all lines within a run.
|
|
10
10
|
|
|
11
|
-
## What remains
|
|
11
|
+
## What remains
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
replace FontMetrics/Fontisan until this is fixed.
|
|
13
|
+
Blocked by pdfrb 0.4.0 stubs in `Fonts#measure_text` (returns
|
|
14
|
+
`length * 0.5 * size`) and `#glyph_width` (returns 500). Until real
|
|
15
|
+
per-glyph widths land:
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
4. **Font subsetting**: `Fonts#add` accepts `**opts`
|
|
22
|
-
|
|
17
|
+
2. **`text_rich` for multi-run text**: tracked in TODO 67. Cannot
|
|
18
|
+
advance between runs without real measurement.
|
|
19
|
+
3. **Replace FontMetrics with pdfrb measurement**: tracked in TODO 63.
|
|
20
|
+
4. **Font subsetting**: tracked in TODO 52. `Fonts#add` accepts `**opts`
|
|
21
|
+
but `subset:` has no effect.
|
|
23
22
|
|
|
24
23
|
## Acceptance criteria
|
|
25
24
|
|
|
26
|
-
- [x] TextFrameRenderer uses canvas.text_lines
|
|
27
|
-
- [ ] FontMetrics replaced with pdfrb glyph_width (blocked
|
|
28
|
-
- [ ] FontResolver replaced with Pdfrb::FontResolver (blocked
|
|
29
|
-
- [ ] Font subsetting verified
|
|
25
|
+
- [x] TextFrameRenderer uses `canvas.text_lines`
|
|
26
|
+
- [ ] FontMetrics replaced with pdfrb `glyph_width` (blocked — TODO 63)
|
|
27
|
+
- [ ] FontResolver replaced with `Pdfrb::FontResolver` (blocked — TODO 63)
|
|
28
|
+
- [ ] Font subsetting verified (blocked — TODO 52)
|
|
29
|
+
- [ ] `text_rich` used for multi-run frames (blocked — TODO 67)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# TODO PDF 67: Multi-run text batching via Canvas#text_rich
|
|
2
|
+
|
|
3
|
+
## Status: BLOCKED (pdfrb measure_text is a stub)
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
Replace the per-run `Canvas#text` calls in `TextFrameRenderer#simple_render`
|
|
8
|
+
with a single `Canvas#text_rich` call per text frame. `text_rich` emits
|
|
9
|
+
all runs inside one BT/ET block, advancing the text matrix between runs.
|
|
10
|
+
|
|
11
|
+
## Motivation
|
|
12
|
+
|
|
13
|
+
`text_rich` is the right primitive for multi-run text — one begin/end
|
|
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 returns a
|
|
16
|
+
stub `length * 0.5 * size`. Without accurate advance, runs overwrite
|
|
17
|
+
each other on the line.
|
|
18
|
+
|
|
19
|
+
## Blocker
|
|
20
|
+
|
|
21
|
+
`/Users/mulgogi/src/claricle/pdfrb/lib/pdfrb/document/fonts.rb:65`:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
def measure_text(text, font:, size:)
|
|
25
|
+
return 0 unless text
|
|
26
|
+
# TODO: use font metrics for per-glyph width lookup
|
|
27
|
+
text.to_s.length * (size || 0).to_f * 0.5
|
|
28
|
+
end
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`glyph_width` (line 79) is also a stub returning 500, and `metrics_for`
|
|
32
|
+
(line 87) returns nil. These need real Fontisan-backed implementations
|
|
33
|
+
before text_rich produces correct output.
|
|
34
|
+
|
|
35
|
+
## Plan (after pdfrb unblocks)
|
|
36
|
+
|
|
37
|
+
1. Build `runs` array of `{ text:, font:, size:, color: }` per line.
|
|
38
|
+
2. Replace `simple_render` body with `canvas.text_lines(..., rich: true)`
|
|
39
|
+
or `canvas.text_rich(runs, at: [x, y])`.
|
|
40
|
+
3. Verify rendered PDF: runs no longer overwrite, multi-color lines work.
|
|
41
|
+
|
|
42
|
+
## Acceptance criteria
|
|
43
|
+
|
|
44
|
+
- [ ] `TextFrameRenderer#simple_render` uses `canvas.text_rich`.
|
|
45
|
+
- [ ] Render spec verifies a multi-run line has correct horizontal advance.
|
|
46
|
+
- [ ] Single BT/ET block per frame (assertion on PDF content stream).
|
|
47
|
+
|
|
48
|
+
## Dependencies
|
|
49
|
+
|
|
50
|
+
- pdfrb 0.4.0 `Fonts#measure_text` returns real per-glyph widths.
|
|
51
|
+
- pdfrb 0.4.0 `Fonts#glyph_width` not returning stub 500.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# TODO PDF 68: Radial gradient shadings via pdfrb 0.4.0
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
RectangleRenderer routes gradient fills to either `Shadings#add_axial`
|
|
6
|
+
(linear) or `Shadings#add_radial` (radial) based on the IDML
|
|
7
|
+
`Gradient.type` attribute (`"Linear"` vs `"Radial"`).
|
|
8
|
+
|
|
9
|
+
## Background
|
|
10
|
+
|
|
11
|
+
IDML `<Gradient Type="...">` supports two gradient shapes:
|
|
12
|
+
- `"Linear"` (default) — colour stops along a straight axis
|
|
13
|
+
- `"Radial"` — colour stops along a radius from a centre point
|
|
14
|
+
|
|
15
|
+
pdfrb 0.4.0 exposes both:
|
|
16
|
+
- `document.shadings.add_axial(from:, to:, stops:, extend:)`
|
|
17
|
+
- `document.shadings.add_radial(from:, to:, stops:, extend:)`
|
|
18
|
+
|
|
19
|
+
Before this TODO, every gradient was rendered as axial regardless of the
|
|
20
|
+
declared type, producing incorrect output for radial swatches.
|
|
21
|
+
|
|
22
|
+
## Implementation
|
|
23
|
+
|
|
24
|
+
1. Read `Gradient#type` (already modelled on `Elements::Gradient`).
|
|
25
|
+
2. In `RectangleRenderer.render_gradient_fill`, dispatch:
|
|
26
|
+
- `type == "Radial"` → `add_radial` with centre-to-edge coords.
|
|
27
|
+
- otherwise → `add_axial` (existing behaviour).
|
|
28
|
+
3. Radial coordinates use the box centre as both `from` and the
|
|
29
|
+
near corner of `to` so the shading fills the rectangle.
|
|
30
|
+
|
|
31
|
+
## Acceptance criteria
|
|
32
|
+
|
|
33
|
+
- [x] `Gradient.type == "Radial"` produces a pdfrb radial shading.
|
|
34
|
+
- [x] `Gradient.type == "Linear"` (or nil) keeps producing an axial shading.
|
|
35
|
+
- [x] Spec covers both branches.
|
|
36
|
+
- [x] All existing specs still pass.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# TODO PDF 69: Transparency and blend modes via pdfrb 0.4.0
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Honor IDML `<BlendingSetting BlendMode="..." Opacity="..."/>` on page
|
|
6
|
+
items by routing through pdfrb's `Canvas#with_transparency` and
|
|
7
|
+
`Canvas#blend_mode=`. Applies to Rectangle, Polygon, GraphicLine, and
|
|
8
|
+
TextFrame fill+stroke paths.
|
|
9
|
+
|
|
10
|
+
## Background
|
|
11
|
+
|
|
12
|
+
IDML models transparency as nested elements under each page item:
|
|
13
|
+
|
|
14
|
+
```xml
|
|
15
|
+
<Rectangle ...>
|
|
16
|
+
<TransparencySetting>
|
|
17
|
+
<BlendingSetting BlendMode="Multiply" Opacity="0.5"
|
|
18
|
+
KnockoutGroup="true" IsolateBlending="false"/>
|
|
19
|
+
<DropShadowSetting .../> <!-- not handled here -->
|
|
20
|
+
</TransparencySetting>
|
|
21
|
+
</Rectangle>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`TransparencySetting` lives as a child of the page item (sibling of
|
|
25
|
+
`Properties`). Its first `BlendingSetting` child carries the blend mode
|
|
26
|
+
and opacity that apply to the item's entire drawing.
|
|
27
|
+
|
|
28
|
+
pdfrb 0.4.0 exposes the matching primitives:
|
|
29
|
+
|
|
30
|
+
- `Canvas#opacity=` — sets alpha for both fill and stroke (ExtGState `ca`/`CA`).
|
|
31
|
+
- `Canvas#blend_mode=` — sets BM name.
|
|
32
|
+
- `Canvas#with_transparency(opacity:, blend_mode:) { ... }` — saves graphics state, applies both, restores on exit.
|
|
33
|
+
|
|
34
|
+
## IDML → PDF blend-mode mapping
|
|
35
|
+
|
|
36
|
+
IDML's `BlendMode` enumeration maps to PDF's `BM` names where they line
|
|
37
|
+
up; PDF does not have equivalents for every IDML mode. Initial mapping:
|
|
38
|
+
|
|
39
|
+
| IDML | PDF |
|
|
40
|
+
|--------------------|---------------|
|
|
41
|
+
| `Normal` | `Normal` |
|
|
42
|
+
| `Multiply` | `Multiply` |
|
|
43
|
+
| `Screen` | `Screen` |
|
|
44
|
+
| `Overlay` | `Overlay` |
|
|
45
|
+
| `SoftLight` | `SoftLight` |
|
|
46
|
+
| `HardLight` | `HardLight` |
|
|
47
|
+
| `Darken` | `Darken` |
|
|
48
|
+
| `Lighten` | `Lighten` |
|
|
49
|
+
| `ColorDodge` | `ColorDodge` |
|
|
50
|
+
| `ColorBurn` | `ColorBurn` |
|
|
51
|
+
| `Difference` | `Difference` |
|
|
52
|
+
| `Exclusion` | `Exclusion` |
|
|
53
|
+
| `Hue` | `Hue` |
|
|
54
|
+
| `Saturation` | `Saturation` |
|
|
55
|
+
| `Color` | `Color` |
|
|
56
|
+
| `Luminosity` | `Luminosity` |
|
|
57
|
+
|
|
58
|
+
Unknown modes fall back to `Normal`.
|
|
59
|
+
|
|
60
|
+
## Implementation
|
|
61
|
+
|
|
62
|
+
1. Add `transparency_setting` attribute to `Rectangle`, `Polygon`,
|
|
63
|
+
`GraphicLine`, `TextFrame`, `Group` (RNC lists it on every page item).
|
|
64
|
+
2. Model `TransparencySetting` with a `blending_setting` child.
|
|
65
|
+
3. New helper `Render::Blending` that maps an `Elements::BlendingSetting`
|
|
66
|
+
to a `{ opacity:, blend_mode: }` hash.
|
|
67
|
+
4. In each renderer, wrap the existing fill/stroke block in
|
|
68
|
+
`canvas.with_transparency(**blending_args) { ... }` when a setting is
|
|
69
|
+
present.
|
|
70
|
+
5. Reuse the existing `save_graphics_state` block — `with_transparency`
|
|
71
|
+
already saves/restores, so renderers call it in place of the plain
|
|
72
|
+
save block when blending applies.
|
|
73
|
+
|
|
74
|
+
## Acceptance criteria
|
|
75
|
+
|
|
76
|
+
- [ ] IDML `<BlendingSetting Opacity="0.5"/>` halves the item's alpha in the output PDF.
|
|
77
|
+
- [ ] IDML `<BlendingSetting BlendMode="Multiply"/>` sets `/BM /Multiply` in ExtGState.
|
|
78
|
+
- [ ] Items without BlendingSetting render exactly as before (no ExtGState emitted).
|
|
79
|
+
- [ ] Spec covers opacity, blend_mode, and pass-through cases.
|
|
80
|
+
|
|
81
|
+
## Dependencies
|
|
82
|
+
|
|
83
|
+
- pdfrb 0.4.0 `Canvas#with_transparency` (DONE — confirmed in pdfrb 0.4.0).
|
|
84
|
+
- pdfrb 0.4.0 `Canvas#blend_mode=` (DONE).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# TODO PDF 70: Stroke styling — line cap, join, dash patterns
|
|
2
|
+
|
|
3
|
+
## Status: DONE (implemented as TODO 72)
|
|
4
|
+
|
|
5
|
+
## What was implemented
|
|
6
|
+
|
|
7
|
+
See [TODO 72](72-stroke-styling.md) for the full implementation
|
|
8
|
+
details. Highlights:
|
|
9
|
+
|
|
10
|
+
- New `Render::StrokeStyle` module with `strokeable?` predicate and
|
|
11
|
+
`apply(canvas, item) { ... }` block-wrapper.
|
|
12
|
+
- Element models `Rectangle`, `Polygon`, `GraphicLine` gained
|
|
13
|
+
`end_cap`, `end_join`, `miter_limit`, `stroke_dash_and_gap`
|
|
14
|
+
attributes per the RNC schema.
|
|
15
|
+
- Each renderer's stroke path routes through `StrokeStyle.apply`,
|
|
16
|
+
which maps IDML enums to PDF line cap/join codes and emits
|
|
17
|
+
`dash_pattern` from the `StrokeDashAndGap` list.
|
|
18
|
+
- 13 dedicated specs in `spec/idml/render/stroke_style_spec.rb`.
|
|
19
|
+
|
|
20
|
+
## Acceptance criteria
|
|
21
|
+
|
|
22
|
+
- [x] Dashed strokes render with correct dash array.
|
|
23
|
+
- [x] Round/miter/bevel joins produce matching PDF output.
|
|
24
|
+
- [x] Round/butt/projecting caps produce matching PDF output.
|
|
25
|
+
- [x] Plain strokes (no style attrs) render exactly as before.
|
|
26
|
+
- [x] Spec covers dashed, round-join, round-cap, and pass-through cases.
|
|
27
|
+
|
|
28
|
+
## Dependencies
|
|
29
|
+
|
|
30
|
+
- pdfrb 0.4.0 `line_cap=`, `line_join=`, `miter_limit=`, `dash_pattern=`
|
|
31
|
+
(DONE).
|
|
32
|
+
- IDML element models for stroke styles (DONE).
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# TODO PDF 71: Shared Placement module (MECE refactor)
|
|
2
|
+
|
|
3
|
+
## Status: DONE
|
|
4
|
+
|
|
5
|
+
## What was implemented
|
|
6
|
+
|
|
7
|
+
Extracted the per-item placement computation (geometric_bounds +
|
|
8
|
+
item_transform + page_height → PDF `{x:, y:, width:, height:}` rect)
|
|
9
|
+
into a single shared module `Idml::Render::Placement`.
|
|
10
|
+
|
|
11
|
+
Before this refactor:
|
|
12
|
+
|
|
13
|
+
- `RectangleRenderer.placement_box` was the canonical helper.
|
|
14
|
+
- `PolygonRenderer` and `GraphicLineRenderer` reached across to
|
|
15
|
+
`RectangleRenderer.placement_box` — a MECE violation (polygon and
|
|
16
|
+
graphic line depend on rectangle for non-rectangle concerns).
|
|
17
|
+
- `TableRenderer` also reached across to `RectangleRenderer`.
|
|
18
|
+
- `TextFrameRenderer.frame_box` re-implemented the bounds→transform
|
|
19
|
+
→rect pipeline by hand with a different fallback shape.
|
|
20
|
+
|
|
21
|
+
After:
|
|
22
|
+
|
|
23
|
+
- `Idml::Render::Placement.box(item, page_height, fallback: false)`
|
|
24
|
+
is the single source of truth.
|
|
25
|
+
- All four shape renderers call `Placement.box`.
|
|
26
|
+
- `TextFrameRenderer.frame_box` is now a one-liner that calls
|
|
27
|
+
`Placement.box(frame, page_height, fallback: true)`.
|
|
28
|
+
|
|
29
|
+
## Why
|
|
30
|
+
|
|
31
|
+
Placement is a geometry concern, not a rectangle concern. Hoisting it
|
|
32
|
+
out of `RectangleRenderer` removes the cross-renderer coupling and
|
|
33
|
+
keeps each renderer focused on its own shape's path construction.
|
|
34
|
+
|
|
35
|
+
## Acceptance criteria
|
|
36
|
+
|
|
37
|
+
- [x] No renderer references `RectangleRenderer.placement_box`.
|
|
38
|
+
- [x] `Placement.box` returns nil for items without bounds (default)
|
|
39
|
+
or the fallback rect when `fallback: true`.
|
|
40
|
+
- [x] All render specs pass.
|
|
41
|
+
- [x] `bundle exec rake` green (2350+ examples).
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# TODO PDF 72: Stroke styling — cap, join, miter, dash
|
|
2
|
+
|
|
3
|
+
## Status: DONE
|
|
4
|
+
|
|
5
|
+
## What was implemented
|
|
6
|
+
|
|
7
|
+
Honors IDML stroke-style attributes by routing through pdfrb 0.4.0's
|
|
8
|
+
stroke-style Canvas setters:
|
|
9
|
+
|
|
10
|
+
- `line_cap=` — from IDML `EndCap` (ButtEndCap/RoundEndCap/ProjectingEndCap)
|
|
11
|
+
- `line_join=` — from IDML `EndJoin` (MiterEndJoin/RoundEndJoin/BevelEndJoin)
|
|
12
|
+
- `miter_limit=` — from IDML `MiterLimit` (ignored when < 1.0)
|
|
13
|
+
- `dash_pattern=` — from IDML `StrokeDashAndGap` flat list
|
|
14
|
+
|
|
15
|
+
New module `Idml::Render::StrokeStyle` exposes:
|
|
16
|
+
|
|
17
|
+
- `StrokeStyle.strokeable?(item)` — predicate (replaces the per-renderer
|
|
18
|
+
`RectangleRenderer.strokeable?`). Returns a real boolean, not the
|
|
19
|
+
`nil`/value chain Ruby `&&` would produce.
|
|
20
|
+
- `StrokeStyle.apply(canvas, item) { ... }` — saves graphics state,
|
|
21
|
+
applies all four style attributes (only those present on the item),
|
|
22
|
+
yields, restores. Scoping prevents cap/join/miter/dash from leaking
|
|
23
|
+
into subsequent strokes on the same page.
|
|
24
|
+
|
|
25
|
+
Element models `Rectangle`, `Polygon`, `GraphicLine` each gained four
|
|
26
|
+
new typed attributes from the RNC schema:
|
|
27
|
+
|
|
28
|
+
- `end_cap` (`EndCap_EnumValue`)
|
|
29
|
+
- `end_join` (`EndJoin_EnumValue`)
|
|
30
|
+
- `miter_limit` (`xsd:double`)
|
|
31
|
+
- `stroke_dash_and_gap` (`list { xsd:double * }`)
|
|
32
|
+
|
|
33
|
+
Each renderer's `render_stroke` (or main `render` for GraphicLine)
|
|
34
|
+
calls `StrokeStyle.apply(canvas, item) { ... build path ... stroke }`
|
|
35
|
+
inside the existing `Blending.wrap`.
|
|
36
|
+
|
|
37
|
+
## IDML → PDF enum map
|
|
38
|
+
|
|
39
|
+
| IDML | PDF code |
|
|
40
|
+
|---------------------|----------|
|
|
41
|
+
| ButtEndCap | 0 |
|
|
42
|
+
| RoundEndCap | 1 |
|
|
43
|
+
| ProjectingEndCap | 2 |
|
|
44
|
+
| MiterEndJoin | 0 |
|
|
45
|
+
| RoundEndJoin | 1 |
|
|
46
|
+
| BevelEndJoin | 2 |
|
|
47
|
+
|
|
48
|
+
## Acceptance criteria
|
|
49
|
+
|
|
50
|
+
- [x] All four stroke-style setters invoked when their IDML attrs present.
|
|
51
|
+
- [x] Items without stroke-style attrs render exactly as before.
|
|
52
|
+
- [x] `StrokeStyle.apply` saves/restores graphics state.
|
|
53
|
+
- [x] Spec covers all four setters, miter_limit clamp, dash array parse,
|
|
54
|
+
no-attrs pass-through, and `strokeable?` truth table (13 specs).
|
|
55
|
+
- [x] `bundle exec rake` green.
|