idml 0.2.6 → 0.2.8
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/52-font-subsetting.md +43 -50
- data/TODO.pdf/63-replace-fontmetrics-with-pdfrb.md +39 -52
- data/TODO.pdf/65-pdfrb-019-integration.md +23 -13
- data/TODO.pdf/67-text-rich-multi-run.md +18 -15
- data/TODO.pdf/70-stroke-styling.md +23 -51
- 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/TODO.pdf/75-pdf-metadata-from-xmp.md +103 -0
- data/TODO.pdf/76-tagged-pdf-structure.md +92 -0
- data/TODO.pdf/77-pdfa-xmp-output-intent.md +74 -0
- data/lib/idml/cli.rb +3 -0
- data/lib/idml/elements/graphic_line.rb +8 -0
- data/lib/idml/elements/polygon.rb +8 -0
- data/lib/idml/elements/rectangle.rb +8 -0
- data/lib/idml/render/gradient_resolver.rb +10 -102
- data/lib/idml/render/pdfrb_writer.rb +22 -16
- data/lib/idml/render/pipeline.rb +3 -1
- data/lib/idml/render/placement.rb +31 -0
- data/lib/idml/render/renderers/graphic_line_renderer.rb +9 -7
- data/lib/idml/render/renderers/polygon_renderer.rb +8 -6
- data/lib/idml/render/renderers/rectangle_renderer.rb +8 -19
- 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 +10 -3
- data/lib/idml/version.rb +1 -1
- metadata +10 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e9833da9d4a8ddda74890980eab76f2a47b03cdb99977730fb1dee5d8809dd68
|
|
4
|
+
data.tar.gz: 1d771cc2775f1071329d2b2dbab681cc46197546be27681fcba39ee9313f9a2b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e3dfbfc1c8ccdccdcd53070ff9aaf8de3f150699e524c3b6b4668c9a2d266cfb530ed5d8fb1e5b515be6da145df17e60832d9f9f60c0c9b2c106092adf716dfd
|
|
7
|
+
data.tar.gz: 437d8bcdc82e87f999b916ea9fc10486f2f4b8ae57965b9753fae465ab8f570684f4b2ea7eb8ae81fbc40fdf6a66288387429d265053562cf2dc4581101473be
|
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.8)
|
|
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.8)
|
|
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,52 +1,45 @@
|
|
|
1
1
|
# TODO PDF 52: Font subsetting via pdfrb
|
|
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
|
-
- [ ] PDF file size significantly reduced vs full-font embedding.
|
|
47
|
-
- [ ] Spec: render text with known characters, verify embedded font
|
|
48
|
-
contains only those glyphs.
|
|
49
|
-
|
|
50
|
-
## Dependencies
|
|
51
|
-
|
|
52
|
-
- pdfrb subsetting API (NOT YET AVAILABLE in 0.4.0).
|
|
3
|
+
## Status: DONE
|
|
4
|
+
|
|
5
|
+
## What was implemented
|
|
6
|
+
|
|
7
|
+
Pipeline now subsets embedded TrueType fonts via pdfrb 0.4.0's
|
|
8
|
+
`Fonts#subset_fonts!`. The flow:
|
|
9
|
+
|
|
10
|
+
1. Every `canvas.text` / `canvas.text_lines` call auto-populates
|
|
11
|
+
`document.fonts.used_codepoints(resource)` per font.
|
|
12
|
+
2. `Pipeline#call` invokes `writer.subset_fonts!` after rendering all
|
|
13
|
+
spreads and before `writer.write`.
|
|
14
|
+
3. `PdfrbWriter#subset_fonts!` delegates to
|
|
15
|
+
`document.fonts.subset_fonts!`, which rewrites each registered
|
|
16
|
+
font's FontFile2 with a subset containing only the used glyphs
|
|
17
|
+
(plus glyph 0 / notdef).
|
|
18
|
+
4. Subsetting uses `Pdfrb::Font::TrueType::Subsetter`, which rebuilds
|
|
19
|
+
glyf/loca/cmap/hmtx/hhea/maxp/head tables and resolves composite
|
|
20
|
+
glyph references.
|
|
21
|
+
|
|
22
|
+
## Opting out
|
|
23
|
+
|
|
24
|
+
`Idml::Render.render(..., subset_fonts: false)` skips the call.
|
|
25
|
+
The CLI exposes `--no-subset` for the same effect. Useful when a
|
|
26
|
+
downstream consumer needs the full font table (e.g. searchable PDFs
|
|
27
|
+
with a specific glyph set).
|
|
28
|
+
|
|
29
|
+
## Verification
|
|
30
|
+
|
|
31
|
+
- `lib/idml/render/pdfrb_writer.rb:48` — `subset_fonts!` delegation.
|
|
32
|
+
- `lib/idml/render/pipeline.rb:38` — `subset_fonts!` call site.
|
|
33
|
+
- `spec/idml/render/render_pdfrb_writer_spec.rb:120` — subsetting
|
|
34
|
+
produces a sub-15KB PDF for "Hello" in Arial.
|
|
35
|
+
|
|
36
|
+
## Acceptance criteria
|
|
37
|
+
|
|
38
|
+
- [x] Pipeline collects used codepoints automatically via pdfrb's
|
|
39
|
+
`encode_text` hook.
|
|
40
|
+
- [x] Each embedded font's FontFile2 is rewritten with a subset
|
|
41
|
+
containing only used glyphs.
|
|
42
|
+
- [x] PDF file size significantly reduced vs full-font embedding.
|
|
43
|
+
- [x] Spec renders text with known characters and verifies the
|
|
44
|
+
output PDF is small.
|
|
45
|
+
- [x] `subset_fonts: false` skips subsetting.
|
|
@@ -1,73 +1,60 @@
|
|
|
1
1
|
# TODO PDF 63: Replace FontMetrics with pdfrb measurement API
|
|
2
2
|
|
|
3
|
-
## Status:
|
|
3
|
+
## Status: PARTIALLY UNBLOCKED (subsetting works; measurement still AFM-only)
|
|
4
4
|
|
|
5
5
|
## Goal
|
|
6
6
|
|
|
7
7
|
Replace `Idml::TextEngine::FontMetrics` (200+ lines of TTF binary parsing
|
|
8
8
|
via Fontisan) with pdfrb's native `Fonts#measure_text`, `#glyph_width`,
|
|
9
|
-
and `#metrics_for`.
|
|
9
|
+
and `#metrics_for`.
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## What pdfrb 0.4.0 provides
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
- `Pdfrb::Font::TrueType::File` — real TTF parser with Head, Hhea,
|
|
14
|
+
Cmap, Hmtx, OS2 tables.
|
|
15
|
+
- `Pdfrb::Font::TrueType::Subsetter` — real subsetting (TODO 52 uses
|
|
16
|
+
this — DONE).
|
|
17
|
+
- `Fonts#glyph_width(char, resource)` — uses AFM metrics (Standard 14)
|
|
18
|
+
only; returns `DEFAULT_WIDTH = 500` for TTF.
|
|
19
|
+
- `Fonts#measure_text(text, font:, size:)` — same: AFM or stub
|
|
20
|
+
(`length * 0.5 * size`).
|
|
21
|
+
- `Fonts#metrics_for(resource)` — AFM metrics only.
|
|
15
22
|
|
|
16
|
-
|
|
23
|
+
## What still needs pdfrb work
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
end
|
|
25
|
+
pdfrb's TTF parser exists but is not wired to the measurement API.
|
|
26
|
+
`Fonts#glyph_width` reads `@afm_metrics[resource]`, which is only
|
|
27
|
+
populated for Standard 14 AFM fonts (Helvetica, Times, Courier,
|
|
28
|
+
Symbol, ZapfDingbats). For every other font (any TTF/OTF), it falls
|
|
29
|
+
back to the stub.
|
|
24
30
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
The per-glyph-width proposal asked for "Look up glyph ID from cmap,
|
|
32
|
+
then width from hmtx." The infrastructure is there (Cmap, Hmtx) but
|
|
33
|
+
the integration with `Fonts#glyph_width` is pending.
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
```
|
|
35
|
+
Until pdfrb's measurement API wires the TTF parser, the idml gem's
|
|
36
|
+
Shaper and LineBreaker must keep using Fontisan-based FontMetrics
|
|
37
|
+
for accurate per-glyph widths.
|
|
33
38
|
|
|
34
|
-
|
|
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.
|
|
39
|
+
## Plan (after pdfrb unblocks)
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
## Path forward
|
|
45
|
-
|
|
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.
|
|
52
|
-
|
|
53
|
-
Once that proposal lands:
|
|
54
|
-
|
|
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`.
|
|
41
|
+
1. `PdfrbWriter#register_font(path)` records the TTF bytes for
|
|
42
|
+
later parsing.
|
|
43
|
+
2. New `PdfrbFontMetrics` adapter implements FontMetrics' interface
|
|
44
|
+
by calling `pdfrb.fonts.glyph_width(font_resource, codepoint)`.
|
|
45
|
+
3. `FontResolver` returns `PdfrbFontMetrics` for resolved fonts.
|
|
46
|
+
4. Remove `text_engine/font_metrics.rb` (200+ lines).
|
|
61
47
|
|
|
62
48
|
## Acceptance criteria (after pdfrb unblocks)
|
|
63
49
|
|
|
64
|
-
- [ ] Pipeline registers fonts
|
|
65
|
-
- [ ] Shaper/LineBreaker call pdfrb's measurement API, not
|
|
66
|
-
|
|
67
|
-
|
|
50
|
+
- [ ] Pipeline registers fonts via `pdfrb.fonts.add(path)`.
|
|
51
|
+
- [ ] Shaper/LineBreaker call pdfrb's measurement API, not
|
|
52
|
+
FontMetrics.
|
|
53
|
+
- [ ] `fontisan` removed from gemspec dependencies.
|
|
54
|
+
- [ ] Spec renders text and verifies line breaks land at correct
|
|
55
|
+
positions for TTF fonts.
|
|
68
56
|
|
|
69
57
|
## Dependencies
|
|
70
58
|
|
|
71
|
-
- pdfrb
|
|
72
|
-
|
|
73
|
-
- pdfrb accepts the external-metrics proposal linked above.
|
|
59
|
+
- pdfrb `Fonts#glyph_width` uses parsed TTF tables, not AFM-only.
|
|
60
|
+
- pdfrb `Fonts#measure_text` uses parsed TTF tables.
|
|
@@ -1,29 +1,39 @@
|
|
|
1
|
-
# TODO PDF 65: pdfrb
|
|
1
|
+
# TODO PDF 65: pdfrb feature integration
|
|
2
2
|
|
|
3
3
|
## Status: PARTIALLY DONE (remainder blocked)
|
|
4
4
|
|
|
5
5
|
## What was implemented
|
|
6
6
|
|
|
7
7
|
1. **Canvas#text_lines**: `TextFrameRenderer` uses `canvas.text_lines`
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
for batched single-font multi-line text (TODO 27).
|
|
9
|
+
2. **Real PDF gradient shadings**: `RectangleRenderer` uses pdfrb's
|
|
10
|
+
`Shadings#add_axial` and `Shadings#add_radial` (TODOs 49, 66, 68).
|
|
11
|
+
3. **Canvas#with_transparency**: `Blending.wrap` applies IDML
|
|
12
|
+
`BlendingSetting` opacity + blend modes (TODO 69).
|
|
13
|
+
4. **Stroke-style setters**: `StrokeStyle.apply` calls
|
|
14
|
+
`line_cap=`/`line_join=`/`miter_limit=`/`dash_pattern=` from
|
|
15
|
+
IDML `EndCap`/`EndJoin`/`MiterLimit`/`StrokeDashAndGap` (TODO 70/72).
|
|
16
|
+
5. **Font subsetting**: `Pipeline` calls `Fonts#subset_fonts!` before
|
|
17
|
+
write (TODO 52).
|
|
18
|
+
6. **Placement module**: shared `Render::Placement.box` (TODO 71).
|
|
10
19
|
|
|
11
20
|
## What remains
|
|
12
21
|
|
|
13
|
-
Blocked by pdfrb 0.4.0
|
|
14
|
-
`length * 0.5 * size`) and `#glyph_width` (returns 500). Until real
|
|
15
|
-
per-glyph widths land:
|
|
22
|
+
Blocked by pdfrb 0.4.0's TTF measurement still being AFM-only:
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
7. **`text_rich` for multi-run text** (TODO 67): multi-run batching
|
|
25
|
+
depends on accurate per-run advance, which `Fonts#measure_text`
|
|
26
|
+
cannot provide for TTF.
|
|
27
|
+
8. **Replace FontMetrics with pdfrb measurement** (TODO 63): same
|
|
28
|
+
blocker.
|
|
22
29
|
|
|
23
30
|
## Acceptance criteria
|
|
24
31
|
|
|
25
32
|
- [x] TextFrameRenderer uses `canvas.text_lines`
|
|
33
|
+
- [x] Real PDF gradient shadings via pdfrb
|
|
34
|
+
- [x] Transparency and blend modes
|
|
35
|
+
- [x] Stroke styling
|
|
36
|
+
- [x] Font subsetting
|
|
37
|
+
- [x] Shared Placement module
|
|
26
38
|
- [ ] 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
39
|
- [ ] `text_rich` used for multi-run frames (blocked — TODO 67)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# TODO PDF 67: Multi-run text batching via Canvas#text_rich
|
|
2
2
|
|
|
3
|
-
## Status: BLOCKED (pdfrb measure_text is
|
|
3
|
+
## Status: BLOCKED (pdfrb measure_text is stub for TTF)
|
|
4
4
|
|
|
5
5
|
## Goal
|
|
6
6
|
|
|
@@ -12,32 +12,35 @@ all runs inside one BT/ET block, advancing the text matrix between runs.
|
|
|
12
12
|
|
|
13
13
|
`text_rich` is the right primitive for multi-run text — one begin/end
|
|
14
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
|
|
16
|
-
stub `length * 0.5 * size
|
|
17
|
-
|
|
15
|
+
`Pdfrb::Document::Fonts#measure_text`, which in pdfrb 0.4.0 still
|
|
16
|
+
returns the stub `length * 0.5 * size` for TTF/OTF fonts (only
|
|
17
|
+
Standard 14 AFM fonts get real measurement). Without accurate advance,
|
|
18
|
+
runs overwrite each other on the line.
|
|
18
19
|
|
|
19
20
|
## Blocker
|
|
20
21
|
|
|
21
|
-
`/Users/mulgogi/src/claricle/pdfrb/lib/pdfrb/document/fonts.rb
|
|
22
|
+
`/Users/mulgogi/src/claricle/pdfrb/lib/pdfrb/document/fonts.rb`:
|
|
22
23
|
|
|
23
24
|
```ruby
|
|
24
25
|
def measure_text(text, font:, size:)
|
|
25
|
-
return 0 unless text
|
|
26
|
-
|
|
27
|
-
text.to_s.length *
|
|
26
|
+
return 0 unless text && size
|
|
27
|
+
metrics = @afm_metrics[font]
|
|
28
|
+
return text.to_s.length * size.to_f * 0.5 unless metrics # STUB
|
|
29
|
+
...
|
|
28
30
|
end
|
|
29
31
|
```
|
|
30
32
|
|
|
31
|
-
`
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
`@afm_metrics` is only populated for Standard 14 fonts. pdfrb has
|
|
34
|
+
the TTF parsing infrastructure (`Pdfrb::Font::TrueType::File` with
|
|
35
|
+
Cmap, Hmtx) but the integration with `Fonts#glyph_width` /
|
|
36
|
+
`measure_text` is pending.
|
|
34
37
|
|
|
35
38
|
## Plan (after pdfrb unblocks)
|
|
36
39
|
|
|
37
40
|
1. Build `runs` array of `{ text:, font:, size:, color: }` per line.
|
|
38
|
-
2. Replace `simple_render` body with `canvas.
|
|
39
|
-
or `canvas.text_rich(runs, at: [x, y])`.
|
|
41
|
+
2. Replace `simple_render` body with `canvas.text_rich(runs, at: [x, y])`.
|
|
40
42
|
3. Verify rendered PDF: runs no longer overwrite, multi-color lines work.
|
|
43
|
+
4. Combine with TODO 63 to drop Fontisan for measurement.
|
|
41
44
|
|
|
42
45
|
## Acceptance criteria
|
|
43
46
|
|
|
@@ -47,5 +50,5 @@ before text_rich produces correct output.
|
|
|
47
50
|
|
|
48
51
|
## Dependencies
|
|
49
52
|
|
|
50
|
-
- pdfrb
|
|
51
|
-
|
|
53
|
+
- pdfrb `Fonts#measure_text` returns real per-glyph widths for TTF
|
|
54
|
+
fonts (currently stub — see TODO 63).
|
|
@@ -1,60 +1,32 @@
|
|
|
1
1
|
# TODO PDF 70: Stroke styling — line cap, join, dash patterns
|
|
2
2
|
|
|
3
|
-
## Status:
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- `
|
|
11
|
-
|
|
12
|
-
- `
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
exposes the matching attributes through `<DashedStrokeStyle>` and
|
|
20
|
-
`<DottedStrokeStyle>` elements in `Resources/Graphic.xml`, plus
|
|
21
|
-
per-item `StrokeColor`/`StrokeWeight` (already handled).
|
|
22
|
-
|
|
23
|
-
IDML → PDF stroke mapping:
|
|
24
|
-
|
|
25
|
-
| IDML | PDF |
|
|
26
|
-
|-------------------------------------|---------------------------|
|
|
27
|
-
| `EndCap="RoundCap"` | `line_cap = 1` (Round) |
|
|
28
|
-
| `EndCap="ButtCap"` | `line_cap = 0` (Butt) |
|
|
29
|
-
| `EndCap="ProjectingCap"` | `line_cap = 2` (Square) |
|
|
30
|
-
| `Join="RoundJoin"` | `line_join = 1` |
|
|
31
|
-
| `Join="MiterJoin"` | `line_join = 0` |
|
|
32
|
-
| `Join="BevelJoin"` | `line_join = 2` |
|
|
33
|
-
| `<DashedStrokeStyle DashArray="...">` | `dash_pattern = [arr, 0]` |
|
|
34
|
-
| `<DottedStrokeStyle DotArray="...">` | `dash_pattern = [arr, 0]` (dots are short dashes) |
|
|
35
|
-
|
|
36
|
-
## Plan
|
|
37
|
-
|
|
38
|
-
1. `Parts::Graphic` already exposes `dashed_stroke_style` and
|
|
39
|
-
`dotted_stroke_style` collections — add accessors on the renderer
|
|
40
|
-
context.
|
|
41
|
-
2. New `Render::StrokeStyle` helper that resolves an
|
|
42
|
-
`Elements::DashedStrokeStyle` / `DottedStrokeStyle` (or stroke-style
|
|
43
|
-
self-id reference on the item) to a `{ line_cap:, line_join:,
|
|
44
|
-
miter_limit:, dash_pattern: }` hash.
|
|
45
|
-
3. Each stroke renderer (Rectangle, Polygon, GraphicLine) calls
|
|
46
|
-
`StrokeStyle.apply(canvas, item, context)` before `canvas.stroke`.
|
|
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`.
|
|
47
19
|
|
|
48
20
|
## Acceptance criteria
|
|
49
21
|
|
|
50
|
-
- [
|
|
51
|
-
- [
|
|
52
|
-
- [
|
|
53
|
-
- [
|
|
54
|
-
- [
|
|
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.
|
|
55
27
|
|
|
56
28
|
## Dependencies
|
|
57
29
|
|
|
58
30
|
- pdfrb 0.4.0 `line_cap=`, `line_join=`, `miter_limit=`, `dash_pattern=`
|
|
59
|
-
(DONE
|
|
60
|
-
- IDML element models for stroke styles (DONE
|
|
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.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# TODO PDF 73: Drop dead GradientResolver approximation code
|
|
2
|
+
|
|
3
|
+
## Status: DONE
|
|
4
|
+
|
|
5
|
+
## What was removed
|
|
6
|
+
|
|
7
|
+
`Idml::Render::GradientResolver` previously carried a 32-rectangle
|
|
8
|
+
discrete-approximation renderer (`render_gradient`, `interpolate`,
|
|
9
|
+
`blend_between`, `blend`, and the `SEGMENTS = 32` constant). The
|
|
10
|
+
discrete approximation was replaced by real PDF shadings in TODO 66
|
|
11
|
+
(pdfrb 0.4.0 `Shadings#add_axial`) and TODO 68 (`add_radial`); the
|
|
12
|
+
old code became dead.
|
|
13
|
+
|
|
14
|
+
The class also had a `build(graphic)` factory that built a lookup
|
|
15
|
+
table no consumer reads.
|
|
16
|
+
|
|
17
|
+
## What was kept
|
|
18
|
+
|
|
19
|
+
- `GradientResolver.gradient?(name)` — pure predicate (`name` is a
|
|
20
|
+
`Gradient/*` reference). Still used by `RectangleRenderer#render_fill`
|
|
21
|
+
to choose between gradient and solid color paths.
|
|
22
|
+
|
|
23
|
+
The module is now a 4-line pure predicate. Every other responsibility
|
|
24
|
+
moved to `RectangleRenderer`'s direct `Shadings#add_axial` /
|
|
25
|
+
`add_radial` calls.
|
|
26
|
+
|
|
27
|
+
## Why
|
|
28
|
+
|
|
29
|
+
Dead code accumulates. Three reasons to drop it:
|
|
30
|
+
|
|
31
|
+
1. **Confusion** — readers see `SEGMENTS = 32` and assume the discrete
|
|
32
|
+
approximation is still in use somewhere.
|
|
33
|
+
2. **Maintenance** — every time `ColorResolver` or the gradient model
|
|
34
|
+
changed, the dead code needed reading to confirm it didn't need
|
|
35
|
+
updating.
|
|
36
|
+
3. **Test surface** — the dead `render_gradient` had its own spec
|
|
37
|
+
using `instance_double(Idml::Parts::Graphic)`, violating the
|
|
38
|
+
no-doubles rule. Removing the method let us delete the offending
|
|
39
|
+
spec.
|
|
40
|
+
|
|
41
|
+
## Acceptance criteria
|
|
42
|
+
|
|
43
|
+
- [x] `GradientResolver` exposes only `.gradient?`.
|
|
44
|
+
- [x] No `render_gradient`, `interpolate`, `blend_between`, `blend`,
|
|
45
|
+
`SEGMENTS`, or `build` in the module.
|
|
46
|
+
- [x] Renderer code unchanged in behavior (still routes gradient fills
|
|
47
|
+
to pdfrb Shadings).
|
|
48
|
+
- [x] `bundle exec rake` green.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# TODO PDF 74: Renderer spec coverage
|
|
2
|
+
|
|
3
|
+
## Status: DONE
|
|
4
|
+
|
|
5
|
+
## What was implemented
|
|
6
|
+
|
|
7
|
+
Dedicated spec files for every renderer that previously had none.
|
|
8
|
+
Each spec uses real `Idml::Elements::*` instances (parsed from XML
|
|
9
|
+
fragments) or lightweight `Struct` doubles that mirror the renderer's
|
|
10
|
+
interface — no RSpec `double()` or `instance_double` anywhere.
|
|
11
|
+
|
|
12
|
+
New spec files:
|
|
13
|
+
|
|
14
|
+
- `spec/idml/render/polygon_renderer_spec.rb` — 4 examples covering
|
|
15
|
+
visibility, missing bounds, fill rendering, and stroke + cap.
|
|
16
|
+
- `spec/idml/render/graphic_line_renderer_spec.rb` — 4 examples
|
|
17
|
+
covering unstrokable skip, color-miss skip, basic line drawing,
|
|
18
|
+
and StrokeStyle application.
|
|
19
|
+
- `spec/idml/render/group_renderer_spec.rb` — 4 examples covering
|
|
20
|
+
empty group, child dispatch, item_transform concat, and
|
|
21
|
+
layer_filter visibility.
|
|
22
|
+
- `spec/idml/render/table_renderer_spec.rb` — 4 examples covering
|
|
23
|
+
visibility skip, empty rows skip, per-cell rectangle count, and
|
|
24
|
+
stroke emission.
|
|
25
|
+
- `spec/idml/render/text_frame_renderer_spec.rb` — 3 examples
|
|
26
|
+
covering no-story skip, non-chain-head skip, and end-to-end BT/ET
|
|
27
|
+
emission against the sample-with-image fixture.
|
|
28
|
+
- `spec/idml/render/blending_spec.rb` — 11 examples (added earlier
|
|
29
|
+
in TODO 69).
|
|
30
|
+
- `spec/idml/render/stroke_style_spec.rb` — 13 examples (added
|
|
31
|
+
earlier in TODO 72).
|
|
32
|
+
- `spec/idml/render/rectangle_renderer_spec.rb` — 6 examples (added
|
|
33
|
+
earlier in TODO 68).
|
|
34
|
+
|
|
35
|
+
## Cleanups
|
|
36
|
+
|
|
37
|
+
- `render_helpers_spec.rb` — removed the `#render_gradient` test that
|
|
38
|
+
used `instance_double(Idml::Parts::Graphic)` (TODO 73).
|
|
39
|
+
- `rectangle_renderer_spec.rb` — replaced
|
|
40
|
+
`canvas.document.shadings.instance_variable_get(:@registry)` with
|
|
41
|
+
the public `shadings.registry` reader.
|
|
42
|
+
- `designmap_spec.rb` — replaced `parsed.send(actual_attr(attr))`
|
|
43
|
+
with an explicit case/when reader.
|
|
44
|
+
|
|
45
|
+
## Acceptance criteria
|
|
46
|
+
|
|
47
|
+
- [x] Every renderer in `lib/idml/render/renderers/` has a dedicated
|
|
48
|
+
spec file with at least 3 examples.
|
|
49
|
+
- [x] No `instance_double` or `double()` in render specs.
|
|
50
|
+
- [x] No `instance_variable_get` / `instance_variable_set` in render
|
|
51
|
+
specs.
|
|
52
|
+
- [x] No `.send(...)` in any spec.
|
|
53
|
+
- [x] `bundle exec rake` green (2350+ examples, 0 failures).
|