idml 0.10.13 → 0.10.14

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: 2f2a7b73c56645a87269780fc0cab9f998f5a6bf0d167b0ce9433dba9a8019cd
4
- data.tar.gz: e4bb1522273937a4c2cf5631de1927f4583f63d16f2b351f893921407d89dc88
3
+ metadata.gz: 00f5eb099cc78c69be52672e179bc0d2c10b102554bb74b28272b04d0ed904c6
4
+ data.tar.gz: c7936ca3c76fef90639d532cc70c0deb7423b2dcd9cd3d73ce6b88bff5a1257c
5
5
  SHA512:
6
- metadata.gz: 83756750d549a02ff7080686509ac45acce77405db6c9d3ba0ee6dae3643622b8eb89198d28fead77047dac50bdcf9491bdab05b7e03d02e18560f1021fa5360
7
- data.tar.gz: 8a9fc59030029b4342019b205f9fe7da46ae6296729d641c49c199391cd5765ffea195dfca6cde46ff0df6518f496dcf9a1b66dc5dad0b808503230be8057107
6
+ metadata.gz: 354bb00de55fe629d84853a8e1dfdc2c8b821f708fc15584cf9ac509ba3489a56f92f48f71698cfcf9b28c9f3814b4c0366f3a88d40aa3e2e05b33b242e20a60
7
+ data.tar.gz: 52cc665c20cdf7c8c766593e38fd18b847f15da0ac3a09b1accfd5dbbe2b48614a7eadb5452b8f4b295bff1870c7a2acb1a48a0c03174f15916f681d62c4a16c
@@ -0,0 +1,27 @@
1
+ # TODO PDF 152: RTL paragraph alignment
2
+
3
+ ## Status: COMPLETE — implemented 2026-08-31
4
+
5
+ ## Problem
6
+
7
+ ParagraphDirection (LeftToRightDirection /
8
+ RightToLeftDirection) was parsed but unused: RTL paragraphs
9
+ aligned left/right by LTR convention, so Arabic/Hebrew text sat
10
+ on the wrong edge.
11
+
12
+ ## Solution
13
+
14
+ `StyleResolver::Paragraph` carries `paragraph_direction` (PSR or
15
+ paragraph style); `TextEngine::Justifier` gains a `direction:`
16
+ argument that mirrors the visual alignment under
17
+ RightToLeftDirection — left↔right, start↔end; center and
18
+ justified unchanged. Both justify call sites (frame renderer and
19
+ footnote layout) pass the paragraph's direction.
20
+
21
+ ## Files
22
+
23
+ - `lib/idml/text_engine/justifier.rb`
24
+ - `lib/idml/render/style_resolver.rb`
25
+ - `lib/idml/render/renderers/text_frame_renderer.rb`
26
+ - `lib/idml/render/footnote.rb`
27
+ - `spec/idml/text_engine/text_engine_spec.rb`
@@ -0,0 +1,31 @@
1
+ # TODO PDF 153: Keep options in vertical writing
2
+
3
+ ## Status: COMPLETE (approximation) — implemented 2026-08-31
4
+
5
+ ## Problem
6
+
7
+ Keep options (TODOs 123/127/133) applied only to the horizontal
8
+ engine; vertical paragraphs split across frames regardless of
9
+ KeepAllLinesTogether / KeepWithNext.
10
+
11
+ ## Solution
12
+
13
+ `VerticalEmit.vertical_render` consults a keep check BEFORE
14
+ placing each paragraph (deferral after placement cannot un-emit):
15
+
16
+ - **KeepAllLinesTogether** defers the paragraph wholly to the
17
+ next frame when its column need exceeds the remaining columns.
18
+ - **KeepWithNext** also defers when the follower's forced break
19
+ or column need would strand it.
20
+ - Never defers the frame's first paragraph — the same progress
21
+ guarantee as KeepPolicy (shared: `KeepPolicy.paragraph_break?`).
22
+
23
+ Approximation: column need = runs count (one column per run),
24
+ which under-estimates runs spanning several columns — deferral
25
+ errs toward placing. KeepFirstLines/KeepLastLines windows remain
26
+ horizontal-only (they are line-window concepts).
27
+
28
+ ## Files
29
+
30
+ - `lib/idml/render/renderers/vertical_emit.rb`
31
+ - `spec/idml/render/vertical_render_spec.rb`
@@ -1,6 +1,6 @@
1
1
  # TODO PDF 61: Known limitations and future work
2
2
 
3
- ## Status: DOCUMENTED — refreshed 2026-08-28
3
+ ## Status: DOCUMENTED — refreshed 2026-08-31
4
4
 
5
5
  Previously listed limitations (font embedding, tagged PDF, per-run
6
6
  styling, dead code) were resolved by TODOs 52/53/55/67/76 long ago;
@@ -14,8 +14,9 @@ this list reflects the current state.
14
14
  wrap after explicit hyphens (TODO 139).
15
15
  - **Justification**: word/letter spacing caps apply (TODO 119),
16
16
  MaximumGlyphScaling stretches (TODO 129) and MinimumGlyphScaling
17
- compresses overlong lines (TODO 135); ToBinding / RTL binding
18
- alignment is not applied.
17
+ compresses overlong lines (TODO 135); RTL paragraphs mirror
18
+ left/right alignment (TODO 152); ToBinding / kashida-based
19
+ Arabic justification is not applied.
19
20
  - **Keep options**: KeepAllLinesTogether (TODO 123), KeepWithNext
20
21
  (TODO 127), and the KeepFirstLines / KeepLastLines windows
21
22
  (TODO 133, line-count approximation) are honored.
@@ -39,8 +40,9 @@ this list reflects the current state.
39
40
  Desired applies per class pair) are applied; Minimum/Maximum
40
41
  aki bounds and CompressionPriority are parsed but unused.
41
42
  - **Vertical mode**: Latin runs rotate as one group per segment
42
- (TODO 142); ruby placement is beside-column; keep options do
43
- not apply.
43
+ (TODO 142); ruby placement is beside-column; KeepAllLines /
44
+ KeepWithNext apply with a column-count approximation (TODO 153);
45
+ keep windows are horizontal-only.
44
46
 
45
47
  ### Structural features
46
48
  - **Endnotes** (TODO 117): reference markers render and endnote TEXT
@@ -62,5 +64,5 @@ this list reflects the current state.
62
64
  - Hyphenation dictionary integration
63
65
  - NextColumn wrap via column-jump chaining (multi-column frames)
64
66
  - Minimum/Maximum mojikumi aki bounds + CompressionPriority
65
- - ToBinding / RTL binding alignment
66
- - Vertical-mode keep options
67
+ - ToBinding / kashida Arabic justification
68
+ - Vertical keep windows (KeepFirstLines / KeepLastLines)
@@ -138,6 +138,7 @@ module Idml
138
138
  TextEngine::Justifier.justify(
139
139
  line: line, frame_width: wrap_width,
140
140
  alignment: paragraph.alignment || :left,
141
+ direction: paragraph.paragraph_direction,
141
142
  last_line: last_line?(paragraph, run, index, lines),
142
143
  limits: limits
143
144
  )
@@ -853,7 +853,8 @@ module Idml
853
853
  line: line, frame_width: wrap_width,
854
854
  alignment: paragraph.alignment || :left,
855
855
  last_line: paragraph_last && index == lines.length - 1,
856
- limits: limits
856
+ limits: limits,
857
+ direction: paragraph.paragraph_direction
857
858
  )
858
859
  end
859
860
  end
@@ -24,10 +24,20 @@ module Idml
24
24
  layout_frame = layout_frame(context.item, box)
25
25
  left_limit = layout_frame.x + (layout_frame.inset_left || 0)
26
26
  resume_vertical_paragraph(state)
27
- next_column = state.column_offset || 0
27
+ entry_offset = state.column_offset || 0
28
+ next_column = entry_offset
28
29
  pending = 0
29
30
 
30
- state.paragraphs.each do |paragraph|
31
+ state.paragraphs.each_with_index do |paragraph, index|
32
+ if vertical_keep_deferred?(paragraph, state.paragraphs[index + 1],
33
+ layout_frame, font, left_limit,
34
+ next_column, pending, entry_offset)
35
+ state.current_paragraph = nil
36
+ state.runs_remaining = []
37
+ state.column_offset = 0
38
+ break
39
+ end
40
+
31
41
  remaining_runs, next_column = vertical_paragraph(
32
42
  canvas, paragraph, context, layout_frame, font,
33
43
  left_limit, next_column
@@ -45,6 +55,64 @@ module Idml
45
55
  state.paragraphs = state.paragraphs[pending..]
46
56
  end
47
57
 
58
+ # Keep options in vertical mode (TODO 153, column-count
59
+ # approximation — one column per run under-estimates runs
60
+ # that span several columns, so deferral errs toward
61
+ # placing): KeepAllLinesTogether defers the paragraph
62
+ # wholly to the next frame; KeepWithNext also defers when
63
+ # the follower (or its forced break) would strand. Never
64
+ # defers the frame's first paragraph — the progress
65
+ # guarantee, shared with KeepPolicy.
66
+ def vertical_keep_deferred?(paragraph, next_paragraph,
67
+ layout_frame, _font, left_limit,
68
+ next_column, pending, entry_offset)
69
+ placed = pending.positive? || entry_offset.positive?
70
+ return false unless placed
71
+ return false unless keep_flagged?(paragraph)
72
+
73
+ needed = [paragraph.runs.length, 1].max
74
+ return true if paragraph.keep_all_lines_together &&
75
+ needed > vertical_room(layout_frame, paragraph, left_limit,
76
+ next_column)
77
+
78
+ with_next_deferred?(paragraph, next_paragraph, layout_frame,
79
+ left_limit, next_column, needed)
80
+ end
81
+
82
+ def keep_flagged?(paragraph)
83
+ paragraph.keep_all_lines_together ||
84
+ !paragraph.keep_with_next.nil?
85
+ end
86
+
87
+ def with_next_deferred?(paragraph, next_paragraph, layout_frame,
88
+ left_limit, next_column, needed)
89
+ return false if paragraph.keep_with_next.nil?
90
+
91
+ follower_needed = [next_paragraph&.runs&.length || 1, 1].max
92
+ follower_break = KeepPolicy.paragraph_break?(next_paragraph)
93
+ room = vertical_room(layout_frame, paragraph, left_limit,
94
+ next_column)
95
+ follower_break || needed + follower_needed > room
96
+ end
97
+
98
+ def vertical_room(layout_frame, paragraph, left_limit,
99
+ next_column)
100
+ leading = TextEngine::VerticalLayout.leading_for(
101
+ paragraph.auto_leading,
102
+ paragraph.runs.first&.point_size ||
103
+ TextFrameRenderer::DEFAULT_SIZE,
104
+ )
105
+ vertical_available_columns(layout_frame, leading, left_limit,
106
+ next_column)
107
+ end
108
+
109
+ def vertical_available_columns(layout_frame, leading, left_limit,
110
+ next_column)
111
+ right = layout_frame.x + layout_frame.width -
112
+ (layout_frame.inset_right || 0)
113
+ (((right - left_limit) / leading).floor - next_column).to_i
114
+ end
115
+
48
116
  # Folds a chain-resumed paragraph (runs_remaining) back to
49
117
  # the head of the paragraph list so the vertical path sees a
50
118
  # uniform sequence. The consumed column offset persists in
@@ -70,6 +70,9 @@ module Idml
70
70
  :maximum_letter_spacing,
71
71
  :maximum_glyph_scaling,
72
72
  :minimum_glyph_scaling,
73
+ # ParagraphDirection (LeftToRightDirection /
74
+ # RightToLeftDirection) — mirrors alignment under RTL.
75
+ :paragraph_direction,
73
76
  # Forced paragraph break (StartParagraph: NextPage /
74
77
  # NextColumn / NextFrame / NextOddPage / NextEvenPage).
75
78
  :start_paragraph,
@@ -197,6 +200,9 @@ module Idml
197
200
  paragraph = Paragraph.new(
198
201
  runs: runs,
199
202
  alignment: runs.first.alignment,
203
+ paragraph_direction: resolve_attr(
204
+ style_lookup, psr, :paragraph_direction
205
+ ),
200
206
  space_before: resolve_attr(style_lookup, psr, :space_before),
201
207
  space_after: resolve_attr(style_lookup, psr, :space_after),
202
208
  first_line_indent: resolve_attr(style_lookup, psr,
@@ -21,13 +21,13 @@ module Idml
21
21
  DEFAULT_MIN_GLYPH_SCALING = 100.0
22
22
 
23
23
  def self.justify(line:, frame_width:, alignment: :left,
24
- last_line: false, limits: nil)
25
- new(frame_width, alignment, last_line, limits).justify(line)
24
+ last_line: false, limits: nil, direction: nil)
25
+ new(frame_width, alignment, last_line, limits, direction).justify(line)
26
26
  end
27
27
 
28
- def initialize(frame_width, alignment, last_line, limits)
28
+ def initialize(frame_width, alignment, last_line, limits, direction)
29
29
  @frame_width = frame_width
30
- @alignment = alignment
30
+ @alignment = mirrored_alignment(alignment, direction)
31
31
  @last_line = last_line
32
32
  @limits = limits
33
33
  end
@@ -44,7 +44,18 @@ module Idml
44
44
  line
45
45
  end
46
46
 
47
- private
47
+ RTL_ALIGNMENT_MIRROR = {
48
+ left: :right, right: :left,
49
+ start: :end, end: :start
50
+ }.freeze
51
+
52
+ # RightToLeftDirection mirrors the visual alignment: what is
53
+ # "left" in an LTR paragraph sits right in an RTL one.
54
+ def mirrored_alignment(alignment, direction)
55
+ return alignment unless direction == "RightToLeftDirection"
56
+
57
+ RTL_ALIGNMENT_MIRROR[alignment] || alignment
58
+ end
48
59
 
49
60
  # The paragraph's last line stays ragged under full
50
61
  # justification.
data/lib/idml/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Idml
4
- VERSION = "0.10.13"
4
+ VERSION = "0.10.14"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: idml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.13
4
+ version: 0.10.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
@@ -216,6 +216,8 @@ files:
216
216
  - TODO.pdf/15-wire-spread-children.md
217
217
  - TODO.pdf/150-architecture-round-three.md
218
218
  - TODO.pdf/151-schema-conformance.md
219
+ - TODO.pdf/152-rtl-alignment.md
220
+ - TODO.pdf/153-vertical-keeps.md
219
221
  - TODO.pdf/16-typed-model-pipeline.md
220
222
  - TODO.pdf/17-shape-rendering.md
221
223
  - TODO.pdf/18-text-rendering.md