idml 0.8.9 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d58f00746f6afb4233e161dfa9cdeb116d714eed0c238ec543a0f039b4167f0e
4
- data.tar.gz: 1a000e34996525fe34cdc441625c9eaa303f121c836ae3f3b07b73c12712abb1
3
+ metadata.gz: eb1bde51604723b865996a34842c6a5216b3c0ce4f55947902aa9dd92358ed25
4
+ data.tar.gz: a1e6ed580c3d0fee769287d3619c33fa6e18ed79ee332d1aa0a7976b1352b760
5
5
  SHA512:
6
- metadata.gz: 15001cbf1427dc278c701cd4980b837ff4b015f6c5657eeb5c88c0c8774933934e606439c40fa3c900368ef2959fc1170cd12841a9caadc0c025a199dd18cd68
7
- data.tar.gz: 41f45b1b2db245e1dd2d5ef40aa60990fc3c957930b3d16a633974d1f267149e8b2c5cf638b7b3549b4318bf8ea62f378fbb5d0f98ad32d4d50fbce0e3cb9505
6
+ metadata.gz: 989af27c2ad974dac8d370e335e923b70727a0c32991fe67011c2231cd5b526fdf59e73d571df8423ae69e9e27a5fa03dd69cf94a732ec76138cb34188af0792
7
+ data.tar.gz: d3daaccd0d51e4a78b825f5ea2f7baf6a3c23c11df1c33eb793b649c23b18bac2255de8e3fc3e3e7d62addac871b77f20879931dac73d6f14fec6fe54c10caea
@@ -1,5 +1,8 @@
1
- # Auto-generated by Cimas: Do not edit it manually!
2
- # See https://github.com/metanorma/cimas
1
+ # Managed by Cimas; trusted-publishing change is intentional (2026-08-18):
2
+ # publish via RubyGems OIDC Trusted Publishing instead of the API key
3
+ # (the API-key path is blocked by rubygems.org MFA OTP demands).
4
+ # See https://github.com/metanorma/ci — the reusable workflow uses its
5
+ # OIDC path whenever the rubygems-api-key secret is not passed.
3
6
  name: release
4
7
 
5
8
  on:
@@ -20,6 +23,5 @@ jobs:
20
23
  with:
21
24
  next_version: ${{ github.event.inputs.next_version }}
22
25
  secrets:
23
- rubygems-api-key: ${{ secrets.METANORMA_CI_RUBYGEMS_API_KEY }}
24
26
  pat_token: ${{ secrets.METANORMA_CI_PAT_TOKEN }}
25
27
 
@@ -1,47 +1,44 @@
1
1
  # TODO PDF 101: Paragraph borders and rules (RuleAbove, RuleBelow, ParagraphBorder)
2
2
 
3
- ## Status: PARTIALRuleAbove and RuleBelow implemented via new
4
- `Render::ParagraphRules` helper; TextFrameRenderer emits them at
5
- paragraph top/bottom. Rule color comes from PSR#stroke_color (IDML's
6
- PSR doesn't have a dedicated RuleAboveColor attribute). RuleAboveWidth
7
- "Text" vs "Column" honored. Tint scaling + line weight + offset honored.
8
-
9
- ParagraphBorder and ParagraphShading remain open — they need the
10
- paragraph's bounding rect, which the renderer doesn't currently
11
- expose to ParagraphRules. Will add when a real document fixture
12
- exercises them.
13
-
14
- ## Problem
15
-
16
- `ParagraphStyleRange` carries:
17
-
18
- - `RuleAbove`, `RuleAboveLineWeight`, `RuleAboveTint`, `RuleAboveOffset`,
19
- `RuleAboveLeftIndent`, `RuleAboveRightIndent`, `RuleAboveWidth`
20
- - `RuleBelow`, `RuleBelowLineWeight`, `RuleBelowTint`, `RuleBelowOffset`,
21
- `RuleBelowLeftIndent`, `RuleBelowRightIndent`, `RuleBelowWidth`
22
- - `ParagraphBorderOn`, `ParagraphBorderTopLineWeight`,
23
- `ParagraphBorderLeftLineWeight`, etc.
24
- - `ParagraphShadingOn`, `ParagraphShadingTint`, `ParagraphShadingColor`
25
-
26
- None of these are rendered today. Real-world documents use RuleAbove
27
- heavily for headings ("section divider line"), and ParagraphShading
28
- for callouts.
29
-
30
- ## What needs to happen
31
-
32
- 1. After laying out a paragraph, emit RuleAbove as a horizontal line
33
- above the first line, with the rule's weight/tint/color/offset/indent.
34
- 2. Emit RuleBelow similarly below the last line.
35
- 3. Optionally emit ParagraphBorder as a rectangle around the paragraph.
36
- 4. Optionally emit ParagraphShading as a fill behind the paragraph.
3
+ ## Status: COMPLETE — implemented 2026-08-17
4
+
5
+ ## What was built
6
+
7
+ - RuleAbove / RuleBelow (earlier milestone): horizontal rules at
8
+ paragraph top/bottom with weight / tint / offset / indent /
9
+ TextWidth-ColumnWidth modes, via `Render::ParagraphRules`.
10
+ - Rule colors now resolve from `Properties > RuleAboveColor` /
11
+ `RuleBelowColor` (typed value elements) with StrokeColor fallback
12
+ modeled via `Elements::TypedValue` + Properties mapping, PSR/CSR
13
+ now carry `properties` collections.
14
+ - ParagraphShading: fill rect behind the paragraph block. Color from
15
+ `Properties > ParagraphShadingColor`, tint scaling, ParagraphShading
16
+ width mode (ColumnWidth default / TextWidth), four outward offsets.
17
+ `TextEngine::Measurement` pre-measures the paragraph's height so
18
+ the rect is known before the lines are laid out (clamped to the
19
+ frame's bottom limit when the paragraph splits).
20
+ - ParagraphBorder: per-side strokes around the same block rect, each
21
+ side with its own line weight and the border offsets/tint/color
22
+ (Properties > ParagraphBorderColor). Shading and border share one
23
+ extent so they always agree.
24
+
25
+ ## Known limitations
26
+
27
+ - Border corner radii and gap colors are parsed on the model but not
28
+ rendered; MergeConsecutiveParaBorders (merged rects across like-
29
+ bordered paragraphs) is not modeled.
30
+ - DisplayIfSplits: when a paragraph splits across frames, both
31
+ shading and border are drawn around the rendered portion only.
37
32
 
38
33
  ## Acceptance criteria
39
34
 
40
- - [ ] PSR with RuleAbove=true and RuleAboveLineWeight=2 draws a 2pt
41
- horizontal line above the paragraph.
42
- - [ ] PSR with RuleAboveOffset=4 leaves 4pt gap between rule and text.
43
- - [ ] PSR with ParagraphShadingOn=true and tint fills the paragraph rect.
35
+ - [x] PSR with RuleAbove=true and RuleAboveLineWeight=2 draws the
36
+ rule above the paragraph.
37
+ - [x] PSR with RuleAboveOffset=4 leaves a 4pt gap.
38
+ - [x] PSR with ParagraphShadingOn=true and tint fills the paragraph
39
+ rect.
40
+ - [x] ParagraphBorderOn draws per-side strokes.
44
41
 
45
42
  ## Dependencies
46
43
 
47
- - TODO 94 (VerticalLayout knows the paragraph's rect).
44
+ - TODO 94 (VerticalLayout knows the paragraph's rect) — satisfied.
@@ -1,11 +1,21 @@
1
1
  # TODO PDF 104: Schema-faithful Table model completion
2
2
 
3
- ## Status: PARTIALadded the rendering-relevant subset (column_count,
4
- single_column_width, header/body/footer row counts, table_direction,
5
- outer-border strokes). The remaining ~150 schema attributes (alternating
6
- stroke rows, column rules, table-flavor bits) aren't needed for
7
- rendering and would be pure model bloat. Add incrementally as use
8
- cases demand.
3
+ ## Status: COMPLETEimplemented 2026-08-18
4
+
5
+ All 171 Table_Object attributes from
6
+ `reference-docs/schemas/package/Stories/Story.rnc` are declared on
7
+ `Elements::Table` (the prior rendering-relevant subset plus the 155
8
+ remaining: border tints/gaps/overprints, default row/column strokes,
9
+ alternating fills, cell insets, diagonal lines, caption settings,
10
+ …), generated with `scripts/rnc_to_lutaml.rb` in schema order.
11
+
12
+ Drift defense: `spec/idml/elements/table_spec.rb` reads the RNC at
13
+ spec time and asserts a Table built with every schema attribute
14
+ round-trips every attribute — any attribute dropped from the model
15
+ fails the spec.
16
+
17
+ The renderer still consults only the rendering-relevant subset; the
18
+ rest are carried for round-trip and downstream-tool fidelity.
9
19
 
10
20
  ## Problem
11
21
 
@@ -0,0 +1,31 @@
1
+ # TODO PDF 116: Justified-text last-line handling
2
+
3
+ ## Status: COMPLETE — implemented 2026-08-18
4
+
5
+ ## Problem
6
+
7
+ `TextEngine::Justifier` spread slack across inter-word spaces for
8
+ `:justified` paragraphs on EVERY line, including each paragraph's
9
+ final line — InDesign (and standard typography) leaves the last line
10
+ of a justified paragraph ragged. Every fully-justified paragraph
11
+ rendered with its final line stretched across the full column width.
12
+
13
+ ## What was done
14
+
15
+ - `Justifier.justify` takes `last_line:`; when true, a `:justified`
16
+ line falls back to left alignment (no space stretching). All other
17
+ alignments unchanged.
18
+ - `TextFrameRenderer.layout_run` passes `last_line:` true for the
19
+ final line of the paragraph's FINAL run (`paragraph_last:` flag
20
+ threaded from `render_runs_for_paragraph`, which knows the run
21
+ list). Mid-paragraph run breaks still justify — only the true
22
+ paragraph-final line goes ragged.
23
+ - Footnote layout (`Render::Footnote.layout_paragraph`) applies the
24
+ same rule for the final line of the final run.
25
+
26
+ ## Known limitations
27
+
28
+ - Justification adjusts inter-word spaces only; letter spacing and
29
+ glyph scaling (Minimum/Maximum/DesiredGlyphScaling, tracking) are
30
+ not applied. ToBinding / RTL binding alignment remains mapped to
31
+ left.
@@ -0,0 +1,45 @@
1
+ # TODO PDF 117: Endnote support
2
+
3
+ ## Status: OPEN (modeling COMPLETE 2026-08-18; rendering OPEN pending
4
+ a real fixture)
5
+
6
+ ## Problem
7
+
8
+ IDML endnotes use a reference architecture unlike footnotes: the
9
+ main story carries `<Endnote Self="..." EndnoteTextRange="..."/>`
10
+ plus `<EndnoteRange SourceEndnote="..."/>` markers (both at Story
11
+ level, per Story.rnc), while the endnote TEXT lives in a separate
12
+ story flagged `IsEndnoteStory="true"`. Documents with endnotes
13
+ (scholarly publishing) currently drop endnote text entirely.
14
+
15
+ ## What is done (2026-08-18)
16
+
17
+ - `Elements::Endnote` (Self, EndnoteTextRange) and
18
+ `Elements::EndnoteRange` (Self, SourceEndnote) — schema-faithful
19
+ models, wired into `StoryInner` (`endnote`, `endnote_range`
20
+ collections). Round-trip + parse specs.
21
+ - `StoryInner#is_endnote_story` already parsed, so endnote stories
22
+ are identifiable.
23
+
24
+ ## Design for rendering (needs a real InDesign fixture to validate)
25
+
26
+ 1. Marker: story-level `Endnote`/`EndnoteRange` pairs delimit the
27
+ anchor range; marker position requires story text-offset
28
+ bookkeeping the extraction layer doesn't currently track (unlike
29
+ footnotes, which sit inside a CSR). A range-aware extraction is
30
+ the prerequisite.
31
+ 2. Endnote text: resolve `EndnoteTextRange` → the endnote story
32
+ (IsEndnoteStory) → its paragraphs.
33
+ 3. Placement: per EndnoteOption Scope — end of story / end of
34
+ section / end of document; end-of-story is the tractable default
35
+ (append after the referencing story's last paragraph).
36
+ 4. Numbering: endnotes number separately from footnotes
37
+ (FootnoteOption vs EndnoteOption StartAt/Prefix/Suffix — the
38
+ Endnote preference object is not yet modeled in Preferences).
39
+
40
+ ## Why rendering is deferred
41
+
42
+ The marker-position machinery (text-range resolution) cannot be
43
+ validated without a real fixture; blind implementation risks wrong
44
+ anchor placement on production files. Schema modeling + round-trip
45
+ is complete and verified.
@@ -1,9 +1,25 @@
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`.
3
+ ## Status: PARTIAL — horizontal-mode CJK complete (2026-08-17):
4
+ CJK glyph measurement, kinsoku shori, and ruby annotations render.
5
+ Vertical writing mode, tate-chu-yoko, and mojikumi remain OPEN
6
+ stretch goals they need a vertical (rotated) layout engine and
7
+ real vertical-mode fixtures to build against. See
8
+ `lib/idml/text_engine/cjk_layout.rb`.
9
+
10
+ ## Progress 2026-08-17
11
+
12
+ - Kinsoku shori is now LIVE in the pipeline: LineBreaker.break
13
+ post-processes any run containing CJK glyphs through
14
+ CjkLayout.apply_kinsoku, so no rendered line starts with
15
+ 、。,」etc. or ends with 「 etc. (previously the logic existed
16
+ but was never called).
17
+ - Ruby (phonetic annotations) render: CSR RubyString /
18
+ RubyFontSize / RubyPosition extract onto the run; the renderer
19
+ emits the annotation centered above the run's first line (half
20
+ the base size by default; Below* positions place it under).
21
+ Approximation: IDML attaches ruby to a character range; we
22
+ annotate the whole first line.
7
23
 
8
24
  ## Goal
9
25
 
@@ -298,6 +298,7 @@ module Idml
298
298
  attribute :content, Idml::Elements::Content, collection: true
299
299
  attribute :hyperlink_text_source, Idml::Elements::HyperlinkTextSource,
300
300
  collection: true
301
+ attribute :properties, Idml::Elements::Properties, collection: true
301
302
  attribute :table, Idml::Elements::Table, collection: true
302
303
  attribute :xml_element, Idml::Elements::XmlElement, collection: true
303
304
  attribute :footnote, Idml::Elements::Footnote, collection: true
@@ -664,6 +665,7 @@ module Idml
664
665
  map_element "CharacterStyleRange", to: :character_style_range
665
666
  map_element "Content", to: :content
666
667
  map_element "HyperlinkTextSource", to: :hyperlink_text_source
668
+ map_element "Properties", to: :properties
667
669
  map_element "Table", to: :table
668
670
  map_element "XMLElement", to: :xml_element
669
671
  map_element "Footnote", to: :footnote
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Idml
4
+ module Elements
5
+ # `<Endnote>` — a story-level endnote reference. The endnote's
6
+ # TEXT lives in a separate story flagged `IsEndnoteStory="true"`;
7
+ # this element points at it via `EndnoteTextRange` (schema:
8
+ # Endnote_Object in Stories/Story.rnc — Self plus the range
9
+ # reference; no inline content). Rendering design in
10
+ # TODO.pdf/117-endnote-support.md.
11
+ class Endnote < Lutaml::Model::Serializable
12
+ attribute :self_attr, :string
13
+ attribute :endnote_text_range, :string
14
+
15
+ xml do
16
+ root "Endnote"
17
+ map_attribute "Self", to: :self_attr
18
+ map_attribute "EndnoteTextRange", to: :endnote_text_range
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Idml
4
+ module Elements
5
+ # `<EndnoteRange>` — marks the text range in the main story that
6
+ # an endnote references, linking back via `SourceEndnote`
7
+ # (schema: EndnoteRange_Object in Stories/Story.rnc). Pairs with
8
+ # Elements::Endnote; rendering design in
9
+ # TODO.pdf/117-endnote-support.md.
10
+ class EndnoteRange < Lutaml::Model::Serializable
11
+ attribute :self_attr, :string
12
+ attribute :source_endnote, :string
13
+
14
+ xml do
15
+ root "EndnoteRange"
16
+ map_attribute "Self", to: :self_attr
17
+ map_attribute "SourceEndnote", to: :source_endnote
18
+ end
19
+ end
20
+ end
21
+ end
@@ -298,6 +298,7 @@ module Idml
298
298
  collection: true
299
299
  attribute :content, Idml::Elements::Content, collection: true
300
300
  attribute :xml_element, Idml::Elements::XmlElement, collection: true
301
+ attribute :properties, Idml::Elements::Properties, collection: true
301
302
 
302
303
  xml do
303
304
  root "ParagraphStyleRange"
@@ -651,6 +652,7 @@ module Idml
651
652
  map_element "ParagraphStyleRange", to: :paragraph_style_range
652
653
  map_element "Content", to: :content
653
654
  map_element "XMLElement", to: :xml_element
655
+ map_element "Properties", to: :properties
654
656
  end
655
657
 
656
658
  def text_content
@@ -2,18 +2,31 @@
2
2
 
3
3
  module Idml
4
4
  module Elements
5
- # `<Properties>` — a common child element on page items. Carries
6
- # optional PathBoundingBox, PathGeometry, and Label. Only the
7
- # modeled children are populated from XML; others are ignored.
5
+ # `<Properties>` — a common child element on page items and
6
+ # story style ranges. Carries optional PathBoundingBox,
7
+ # PathGeometry, Label, and the paragraph-decoration color value
8
+ # elements (RuleAboveColor, ParagraphShadingColor, …) used on
9
+ # PSR/CSR Properties. Only the modeled children are populated
10
+ # from XML; others are ignored.
8
11
  class Properties < Lutaml::Model::Serializable
9
12
  attribute :path_bounding_box, :string
10
13
  attribute :path_geometry, Idml::Elements::PathGeometry,
11
14
  collection: true
15
+ attribute :rule_above_color, Idml::Elements::TypedValue
16
+ attribute :rule_below_color, Idml::Elements::TypedValue
17
+ attribute :paragraph_shading_color, Idml::Elements::TypedValue
18
+ attribute :paragraph_border_color, Idml::Elements::TypedValue
19
+ attribute :paragraph_border_gap_color, Idml::Elements::TypedValue
12
20
 
13
21
  xml do
14
22
  root "Properties"
15
23
  map_element "PathBoundingBox", to: :path_bounding_box
16
24
  map_element "PathGeometry", to: :path_geometry
25
+ map_element "RuleAboveColor", to: :rule_above_color
26
+ map_element "RuleBelowColor", to: :rule_below_color
27
+ map_element "ParagraphShadingColor", to: :paragraph_shading_color
28
+ map_element "ParagraphBorderColor", to: :paragraph_border_color
29
+ map_element "ParagraphBorderGapColor", to: :paragraph_border_gap_color
17
30
  end
18
31
 
19
32
  def first_geometry
@@ -313,6 +313,9 @@ module Idml
313
313
  attribute :content, Idml::Elements::Content, collection: true
314
314
  attribute :xml_element, Idml::Elements::XmlElement, collection: true
315
315
  attribute :footnote, Idml::Elements::Footnote, collection: true
316
+ attribute :endnote, Idml::Elements::Endnote, collection: true
317
+ attribute :endnote_range, Idml::Elements::EndnoteRange,
318
+ collection: true
316
319
 
317
320
  xml do
318
321
  root "Story"
@@ -678,6 +681,8 @@ module Idml
678
681
  map_element "Content", to: :content
679
682
  map_element "XMLElement", to: :xml_element
680
683
  map_element "Footnote", to: :footnote
684
+ map_element "Endnote", to: :endnote
685
+ map_element "EndnoteRange", to: :endnote_range
681
686
  end
682
687
 
683
688
  def text_content