idml 0.6.1 → 0.6.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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +1 -3
  3. data/.gitignore +1 -0
  4. data/Gemfile +2 -1
  5. data/TODO.pdf/01-font-metrics.md +6 -0
  6. data/TODO.pdf/02-font-resolver.md +5 -0
  7. data/TODO.pdf/03-text-shaper.md +4 -0
  8. data/TODO.pdf/04-line-breaker-justifier.md +4 -0
  9. data/TODO.pdf/05-vertical-layout.md +4 -0
  10. data/TODO.pdf/06-pdf-color-operators.md +5 -0
  11. data/TODO.pdf/07-pdf-path-operators.md +5 -0
  12. data/TODO.pdf/08-pdf-text-operators.md +5 -0
  13. data/TODO.pdf/09-image-embedding.md +5 -0
  14. data/TODO.pdf/10-spread-renderer.md +5 -0
  15. data/TODO.pdf/100-character-level-styling.md +61 -0
  16. data/TODO.pdf/101-paragraph-rules-borders.md +38 -0
  17. data/TODO.pdf/102-anchored-objects.md +41 -0
  18. data/TODO.pdf/103-drop-caps.md +33 -0
  19. data/TODO.pdf/104-table-schema-completion.md +44 -0
  20. data/TODO.pdf/105-whole-package-round-trip.md +45 -0
  21. data/TODO.pdf/106-page-item-type-coverage.md +62 -0
  22. data/TODO.pdf/107-image-rendering-unification.md +42 -0
  23. data/TODO.pdf/11-font-embedding.md +6 -0
  24. data/TODO.pdf/12-idml-to-pdf-pipeline.md +6 -0
  25. data/TODO.pdf/13-cjk-text-layout.md +5 -0
  26. data/TODO.pdf/14-page-item-element-models.md +5 -0
  27. data/TODO.pdf/15-wire-spread-children.md +5 -0
  28. data/TODO.pdf/16-typed-model-pipeline.md +5 -0
  29. data/TODO.pdf/17-shape-rendering.md +5 -0
  30. data/TODO.pdf/18-text-rendering.md +5 -0
  31. data/TODO.pdf/19-page-dimensions.md +4 -0
  32. data/TODO.pdf/20-png-image-support.md +4 -0
  33. data/TODO.pdf/21-coordinate-transform.md +5 -0
  34. data/TODO.pdf/22-color-resolution.md +5 -0
  35. data/TODO.pdf/23-fontmetrics-ps-name-fix.md +5 -0
  36. data/TODO.pdf/24-antipattern-coverage.md +5 -0
  37. data/TODO.pdf/25-cli-render.md +4 -0
  38. data/TODO.pdf/26-renderer-registry.md +5 -0
  39. data/TODO.pdf/27-text-engine-integration.md +6 -0
  40. data/TODO.pdf/28-path-geometry-models.md +6 -0
  41. data/TODO.pdf/29-character-style-extraction.md +5 -0
  42. data/TODO.pdf/30-shape-geometry-rendering.md +6 -0
  43. data/TODO.pdf/31-stroke-rendering.md +6 -0
  44. data/TODO.pdf/32-master-spread-rendering.md +6 -0
  45. data/TODO.pdf/33-font-resolution-from-idml.md +7 -0
  46. data/TODO.pdf/34-story-threading.md +5 -0
  47. data/TODO.pdf/35-pdf-metadata.md +6 -0
  48. data/TODO.pdf/36-performance-caching.md +5 -0
  49. data/TODO.pdf/37-round-trip-specs.md +5 -0
  50. data/TODO.pdf/38-pdf-output-validation.md +5 -0
  51. data/TODO.pdf/41-graphic-line-rendering.md +5 -0
  52. data/TODO.pdf/42-group-transform-application.md +5 -0
  53. data/TODO.pdf/43-text-frame-insets.md +6 -0
  54. data/TODO.pdf/44-layer-support.md +6 -0
  55. data/TODO.pdf/45-image-clipping.md +4 -0
  56. data/TODO.pdf/46-spread-to-page-separation.md +5 -0
  57. data/TODO.pdf/47-pdf-bookmarks.md +6 -0
  58. data/TODO.pdf/48-table-rendering.md +7 -0
  59. data/TODO.pdf/49-gradient-fill-rendering.md +6 -0
  60. data/TODO.pdf/50-pdf-a-compliance.md +6 -0
  61. data/TODO.pdf/68-radial-gradient-shadings.md +4 -0
  62. data/TODO.pdf/69-transparency-blend-modes.md +6 -0
  63. data/TODO.pdf/94-text-frame-vertical-layout.md +63 -0
  64. data/TODO.pdf/95-paragraph-spacing-indents.md +50 -0
  65. data/TODO.pdf/96-text-frame-insets-actual.md +39 -0
  66. data/TODO.pdf/97-table-cell-rendering-fidelity.md +62 -0
  67. data/TODO.pdf/98-rowspan-columnspan.md +35 -0
  68. data/TODO.pdf/99-table-column-widths.md +40 -0
  69. data/TODO.pdf/README.md +6 -9
  70. data/idml.gemspec +2 -1
  71. data/lib/idml/render/font_setup.rb +9 -0
  72. data/lib/idml/version.rb +1 -1
  73. metadata +39 -8
  74. data/Gemfile.lock +0 -235
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5bb1c7b34977858469e94bb4c827fa29345a7e47bcecf9ccce4d5c23b318b6e4
4
- data.tar.gz: d0e08d56d56d65903db2871b7de5ec1c6178e60ed6d2f79aa7370e10bef535de
3
+ metadata.gz: 46b15814f26a45f5df5c3170df66846ff831727ce3dcf4b7ebe1b1c0377d8c0e
4
+ data.tar.gz: d4041eacffab33e437330116d3b1268577b6ad8afbc0f2ee4cc1e015c07ec3a4
5
5
  SHA512:
6
- metadata.gz: 1a89f6c00cf82b3337cb6b4df486276cf27b9d1dceb8faa47ead1942b27ebf2ba758dcb135d75f9e0f6dcb973e8ee15e6483c322efde055707cbc7f6749a1841
7
- data.tar.gz: 8cf5bfff805beab37073490eb96788daed1832a756a86a4dfc980f5851cf8a6df941b3751f67aef4da5567f728ef4988b43fb8af08d6109b07ae17fed45bacc2
6
+ metadata.gz: 45e7dcb9c7bad255b0bc4fbe2e97e741c7a1b1ff52a6bf899ffdffdf838f310780c1e90832b54f9cbd5a0044b7b908f9c3791fd8274b4272fe1f40506b0ce793
7
+ data.tar.gz: b928db59ed416b59431bb638e1d86630488247dff7a3aaa6361954029920d3cb053095fdba28d7d7a00ec386e1b7f6153c89a7478135ad44523a3cb000cba840
@@ -19,9 +19,7 @@ jobs:
19
19
  uses: metanorma/ci/.github/workflows/rubygems-release.yml@main
20
20
  with:
21
21
  next_version: ${{ github.event.inputs.next_version }}
22
- release_command: |
23
- bundle install
24
- bundle exec rake release
25
22
  secrets:
26
23
  rubygems-api-key: ${{ secrets.METANORMA_CI_RUBYGEMS_API_KEY }}
27
24
  pat_token: ${{ secrets.METANORMA_CI_PAT_TOKEN }}
25
+
data/.gitignore CHANGED
@@ -11,6 +11,7 @@
11
11
  # Bundler
12
12
  /.bundle/
13
13
  /vendor/bundle/
14
+ /Gemfile.lock
14
15
 
15
16
  # Ruby build artifacts
16
17
  *.gem
data/Gemfile CHANGED
@@ -4,8 +4,9 @@ source "https://rubygems.org"
4
4
 
5
5
  gemspec
6
6
 
7
+ gem "logger"
7
8
  gem "lutaml-model", "~> 0.8.0"
8
- gem "pdfrb", path: "/Users/mulgogi/src/claricle/pdfrb"
9
+ gem "pdfrb", github: "claricle/pdfrb", ref: "629c72a"
9
10
  gem "rake"
10
11
  gem "rspec"
11
12
  gem "rubocop"
@@ -1,5 +1,11 @@
1
1
  # TODO PDF 01: Font metrics reader
2
2
 
3
+ ## Status: DONE — Superseded by `Idml::TextEngine::PdfrbFontMetrics`, which
4
+ delegates glyph metrics to pdfrb's `Fonts` collection. See
5
+ `lib/idml/text_engine/pdfrb_font_metrics.rb`. (Original plan to use
6
+ `ttfunk` directly was dropped when pdfrb 0.4.0+ exposed a typed
7
+ Font/Fonts API.)
8
+
3
9
  ## Goal
4
10
 
5
11
  Read OpenType/TrueType font files and expose per-glyph advance widths
@@ -1,5 +1,10 @@
1
1
  # TODO PDF 02: Font resolver
2
2
 
3
+ ## Status: DONE — `Idml::Render::FontSetup` resolves the document default
4
+ font from `Resources/Fonts.xml`, with `FontReferenceResolver` providing
5
+ the family → PostScriptName lookup. Per-run resolution lives in
6
+ `TextFrameRenderer#font_for_run`. See TODOs 33, 40, 93.
7
+
3
8
  ## Goal
4
9
 
5
10
  Map IDML font references (family + style) to .ttf/.otf file paths on
@@ -1,5 +1,9 @@
1
1
  # TODO PDF 03: Text shaper
2
2
 
3
+ ## Status: DONE — `Idml::TextEngine::Shaper` produces `ShapedGlyph`
4
+ structs (codepoint, width, x/y offsets) consumed by LineBreaker.
5
+ See `lib/idml/text_engine/shaper.rb`.
6
+
3
7
  ## Goal
4
8
 
5
9
  Convert a text string + font + size into a sequence of positioned
@@ -1,5 +1,9 @@
1
1
  # TODO PDF 04: Line breaker + justifier
2
2
 
3
+ ## Status: DONE — `Idml::TextEngine::LineBreaker` (greedy wrap) and
4
+ `Idml::TextEngine::Justifier` (left/center/right/justified) live in
5
+ `lib/idml/text_engine/{line_breaker,justifier}.rb`.
6
+
3
7
  ## Goal
4
8
 
5
9
  Break shaped text into lines that fit within a frame width, then
@@ -1,5 +1,9 @@
1
1
  # TODO PDF 05: Vertical layout
2
2
 
3
+ ## Status: DONE — `Idml::TextEngine::VerticalLayout` positions lines
4
+ within frame insets and applies leading + paragraph spacing.
5
+ Drop caps remain a stretch goal. See `lib/idml/text_engine/vertical_layout.rb`.
6
+
3
7
  ## Goal
4
8
 
5
9
  Position lines vertically within a text frame: leading, paragraph
@@ -1,5 +1,10 @@
1
1
  # TODO PDF 06: PDF color operators
2
2
 
3
+ ## Status: DONE — Colors route through pdfrb's Canvas (`fill_color`,
4
+ `stroke_color`) via `Render::ColorResolver`, which converts IDML RGB /
5
+ CMYK / Tint values to pdfrb's color tuples. See
6
+ `lib/idml/render/color_resolver.rb`.
7
+
3
8
  ## Goal
4
9
 
5
10
  Map IDML color definitions (from Graphic.xml) to PDF color space
@@ -1,5 +1,10 @@
1
1
  # TODO PDF 07: PDF path operators
2
2
 
3
+ ## Status: DONE — Path operators are emitted by pdfrb's Canvas
4
+ (`rectangle`, `move_to`, `line_to`, `close_path`, `fill`, `stroke`).
5
+ Renderer classes under `lib/idml/render/renderers/` build the calls
6
+ from typed `PathGeometry` models.
7
+
3
8
  ## Goal
4
9
 
5
10
  Map IDML geometric page items (Rectangle, Polygon, GraphicLine, etc.)
@@ -1,5 +1,10 @@
1
1
  # TODO PDF 08: PDF text operators
2
2
 
3
+ ## Status: DONE — `Render::TextFrameRenderer` emits text via
4
+ `canvas.text_rich` / `canvas.text` (pdfrb wraps BT/Tf/Td/Tj/TJ).
5
+ Per-run font selection through `font_for_run`. See
6
+ `lib/idml/render/renderers/text_frame_renderer.rb`.
7
+
3
8
  ## Goal
4
9
 
5
10
  Map positioned glyphs from the text engine to PDF text-showing
@@ -1,5 +1,10 @@
1
1
  # TODO PDF 09: Image embedding
2
2
 
3
+ ## Status: DONE — `Render::ImageCollector` resolves Link `href`s and
4
+ delegates to `PdfrbWriter#draw_image_matrix` (handles JPEG/PNG/etc.
5
+ via pdfrb's image embedding). Missing files are silently skipped.
6
+ See `lib/idml/render/image_collector.rb`.
7
+
3
8
  ## Goal
4
9
 
5
10
  Resolve IDML image links and embed them as PDF XObject images.
@@ -1,5 +1,10 @@
1
1
  # TODO PDF 10: Spread renderer
2
2
 
3
+ ## Status: DONE — `Render::SpreadRenderer` walks each Page's items in
4
+ z-order, dispatching via `PageItemRenderer::RENDERER_MAP`. Master items
5
+ rendered first as background. See `lib/idml/render/spread_renderer.rb`
6
+ and TODO 32.
7
+
3
8
  ## Goal
4
9
 
5
10
  Render an IDML Spread (all its page items) into a single PDF page's
@@ -0,0 +1,61 @@
1
+ # TODO PDF 100: Character-level styling (underline, strikethrough, caps, color)
2
+
3
+ ## Status: OPEN — gap identified in 2026-08-08 audit
4
+
5
+ ## Problem
6
+
7
+ `Elements::CharacterStyleRange` carries 284 character-level
8
+ attributes from the RNC. `StyleResolver::StyledRun` extracts only:
9
+ `text`, `font_style`, `point_size`, `fill_color`, `fill_tint`,
10
+ `applied_font`, `alignment`.
11
+
12
+ Untyped into the renderer (but modeled on CSR):
13
+
14
+ | Attribute | Visual effect |
15
+ |-------------------|------------------------------------------------|
16
+ | `Underline` | underline rule under glyphs |
17
+ | `UnderlineOffset`, `UnderlineWeight`, `UnderlineTint`, `UnderlineColor` | fine underline control |
18
+ | `StrikeThru` | strike-through rule through glyphs |
19
+ | `StrikeThruOffset`, `StrikeThruWeight`, etc. | fine strike control |
20
+ | `Capitalization` | AllCaps / SmallCaps / Title |
21
+ | `Position` | Superscript / Subscript |
22
+ | `HorizontalScale`, `VerticalScale` | glyph scaling |
23
+ | `BaselineShift` | vertical offset from baseline |
24
+ | `Skew` | synthetic italic (glyph shear) |
25
+ | `Tracking` | inter-letter spacing offset |
26
+ | `FillColor` / `FillTint` | text color (currently extracted but unused) |
27
+ | `StrokeColor` / `StrokeWeight` | text outline |
28
+ | `Ligatures` | enable OpenType ligatures |
29
+ | `KerningMethod` | metrics vs optical kerning |
30
+
31
+ Today the renderer emits a single `canvas.text` per line with a
32
+ single font + size — no underline, no strike, no color, no scaling.
33
+
34
+ ## What needs to happen
35
+
36
+ 1. Extend `StyledRun` to carry these character-level attributes.
37
+ 2. After emitting each line's text, optionally:
38
+ - Draw underline rule (`canvas.line` from start-x to start-x+width
39
+ at baseline - underline_offset, with underline_weight).
40
+ - Draw strike-through rule (similar, at baseline + offset).
41
+ 3. Apply capitalization by uppercasing the text (or using SmallCaps
42
+ OpenType feature when supported by the font).
43
+ 4. Apply Position (super/subscript) by scaling the font size and
44
+ shifting baseline.
45
+ 5. Apply HorizontalScale / VerticalScale via canvas text matrix
46
+ (`canvas.text` may need to be augmented, or emit raw cm + Tm).
47
+ 6. Apply text fill color via `canvas.fill_color` before `canvas.text`.
48
+ 7. Apply Tracking by inserting extra width per glyph (Shaper level).
49
+
50
+ ## Acceptance criteria
51
+
52
+ - [ ] CSR with Underline=true draws a rule under each glyph in the run.
53
+ - [ ] CSR with Capitalization="AllCaps" uppercases the text.
54
+ - [ ] CSR with Position="Superscript" raises the baseline and shrinks size.
55
+ - [ ] CSR with FillColor="Color/Red" renders red text.
56
+ - [ ] CSR with HorizontalScale=150 widens glyphs by 50%.
57
+
58
+ ## Dependencies
59
+
60
+ - TODO 94 (VerticalLayout delegation positions glyphs).
61
+ - pdfrb may need text-matrix helpers for skew/scale.
@@ -0,0 +1,38 @@
1
+ # TODO PDF 101: Paragraph borders and rules (RuleAbove, RuleBelow, ParagraphBorder)
2
+
3
+ ## Status: OPEN — gap identified in 2026-08-08 audit
4
+
5
+ ## Problem
6
+
7
+ `ParagraphStyleRange` carries:
8
+
9
+ - `RuleAbove`, `RuleAboveLineWeight`, `RuleAboveTint`, `RuleAboveOffset`,
10
+ `RuleAboveLeftIndent`, `RuleAboveRightIndent`, `RuleAboveWidth`
11
+ - `RuleBelow`, `RuleBelowLineWeight`, `RuleBelowTint`, `RuleBelowOffset`,
12
+ `RuleBelowLeftIndent`, `RuleBelowRightIndent`, `RuleBelowWidth`
13
+ - `ParagraphBorderOn`, `ParagraphBorderTopLineWeight`,
14
+ `ParagraphBorderLeftLineWeight`, etc.
15
+ - `ParagraphShadingOn`, `ParagraphShadingTint`, `ParagraphShadingColor`
16
+
17
+ None of these are rendered today. Real-world documents use RuleAbove
18
+ heavily for headings ("section divider line"), and ParagraphShading
19
+ for callouts.
20
+
21
+ ## What needs to happen
22
+
23
+ 1. After laying out a paragraph, emit RuleAbove as a horizontal line
24
+ above the first line, with the rule's weight/tint/color/offset/indent.
25
+ 2. Emit RuleBelow similarly below the last line.
26
+ 3. Optionally emit ParagraphBorder as a rectangle around the paragraph.
27
+ 4. Optionally emit ParagraphShading as a fill behind the paragraph.
28
+
29
+ ## Acceptance criteria
30
+
31
+ - [ ] PSR with RuleAbove=true and RuleAboveLineWeight=2 draws a 2pt
32
+ horizontal line above the paragraph.
33
+ - [ ] PSR with RuleAboveOffset=4 leaves 4pt gap between rule and text.
34
+ - [ ] PSR with ParagraphShadingOn=true and tint fills the paragraph rect.
35
+
36
+ ## Dependencies
37
+
38
+ - TODO 94 (VerticalLayout knows the paragraph's rect).
@@ -0,0 +1,41 @@
1
+ # TODO PDF 102: Anchored objects (anchored object settings)
2
+
3
+ ## Status: OPEN — gap identified in 2026-08-08 audit
4
+
5
+ ## Problem
6
+
7
+ `Elements::AnchoredObjectSetting` is modeled with the standard
8
+ attributes (AnchorSpot, AnchorXunit, AnchorYunit, AnchorPoint,
9
+ AnchorSpaceBefore, etc.). The renderer doesn't consult it.
10
+
11
+ In IDML, an anchored object is a page item (Rectangle, Polygon,
12
+ Group) that's "anchored" to a position within a story's text flow.
13
+ When the text reflows, the anchored object moves with it. Today
14
+ the renderer treats anchored objects as standalone page items in
15
+ the Spread — they appear at their spread coordinates, not their
16
+ anchored position.
17
+
18
+ ## What needs to happen
19
+
20
+ 1. Detect when a page item has an `AnchoredObjectSetting` child
21
+ with `AnchorType` indicating inline/anchored (vs. inline-only
22
+ or floating).
23
+ 2. For anchored items, suppress standalone rendering in the spread.
24
+ 3. When laying out the story that owns the anchored object, reserve
25
+ space at the anchor point and render the item there.
26
+
27
+ This is a significant feature — anchored objects are common in
28
+ real-world documents but rare in test fixtures.
29
+
30
+ ## Acceptance criteria
31
+
32
+ - [ ] Page item with AnchoredObjectSetting AnchorType="Inline" renders
33
+ at the anchor character's position within the text flow.
34
+ - [ ] Page item with AnchorType="Above Line" renders above the line
35
+ containing the anchor.
36
+ - [ ] Page item with AnchorType="Anchored" renders at the absolute
37
+ position derived from AnchorXunit / AnchorYunit.
38
+
39
+ ## Dependencies
40
+
41
+ - TODO 94 (need to know character positions in the text flow).
@@ -0,0 +1,33 @@
1
+ # TODO PDF 103: Drop caps (DropCapLines, DropCapCharacters)
2
+
3
+ ## Status: OPEN — gap identified in 2026-08-08 audit
4
+
5
+ ## Problem
6
+
7
+ `ParagraphStyleRange` carries:
8
+
9
+ - `DropCapLines` (integer) — number of lines the drop cap spans
10
+ - `DropCapCharacters` (integer) — number of characters in the drop cap
11
+ - `DropcapDetail` (integer) — bitmap of effects
12
+
13
+ The renderer ignores these. Documents that use drop caps (very
14
+ common in magazine/book layouts) currently render with no drop cap.
15
+
16
+ ## What needs to happen
17
+
18
+ 1. When `DropCapCharacters > 0` and `DropCapLines > 1`, treat the
19
+ first `DropCapCharacters` characters as a drop cap:
20
+ - Increase their font size to `DropCapLines × line_height`.
21
+ - Wrap subsequent lines around the drop cap's width.
22
+ 2. This requires the layout to track the drop-cap region and
23
+ indent lines 1..DropCapLines on the left.
24
+
25
+ ## Acceptance criteria
26
+
27
+ - [ ] PSR with DropCapLines=3, DropCapCharacters=1 renders the first
28
+ character at 3× line height.
29
+ - [ ] Lines 2 and 3 are indented right by the drop cap's width.
30
+
31
+ ## Dependencies
32
+
33
+ - TODO 94 (VerticalLayout needs to support drop-cap regions).
@@ -0,0 +1,44 @@
1
+ # TODO PDF 104: Schema-faithful Table model completion
2
+
3
+ ## Status: OPEN — gap identified in 2026-08-08 audit
4
+
5
+ ## Problem
6
+
7
+ `Elements::Table` was added (TODO 84) as a schema-faithful model,
8
+ but the audit shows several Table-level attributes from
9
+ `Table_Object` in the RNC are missing from the model:
10
+
11
+ - `TableDirection` (LTR / RTL)
12
+ - `TableDrawingOrder`
13
+ - `TableFlavor`
14
+ - `Spacing` (cell spacing)
15
+ - `StrokeOrder` (row-then-col vs col-then-row)
16
+ - `TopBorderWeight`, `LeftBorderWeight`, etc. (table outer border)
17
+ - `ColumnCount`, `BodyRowCount`, `HeaderRowCount`, `FooterRowCount`
18
+ - `TableStride` (column stride for spanning)
19
+ - `SkipHeader` / `SkipFooter` (alternating-page behavior)
20
+ - `TableLocked` / `TableResizePreference`
21
+ - `AppliedTableStyle`
22
+
23
+ The renderer can't emit correct outer borders or honor table-level
24
+ direction without these attributes being modeled.
25
+
26
+ ## What needs to happen
27
+
28
+ 1. Generate the missing attributes from `Table_Object` in
29
+ `reference-docs/schemas/package/Stories/Story.rnc` via
30
+ `scripts/rnc_to_lutaml.rb`.
31
+ 2. Add `column_attributes` child collection.
32
+ 3. Add specs asserting the full attribute set against the RNC.
33
+
34
+ ## Acceptance criteria
35
+
36
+ - [ ] Every attribute listed in `Table_Object` in Story.rnc is
37
+ declared on `Elements::Table`.
38
+ - [ ] Round-trip: parse fixture Spread, re-serialize, equivalent.
39
+ - [ ] Renderer can read `column_count`, `body_row_count`,
40
+ `header_row_count`, etc. for layout.
41
+
42
+ ## Dependencies
43
+
44
+ - TODOs 97, 98, 99 (cell fidelity / spans / column widths).
@@ -0,0 +1,45 @@
1
+ # TODO PDF 105: Whole-package round-trip suite (byte-equivalent)
2
+
3
+ ## Status: OPEN — gap identified in 2026-08-08 audit
4
+
5
+ ## Problem
6
+
7
+ CLAUDE.md states:
8
+
9
+ > The gold standard is whole-package round-trip: unzip a fixture
10
+ > `.idml`, rebuild every part, rezipping, and assert byte-equivalence
11
+ > part-by-part (the `mimetype` member must remain stored/uncompressed
12
+ > and first in the archive).
13
+
14
+ Today there are round-trip specs for individual element classes
15
+ and per-part parse/serialize, but no spec that takes a complete
16
+ `.idml` fixture, rebuilds every part, rezips, and asserts
17
+ byte-equivalence.
18
+
19
+ The `sample-with-image` fixture is the canonical DOMVersion 21.5
20
+ fixture (validated clean against all 14 part schemas), so it's the
21
+ right target for this spec.
22
+
23
+ ## What needs to happen
24
+
25
+ 1. New spec `spec/idml/package/round_trip_spec.rb`:
26
+ - Open `spec/fixtures/sample-with-image/sample-with-image.idml`.
27
+ - For each part in the package, parse via the typed Parts class
28
+ and re-serialize.
29
+ - Assert each re-serialized part is XML-equivalent to the original
30
+ (canonicalize attribute order, whitespace).
31
+ 2. Re-zip the package into a temporary file.
32
+ 3. Assert: `mimetype` is the first entry and stored uncompressed
33
+ (`Zip::Entry::STORED`).
34
+ 4. Assert: every other entry matches the original byte-for-byte
35
+ after normalization.
36
+
37
+ ## Acceptance criteria
38
+
39
+ - [ ] All 14 parts of `sample-with-image.idml` round-trip XML-equivalent.
40
+ - [ ] Re-zipped package has `mimetype` first and stored.
41
+ - [ ] Spec covers both `sample-with-image` and `sample-with-table-more`.
42
+
43
+ ## Dependencies
44
+
45
+ - None — exercises existing typed parts.
@@ -0,0 +1,62 @@
1
+ # TODO PDF 106: Page-item type coverage — Ellipse, Path, EPS, WMF, PICT, HtmlItem
2
+
3
+ ## Status: OPEN — gap identified in 2026-08-08 audit
4
+
5
+ ## Problem
6
+
7
+ The IDML Spread schema (`reference-docs/schemas/package/Spreads/Spread.rnc`)
8
+ defines many page-item types. Today the codebase models and renders
9
+ six:
10
+
11
+ | Modeled & rendered | Notes |
12
+ |---|---|
13
+ | `Rectangle` | full support |
14
+ | `TextFrame` | full support |
15
+ | `Polygon` | full support |
16
+ | `GraphicLine` | full support |
17
+ | `Group` | full support |
18
+ | `Table` | full support (story-embedded) |
19
+
20
+ Schema types that are NOT modeled or NOT rendered:
21
+
22
+ | Type | Used for | Status |
23
+ |---|---|---|
24
+ | `Ellipse` | Circular/elliptical shapes | Not modeled in SpreadObject children, no renderer |
25
+ | `Image_Object` | Placed raster images | Handled via `ImageCollector` separately; not in RENDERER_MAP |
26
+ | `EPS_Object` | Placed EPS files | Not modeled, not rendered |
27
+ | `WMF_Object` | Windows Metafile (legacy) | Not modeled, not rendered |
28
+ | `PICT_Object` | Mac PICT (legacy) | Not modeled, not rendered |
29
+ | `Path_Object` | Compound paths (multi-path shapes) | Not modeled |
30
+ | `HtmlItem_Object` | Embedded HTML (InDesign 19+) | Not modeled, not rendered |
31
+ | `Movie_Object`, `Sound_Object` | Media placeholders | Not modeled, not rendered |
32
+ | `FormField_Object`, `SignatureField_Object` | Interactive forms | Not modeled, not rendered |
33
+
34
+ ## What needs to happen (priorities)
35
+
36
+ 1. **Ellipse** — model in `Elements::Ellipse` (very similar to Polygon;
37
+ just emit a polygon with many vertices, or use pdfrb's circle if it
38
+ exists). Add to SpreadObject children and RENDERER_MAP.
39
+ 2. **Image in RENDERER_MAP** — today images are extracted via
40
+ `ImageCollector` (regex-free, model-driven) but rendered in a
41
+ separate code path. Unify so the dispatcher sees Image like any
42
+ other page item.
43
+ 3. **EPS** — model `Elements::EPS`. For rendering, either:
44
+ - Embed the EPS as a PostScript XObject (PDF supports this), or
45
+ - Rasterize to PNG and embed (requires external tool).
46
+ 4. **Path** — model `Elements::Path` (compound shape). Renderer emits
47
+ multiple sub-paths within one `m..l..h` sequence.
48
+ 5. **HtmlItem** — out of scope for v1 (requires HTML rendering engine).
49
+ 6. **Movie/Sound/FormField** — out of scope for v1 (interactive elements).
50
+
51
+ ## Acceptance criteria
52
+
53
+ - [ ] SpreadObject has `ellipse` collection.
54
+ - [ ] `EllipseRenderer` registered in RENDERER_MAP.
55
+ - [ ] Image page items (when present as Spread children) render via the
56
+ standard PageItemRenderer dispatch path.
57
+ - [ ] EPS files embedded as PostScript XObjects (or skipped with a warning).
58
+ - [ ] Compound Path objects render with multiple sub-paths.
59
+
60
+ ## Dependencies
61
+
62
+ - None — independent feature work.
@@ -0,0 +1,42 @@
1
+ # TODO PDF 107: Image rendering unification via PageItemRenderer
2
+
3
+ ## Status: OPEN — gap identified in 2026-08-08 audit
4
+
5
+ ## Problem
6
+
7
+ Today images flow through a separate code path:
8
+
9
+ 1. `Pipeline#render_spread` calls `ImageCollector.new(...).collect(spread)`
10
+ to gather all image refs *before* rendering.
11
+ 2. `SpreadRenderer#render_images` emits each image as a flat layer at
12
+ the start of the page (before any shapes/text).
13
+ 3. `Rectangle` (etc.) renderers don't know about images embedded in
14
+ them — they just render the shape, and the image falls through
15
+ separately.
16
+
17
+ The IDML model has `Image_Object` as a first-class page item that
18
+ can appear:
19
+ - Directly in Spread children
20
+ - As a child of Rectangle (graphic content placed in a frame)
21
+ - As a child of Polygon (graphic content in a non-rectangular frame)
22
+ - As a child of Group
23
+
24
+ ## What needs to happen
25
+
26
+ 1. Register `ImageRenderer` in `PageItemRenderer::RENDERER_MAP`.
27
+ 2. Move image rendering from `SpreadRenderer#render_images` to the
28
+ standard dispatch — images render in z-order with other page items.
29
+ 3. When a Rectangle/Polygon/Group has an Image child, render the image
30
+ *clipped to the parent's PathGeometry* — that's the InDesign behavior.
31
+ 4. Deprecate `ImageCollector` once the model-driven path is complete.
32
+
33
+ ## Acceptance criteria
34
+
35
+ - [ ] Image as a direct Spread child renders via RENDERER_MAP dispatch.
36
+ - [ ] Image inside a Rectangle renders clipped to the Rectangle's bounds.
37
+ - [ ] Z-order preserved — later items render on top of earlier items.
38
+ - [ ] Existing image specs pass via the new code path.
39
+
40
+ ## Dependencies
41
+
42
+ - TODO 106 (page-item type coverage).
@@ -1,5 +1,11 @@
1
1
  # TODO PDF 11: Font embedding
2
2
 
3
+ ## Status: DONE — `FontSetup#register` loads the document font via
4
+ `PdfrbWriter#register_font` (passes `File.open(path, "rb")` as IO).
5
+ Subsetting happens via `PdfrbWriter#subset_fonts!`. See
6
+ `lib/idml/render/font_setup.rb`, `lib/idml/render/pdfrb_writer.rb`,
7
+ and TODOs 52, 62, 65.
8
+
3
9
  ## Goal
4
10
 
5
11
  Embed font subsets in the output PDF so the rendered text is
@@ -1,5 +1,11 @@
1
1
  # TODO PDF 12: IDML → PDF pipeline
2
2
 
3
+ ## Status: DONE — `Render::Pipeline` orchestrates Package → PDF:
4
+ metadata, fonts, spreads, hyperlinks, structure, bookmarks, subsetting.
5
+ `Idml::Render.render(package:, to:, ...)` and `idml render` CLI both
6
+ delegate here. See `lib/idml/render/pipeline.rb`, `lib/idml/render.rb`,
7
+ `lib/idml/cli.rb`, and TODOs 25, 64, 83.
8
+
3
9
  ## Goal
4
10
 
5
11
  Top-level pipeline: `Idml::Package` → `Pdfrb::Document` → PDF file.
@@ -1,5 +1,10 @@
1
1
  # TODO PDF 13: CJK text layout
2
2
 
3
+ ## Status: PARTIAL — `Idml::TextEngine::CjkLayout` handles CJK glyph
4
+ measurement and basic horizontal layout. Vertical writing mode,
5
+ kinsoku shori, tate-chu-yoko, and ruby remain stretch goals as
6
+ flagged in the original TODO. See `lib/idml/text_engine/cjk_layout.rb`.
7
+
3
8
  ## Goal
4
9
 
5
10
  Extend the text engine to handle CJK (Chinese, Japanese, Korean)
@@ -1,5 +1,10 @@
1
1
  # TODO PDF 14: Page-item element models
2
2
 
3
+ ## Status: DONE — `Idml::Elements::{Page,Rectangle,TextFrame,Image,Link,
4
+ Polygon,Group,GraphicLine}` all carry attributes from the RNC and are
5
+ registered under `Idml::Elements`. Round-trip and attribute-set specs
6
+ live under `spec/idml/elements/`.
7
+
3
8
  ## Goal
4
9
 
5
10
  Create typed `Lutaml::Model::Serializable` subclasses for every page-item
@@ -1,5 +1,10 @@
1
1
  # TODO PDF 15: Wire SpreadObject child elements
2
2
 
3
+ ## Status: DONE — `Idml::Elements::SpreadObject` declares typed child
4
+ collections and uses `ordered` mapping. `Idml::Parts::Spread#each_page_item`
5
+ yields every page item in document order. See
6
+ `lib/idml/elements/spread_object.rb`, `lib/idml/parts/spread.rb`.
7
+
3
8
  ## Goal
4
9
 
5
10
  Extend `Idml::Elements::SpreadObject` to expose its child page items as
@@ -1,5 +1,10 @@
1
1
  # TODO PDF 16: Typed-model rendering pipeline
2
2
 
3
+ ## Status: DONE — Pipeline iterates `package.spreads` (typed
4
+ `Parts::Spread`) and dispatches via `PageItemRenderer::RENDERER_MAP`.
5
+ No raw XML / regex in the render path. See TODOs 26, 83 and
6
+ `lib/idml/render/pipeline.rb`.
7
+
3
8
  ## Goal
4
9
 
5
10
  Replace the regex-based image extraction and hardcoded page dimensions
@@ -1,5 +1,10 @@
1
1
  # TODO PDF 17: Shape rendering
2
2
 
3
+ ## Status: DONE — `RectangleRenderer`, `PolygonRenderer`,
4
+ `GraphicLineRenderer` register in `PageItemRenderer::RENDERER_MAP`
5
+ and call pdfrb Canvas path operators. Colors via `ColorResolver`.
6
+ See `lib/idml/render/renderers/` and TODOs 26, 30.
7
+
3
8
  ## Goal
4
9
 
5
10
  Render IDML page-item shapes (Rectangle, Polygon, GraphicLine, Ellipse)
@@ -1,5 +1,10 @@
1
1
  # TODO PDF 18: Text rendering from stories
2
2
 
3
+ ## Status: DONE — `TextFrameRenderer` resolves `ParentStory` via
4
+ `Package#story_by_id`, walks PSR → CSR → Content, and renders styled
5
+ runs through the text engine. See
6
+ `lib/idml/render/renderers/text_frame_renderer.rb` and TODOs 27, 67.
7
+
3
8
  ## Goal
4
9
 
5
10
  Wire the text engine to actual IDML story content. Extract text from
@@ -1,5 +1,9 @@
1
1
  # TODO PDF 19: Page dimensions from IDML
2
2
 
3
+ ## Status: DONE — `Pipeline#page_dimensions_for` reads `Page#width` /
4
+ `Page#height` (falling back to US Letter) and passes them to
5
+ `writer.add_page`. See `lib/idml/render/pipeline.rb`.
6
+
3
7
  ## Goal
4
8
 
5
9
  Derive PDF page width and height from the IDML document's Page element
@@ -1,5 +1,9 @@
1
1
  # TODO PDF 20: PNG image support
2
2
 
3
+ ## Status: DONE — pdfrb's `Canvas#draw_image_matrix` handles JPEG, PNG,
4
+ and other formats natively (image-type detection is internal to pdfrb).
5
+ No format-specific code needed in idml.
6
+
3
7
  ## Goal
4
8
 
5
9
  Extend image embedding to support PNG files in addition to JPEG.