idml 0.10.14 → 0.10.15

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: 00f5eb099cc78c69be52672e179bc0d2c10b102554bb74b28272b04d0ed904c6
4
- data.tar.gz: c7936ca3c76fef90639d532cc70c0deb7423b2dcd9cd3d73ce6b88bff5a1257c
3
+ metadata.gz: 578a2a0fbd712f1931eaff74db316971441be4a967afd67d47ab55ecdc9679d1
4
+ data.tar.gz: e93c08f8694da2e0522a787ff0f58b4f607952f7024a0dd0d54bab42599ac8e9
5
5
  SHA512:
6
- metadata.gz: 354bb00de55fe629d84853a8e1dfdc2c8b821f708fc15584cf9ac509ba3489a56f92f48f71698cfcf9b28c9f3814b4c0366f3a88d40aa3e2e05b33b242e20a60
7
- data.tar.gz: 52cc665c20cdf7c8c766593e38fd18b847f15da0ac3a09b1accfd5dbbe2b48614a7eadb5452b8f4b295bff1870c7a2acb1a48a0c03174f15916f681d62c4a16c
6
+ metadata.gz: 260e3fdbcd2339d2e461aae7f2992b311120a0b15544d48b65c39d915dee9fd0219fcb3ee6d9e07ce40eaeb9bf68846fa72bdf2475d9ce12d1982cdd1503afd5
7
+ data.tar.gz: 6d760f50d28216bbe1a620aa4ba2b43028ab3051384e0fee559764892f8495b04f5623b2a3ba14698f89cf6bf1e3de9f0426be158e3e7faf0d8d7a3b639e289e
@@ -0,0 +1,21 @@
1
+ # TODO PDF 154: Vertical keep windows
2
+
3
+ ## Status: COMPLETE (approximation) — implemented 2026-09-01
4
+
5
+ ## Problem
6
+
7
+ KeepFirstLines / KeepLastLines windows (TODO 133) applied only to
8
+ the horizontal engine; vertical paragraphs ignored them.
9
+
10
+ ## Solution
11
+
12
+ `VerticalEmit`'s pre-placement keep check (TODO 153) also honors
13
+ the windows on the same column-count approximation: a splitting
14
+ paragraph (needed columns > available) defers when fewer than
15
+ KeepFirstLines columns fit, or when the stranded tail would hold
16
+ fewer than KeepLastLines columns.
17
+
18
+ ## Files
19
+
20
+ - `lib/idml/render/renderers/vertical_emit.rb`
21
+ - `spec/idml/render/vertical_render_spec.rb`
@@ -0,0 +1,29 @@
1
+ # TODO PDF 155: NextColumn column jumping in multi-column frames
2
+
3
+ ## Status: COMPLETE — implemented 2026-09-01
4
+
5
+ ## Problem
6
+
7
+ NextColumnTextWrap approximated as JumpObject-below (TODO 146):
8
+ in a multi-column frame, InDesign moves the text to the NEXT
9
+ COLUMN, not below the object.
10
+
11
+ ## Solution
12
+
13
+ The wrap contour carries the mode: `jump` (JumpObjectTextWrap)
14
+ vs `next_column` (NextColumnTextWrap). Both block the full column
15
+ width; the renderer chooses the response —
16
+ `TextFrameRenderer#next_column_jump?` abandons the column when a
17
+ NextColumn contour blocks the run's band in a multi-column frame:
18
+ the cursor drops to the bottom limit mid-run, the unplaced runs
19
+ return to the chain state, and the next column (or frame)
20
+ resumes them. Single-column frames and JumpObject keep the
21
+ skip-below path (`jump_contour_bottom` reports the resume point
22
+ for both flavors).
23
+
24
+ ## Files
25
+
26
+ - `lib/idml/render/text_wrap_resolver.rb`
27
+ - `lib/idml/render/renderers/text_frame_renderer.rb`
28
+ - `spec/idml/render/text_wrap_resolver_spec.rb`
29
+ - `spec/idml/render/jump_object_render_spec.rb` (end-to-end)
@@ -1,6 +1,6 @@
1
1
  # TODO PDF 61: Known limitations and future work
2
2
 
3
- ## Status: DOCUMENTED — refreshed 2026-08-31
3
+ ## Status: DOCUMENTED — refreshed 2026-09-01
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;
@@ -27,11 +27,10 @@ this list reflects the current state.
27
27
  odd/even page parity.
28
28
  - **Text wrap**: BoundingBoxTextWrap, Contour, and Inverse modes
29
29
  render (TODOs 130-131) with TextWrapSide side-awareness for both
30
- box contours and Contour shapes (TODOs 138/147); JumpObject and
31
- NextColumn move text below the object (TODOs 143/146) NextColumn
32
- in a MULTI-column frame should jump to the next column instead
33
- (chain integration, future); spine variants approximate as
34
- BothSides.
30
+ box contours and Contour shapes (TODOs 138/147); JumpObject moves
31
+ text below the object (TODO 143); NextColumn jumps to the next
32
+ column in multi-column frames and below the object otherwise
33
+ (TODOs 146/155); spine variants approximate as BothSides.
35
34
 
36
35
  ### CJK
37
36
  - **Mojikumi**: script spacing (TODO 125, now actually wired),
@@ -40,9 +39,8 @@ this list reflects the current state.
40
39
  Desired applies per class pair) are applied; Minimum/Maximum
41
40
  aki bounds and CompressionPriority are parsed but unused.
42
41
  - **Vertical mode**: Latin runs rotate as one group per segment
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.
42
+ (TODO 142); ruby placement is beside-column; keep options apply
43
+ with a column-count approximation (TODOs 153-154).
46
44
 
47
45
  ### Structural features
48
46
  - **Endnotes** (TODO 117): reference markers render and endnote TEXT
@@ -62,7 +60,5 @@ this list reflects the current state.
62
60
  ## Future enhancements
63
61
 
64
62
  - Hyphenation dictionary integration
65
- - NextColumn wrap via column-jump chaining (multi-column frames)
66
63
  - Minimum/Maximum mojikumi aki bounds + CompressionPriority
67
64
  - ToBinding / kashida Arabic justification
68
- - Vertical keep windows (KeepFirstLines / KeepLastLines)
@@ -543,6 +543,11 @@ module Idml
543
543
  context, layout_frame, cursor_y, run
544
544
  )
545
545
  run_wrap -= wrap_reduction
546
+ if next_column_jump?(context, layout_frame, cursor_y, run,
547
+ run_wrap)
548
+ cursor_y = bottom_limit
549
+ break
550
+ end
546
551
  cursor_y, run_wrap, wrap_shift = skip_below_jump_object(
547
552
  context, layout_frame, cursor_y, run, run_wrap, wrap_shift,
548
553
  wrap_reduction, bottom_limit
@@ -612,6 +617,37 @@ module Idml
612
617
  end
613
618
  private_class_method :render_footnote_entries
614
619
 
620
+ # NextColumnTextWrap in a multi-column frame (TODO 155):
621
+ # abandon the column — the unplaced runs return to the
622
+ # chain state and the next column resumes them. Falls
623
+ # through to skip-below everywhere else (single-column
624
+ # frames included).
625
+ def self.next_column_jump?(context, layout_frame, cursor_y, run,
626
+ run_wrap)
627
+ size = run.point_size || DEFAULT_SIZE
628
+ return false unless run_wrap < size
629
+ return false unless multi_column_frame?(context)
630
+
631
+ blocked_in_band?(context, layout_frame, cursor_y, size)
632
+ end
633
+
634
+ def self.multi_column_frame?(context)
635
+ text_column_count(context).to_i > 1
636
+ end
637
+ private_class_method :multi_column_frame?
638
+
639
+ def self.blocked_in_band?(context, layout_frame, cursor_y, size)
640
+ resolver = context.text_wrap_resolver
641
+ return false unless resolver
642
+
643
+ frame_x = layout_frame.x + (layout_frame.inset_left || 0)
644
+ frame_right = layout_frame.x + layout_frame.width -
645
+ (layout_frame.inset_right || 0)
646
+ resolver.next_column_block?(cursor_y, size, frame_x, frame_right)
647
+ end
648
+ private_class_method :blocked_in_band?
649
+ private_class_method :next_column_jump?
650
+
615
651
  # JumpObjectTextWrap blocks the full column width: when the
616
652
  # run's band is fully blocked, move the cursor below the
617
653
  # object's bottom edge and re-measure once. InDesign's
@@ -71,9 +71,10 @@ module Idml
71
71
  return false unless keep_flagged?(paragraph)
72
72
 
73
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)
74
+ room = vertical_room(layout_frame, paragraph, left_limit,
75
+ next_column)
76
+ return true if keep_all_deferred?(paragraph, needed, room)
77
+ return true if window_deferred?(paragraph, needed, room)
77
78
 
78
79
  with_next_deferred?(paragraph, next_paragraph, layout_frame,
79
80
  left_limit, next_column, needed)
@@ -81,7 +82,29 @@ module Idml
81
82
 
82
83
  def keep_flagged?(paragraph)
83
84
  paragraph.keep_all_lines_together ||
84
- !paragraph.keep_with_next.nil?
85
+ !paragraph.keep_with_next.nil? ||
86
+ paragraph.keep_first_lines ||
87
+ paragraph.keep_last_lines
88
+ end
89
+
90
+ def keep_all_deferred?(paragraph, needed, room)
91
+ paragraph.keep_all_lines_together && needed > room
92
+ end
93
+
94
+ # Keep windows on the same column-count approximation:
95
+ # defer a splitting paragraph when fewer than
96
+ # KeepFirstLines columns fit, or when the stranded tail
97
+ # would hold fewer than KeepLastLines columns.
98
+ def window_deferred?(paragraph, needed, room)
99
+ return false unless needed > room
100
+
101
+ if paragraph.keep_first_lines &&
102
+ room < paragraph.keep_first_lines
103
+ return true
104
+ end
105
+
106
+ paragraph.keep_last_lines &&
107
+ (needed - room) < paragraph.keep_last_lines
85
108
  end
86
109
 
87
110
  def with_next_deferred?(paragraph, next_paragraph, layout_frame,
@@ -18,7 +18,7 @@ module Idml
18
18
  class TextWrapResolver
19
19
  # A wrap contour: a PDF-coordinate rectangle the text avoids.
20
20
  Contour = Struct.new(
21
- :x, :y, :width, :height, :inverse, :side, :jump,
21
+ :x, :y, :width, :height, :inverse, :side, :jump, :next_column,
22
22
  keyword_init: true
23
23
  )
24
24
 
@@ -100,9 +100,10 @@ module Idml
100
100
  return [0.0, 0.0] if overlap.zero?
101
101
 
102
102
  # JumpObject / NextColumn: text never flows beside the
103
- # object — the full frame width is blocked and the renderer
104
- # skips the run below the object's bottom edge.
105
- return [frame_right - frame_x, 0.0] if contour.jump
103
+ # object — the full frame width is blocked. The renderer
104
+ # either skips below (JumpObject) or jumps column
105
+ # (NextColumn in multi-column frames).
106
+ return [frame_right - frame_x, 0.0] if blocks_full_width?(contour)
106
107
 
107
108
  interval_side_adjustment(contour.side, contour.x,
108
109
  contour.x + contour.width, overlap,
@@ -115,7 +116,7 @@ module Idml
115
116
  # jump contour blocks the band.
116
117
  def jump_contour_bottom(line_y, line_height, frame_x, frame_right)
117
118
  bottoms = @contours.filter_map do |contour|
118
- next unless box_contour?(contour) && contour.jump
119
+ next unless skip_contour?(contour)
119
120
  next unless y_overlap?(contour, line_y, line_height) &&
120
121
  x_overlap?(contour, frame_x, frame_right)
121
122
 
@@ -124,6 +125,25 @@ module Idml
124
125
  bottoms.min
125
126
  end
126
127
 
128
+ # Contours whose block resolves as skip-below (both jump
129
+ # flavors report a resume point; the renderer chooses).
130
+ def skip_contour?(contour)
131
+ box_contour?(contour) &&
132
+ (contour.jump || contour.next_column)
133
+ end
134
+ private :skip_contour?
135
+
136
+ # True when a NextColumn contour blocks the given band: in a
137
+ # multi-column frame the renderer abandons the column and
138
+ # the chain resumes in the next one.
139
+ def next_column_block?(line_y, line_height, frame_x, frame_right)
140
+ @contours.any? do |contour|
141
+ box_contour?(contour) && contour.next_column &&
142
+ y_overlap?(contour, line_y, line_height) &&
143
+ x_overlap?(contour, frame_x, frame_right)
144
+ end
145
+ end
146
+
127
147
  def box_contour?(contour)
128
148
  !contour.is_a?(WrapContour::Shape)
129
149
  end
@@ -136,6 +156,14 @@ module Idml
136
156
  # shift); RightSide moves text past its right edge (reduce +
137
157
  # shift); LargestArea picks the roomier side; BothSides and
138
158
  # the spine variants reduce by `overlap` only.
159
+ # JumpObject / NextColumn both forbid text beside the
160
+ # object; the renderer decides between skip-below and
161
+ # column jump.
162
+ def blocks_full_width?(contour)
163
+ contour.jump || contour.next_column
164
+ end
165
+ private :blocks_full_width?
166
+
139
167
  def interval_side_adjustment(side, obj_left, obj_right, overlap,
140
168
  frame_x, frame_right)
141
169
  case side
@@ -185,11 +213,13 @@ module Idml
185
213
 
186
214
  rect = Geometry.placement_rect(bounds, item.item_transform,
187
215
  page_height)
216
+ mode = pref&.text_wrap_mode
188
217
  Contour.new(
189
218
  x: rect[:x], y: rect[:y],
190
219
  width: rect[:width], height: rect[:height],
191
220
  side: pref&.text_wrap_side,
192
- jump: JUMP_MODES.include?(pref&.text_wrap_mode)
221
+ jump: mode == "JumpObjectTextWrap",
222
+ next_column: mode == "NextColumnTextWrap"
193
223
  )
194
224
  end
195
225
  private_class_method :box_contour
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.14"
4
+ VERSION = "0.10.15"
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.14
4
+ version: 0.10.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
@@ -218,6 +218,8 @@ files:
218
218
  - TODO.pdf/151-schema-conformance.md
219
219
  - TODO.pdf/152-rtl-alignment.md
220
220
  - TODO.pdf/153-vertical-keeps.md
221
+ - TODO.pdf/154-vertical-keep-windows.md
222
+ - TODO.pdf/155-next-column-jump.md
221
223
  - TODO.pdf/16-typed-model-pipeline.md
222
224
  - TODO.pdf/17-shape-rendering.md
223
225
  - TODO.pdf/18-text-rendering.md