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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 02ab670c36492471dd8dd986380fd180826e9ce250d5e22dde6fb5fda18b9bd3
4
- data.tar.gz: 73d2c392dee732f12b1551a445f5e1a866a551885742adf3552d81abe0c3e9d4
3
+ metadata.gz: e9833da9d4a8ddda74890980eab76f2a47b03cdb99977730fb1dee5d8809dd68
4
+ data.tar.gz: 1d771cc2775f1071329d2b2dbab681cc46197546be27681fcba39ee9313f9a2b
5
5
  SHA512:
6
- metadata.gz: b5c44e670ba295c964c6b58f6cecab0dcde43b579d4e4eb282e2d5b66cf567df34f943377a84510df7edd84d831892b3f3d6e13d7a4b13eb5311058377b94494
7
- data.tar.gz: d1692d7d0ed411358d2ed877497abe2838fe25d2a1aa6a9fc534561b116743fd0a4e279819ea0206b8dfb5ecaf29c8f806e4341cca941267a2c2dc71193dbd9e
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.6)
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.6)
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: BLOCKED (pdfrb has no subsetting API)
4
-
5
- ## Goal
6
-
7
- Subset embedded TrueType fonts to include only the glyphs actually used
8
- in the document. This dramatically reduces PDF file size (from full font
9
- ~500KB to subset ~5-20KB per font).
10
-
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)
42
-
43
- - [ ] Pipeline collects all Unicode codepoints used across all stories.
44
- - [ ] For each embedded font, only the used glyphs are included.
45
- - [ ] FontFile2 contains a subsetted font table (not the full font).
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: BLOCKED (pdfrb measurement APIs are stubs)
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`. Eliminates the Fontisan dependency for text layout.
9
+ and `#metrics_for`.
10
10
 
11
- ## Blocker
11
+ ## What pdfrb 0.4.0 provides
12
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:
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
- `/Users/mulgogi/src/claricle/pdfrb/lib/pdfrb/document/fonts.rb`:
23
+ ## What still needs pdfrb work
17
24
 
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
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
- def glyph_width(_char, _resource)
26
- 500
27
- end
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
- def metrics_for(_resource)
30
- nil
31
- end
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
- 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.
39
+ ## Plan (after pdfrb unblocks)
37
40
 
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
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 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.
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 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.
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 0.19.0 feature integration
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
- instead of N separate `canvas.text` calls per line. Single batch
9
- call for all lines within a run.
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 stubs in `Fonts#measure_text` (returns
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
- 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.
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 a stub)
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 returns a
16
- stub `length * 0.5 * size`. Without accurate advance, runs overwrite
17
- each other on the line.
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:65`:
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
- # TODO: use font metrics for per-glyph width lookup
27
- text.to_s.length * (size || 0).to_f * 0.5
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
- `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.
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.text_lines(..., rich: true)`
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 0.4.0 `Fonts#measure_text` returns real per-glyph widths.
51
- - pdfrb 0.4.0 `Fonts#glyph_width` not returning stub 500.
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: PLANNED (not started)
4
-
5
- ## Goal
6
-
7
- Honor IDML stroke styling attributes by routing through pdfrb 0.4.0's
8
- stroke-style Canvas setters:
9
-
10
- - `line_width=` (already used TODO 31)
11
- - `line_cap=` RoundButt/RoundJoin cap shape
12
- - `line_join=` miter/round/bevel corner joining
13
- - `miter_limit=` — clamps spikes on sharp angles
14
- - `dash_pattern=` dashed/dotted stroke styles
15
-
16
- ## Background
17
-
18
- pdfrb 0.4.0 exposes the full PDF graphics-state stroke suite. IDML
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
- - [ ] Dashed strokes render with correct dash array.
51
- - [ ] Dotted strokes render as zero-length dashes with round caps.
52
- - [ ] Round/miter/bevel joins produce matching PDF output.
53
- - [ ] Plain strokes (no style) render exactly as before.
54
- - [ ] Spec covers dashed, dotted, round-join, and pass-through cases.
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 — all confirmed in pdfrb 0.4.0).
60
- - IDML element models for stroke styles (DONE — already generated from RNC).
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).