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 +4 -4
- data/.github/workflows/release.yml +5 -3
- data/TODO.pdf/101-paragraph-rules-borders.md +36 -39
- data/TODO.pdf/104-table-schema-completion.md +16 -6
- data/TODO.pdf/116-justification-last-line.md +31 -0
- data/TODO.pdf/117-endnote-support.md +45 -0
- data/TODO.pdf/13-cjk-text-layout.md +20 -4
- data/lib/idml/elements/character_style_range.rb +2 -0
- data/lib/idml/elements/endnote.rb +22 -0
- data/lib/idml/elements/endnote_range.rb +21 -0
- data/lib/idml/elements/paragraph_style_range.rb +2 -0
- data/lib/idml/elements/properties.rb +16 -3
- data/lib/idml/elements/story_inner.rb +5 -0
- data/lib/idml/elements/table.rb +369 -1
- data/lib/idml/elements/typed_value.rb +20 -0
- data/lib/idml/elements.rb +3 -0
- data/lib/idml/render/footnote.rb +10 -2
- data/lib/idml/render/paragraph_rules.rb +125 -0
- data/lib/idml/render/renderers/text_frame_renderer.rb +88 -11
- data/lib/idml/render/style_resolver.rb +92 -2
- data/lib/idml/text_engine/justifier.rb +16 -5
- data/lib/idml/text_engine/line_breaker.rb +11 -1
- data/lib/idml/text_engine/measurement.rb +40 -0
- data/lib/idml/text_engine.rb +2 -1
- data/lib/idml/version.rb +1 -1
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eb1bde51604723b865996a34842c6a5216b3c0ce4f55947902aa9dd92358ed25
|
|
4
|
+
data.tar.gz: a1e6ed580c3d0fee769287d3619c33fa6e18ed79ee332d1aa0a7976b1352b760
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 989af27c2ad974dac8d370e335e923b70727a0c32991fe67011c2231cd5b526fdf59e73d571df8423ae69e9e27a5fa03dd69cf94a732ec76138cb34188af0792
|
|
7
|
+
data.tar.gz: d3daaccd0d51e4a78b825f5ea2f7baf6a3c23c11df1c33eb793b649c23b18bac2255de8e3fc3e3e7d62addac871b77f20879931dac73d6f14fec6fe54c10caea
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
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:
|
|
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
|
-
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
|
-
- [
|
|
41
|
-
|
|
42
|
-
- [
|
|
43
|
-
- [
|
|
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:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
## Status: COMPLETE — implemented 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 —
|
|
4
|
-
measurement and
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
6
|
-
#
|
|
7
|
-
#
|
|
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
|