idml 0.8.2 → 0.8.4
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/TODO.pdf/107-image-rendering-unification.md +14 -1
- data/TODO.pdf/113-multi-column-text-frames.md +5 -1
- data/lib/idml/render/image_collector.rb +5 -2
- data/lib/idml/render/renderers/text_frame_renderer.rb +97 -2
- data/lib/idml/render/spread_renderer.rb +28 -7
- data/lib/idml/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a20e81d5d762e6dfcb5c374d342210ac6132a96604d83ee305dfa1c64e64e535
|
|
4
|
+
data.tar.gz: e6da886fc60f952b2e9f39f512027b953958c19e14cadf3200dc9952e4c3fa96
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f8a51af44eaa9c43cba3bbdc9f332b1fa94f34f3eb08cb32ce01b957ceff14d6903f03a2d458884a538ef6f6e56d224868427abc46f1303d8f04d82324d9aa5b
|
|
7
|
+
data.tar.gz: 68e81d0fc87751c37015ea4a3b905a65c7863145bc074714e3241a02e4f951294703b3a3b4eef8d46601d896b95ddc40d3d267f9e54ee71483b2ba9bccbb742a
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
# TODO PDF 107: Image rendering unification via PageItemRenderer
|
|
2
2
|
|
|
3
|
-
## Status:
|
|
3
|
+
## Status: DONE for z-order — ImageCollector now tags each ref with
|
|
4
|
+
`parent_self` (the containing page item's Self). SpreadRenderer
|
|
5
|
+
groups refs by parent and renders each item's images when that item
|
|
6
|
+
is reached in the each_page_item loop (instead of all upfront as a
|
|
7
|
+
background layer). Images now respect z-order relative to other
|
|
8
|
+
page items.
|
|
9
|
+
|
|
10
|
+
Intra-item ordering (fill behind image, stroke on top) remains
|
|
11
|
+
approximate: the image renders BEFORE the item's fill/stroke dispatch.
|
|
12
|
+
For the common case (image inside unfilled rectangle with stroke)
|
|
13
|
+
this produces correct output — image behind stroke. For filled
|
|
14
|
+
rectangles containing images (rare), the fill would cover the image.
|
|
15
|
+
Full intra-item ordering would require integrating image rendering
|
|
16
|
+
into RectangleRenderer between fill and stroke.
|
|
4
17
|
|
|
5
18
|
## Problem
|
|
6
19
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# TODO PDF 113: Multi-column text frames (TextColumnCount, TextColumnGutter)
|
|
2
2
|
|
|
3
|
-
## Status:
|
|
3
|
+
## Status: DONE — engine_render detects TextColumnCount > 1 and
|
|
4
|
+
splits the frame into N column-specific Frame structs. Text flows
|
|
5
|
+
column 1 → column 2 → ... → chain to next frame via StoryChainController.
|
|
6
|
+
Each column acts as a mini-frame with the same height/insets but a
|
|
7
|
+
narrower width. TextColumnGutter honored as the gap between columns.
|
|
4
8
|
|
|
5
9
|
## Problem
|
|
6
10
|
|
|
@@ -56,11 +56,13 @@ module Idml
|
|
|
56
56
|
|
|
57
57
|
load_new(image, parent, uri)
|
|
58
58
|
end
|
|
59
|
+
private :register
|
|
59
60
|
|
|
60
61
|
def reuse(name, image, parent)
|
|
61
62
|
{ name: name, placement: placement_for(image, parent),
|
|
62
|
-
clip_box: clip_box_for(parent) }
|
|
63
|
+
clip_box: clip_box_for(parent), parent_self: parent.self_attr }
|
|
63
64
|
end
|
|
65
|
+
private :reuse
|
|
64
66
|
|
|
65
67
|
def load_new(image, parent, uri)
|
|
66
68
|
path = Image.resolve_path(uri, base_dir: @base_dir)
|
|
@@ -73,8 +75,9 @@ module Idml
|
|
|
73
75
|
name = @writer.add_image(data: data)
|
|
74
76
|
@writer.register_image_name(uri, name)
|
|
75
77
|
{ name: name, placement: placement_for(image, parent, dims[1]),
|
|
76
|
-
clip_box: clip_box_for(parent) }
|
|
78
|
+
clip_box: clip_box_for(parent), parent_self: parent.self_attr }
|
|
77
79
|
end
|
|
80
|
+
private :load_new
|
|
78
81
|
|
|
79
82
|
def clip_box_for(parent)
|
|
80
83
|
return nil unless parent.geometric_bounds
|
|
@@ -158,7 +158,22 @@ module Idml
|
|
|
158
158
|
# next frame in the chain.
|
|
159
159
|
def self.engine_render(canvas, state, context, box, font)
|
|
160
160
|
layout_frame = layout_frame(context.item, box)
|
|
161
|
-
|
|
161
|
+
col_count = text_column_count(context)
|
|
162
|
+
|
|
163
|
+
if col_count && col_count > 1
|
|
164
|
+
engine_render_multi_column(canvas, state, context, layout_frame,
|
|
165
|
+
font, col_count)
|
|
166
|
+
else
|
|
167
|
+
engine_render_single_column(canvas, state, context, layout_frame,
|
|
168
|
+
font)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
private_class_method :engine_render
|
|
172
|
+
|
|
173
|
+
def self.engine_render_single_column(canvas, state, context,
|
|
174
|
+
layout_frame, font)
|
|
175
|
+
top_y = layout_frame.y + layout_frame.height -
|
|
176
|
+
(layout_frame.inset_top || 0)
|
|
162
177
|
bottom_limit = TextEngine::VerticalLayout.bottom_limit(layout_frame)
|
|
163
178
|
cursor_y = vertical_justify_top(top_y, bottom_limit, state, context)
|
|
164
179
|
char_cursor = state.char_cursor
|
|
@@ -174,7 +189,87 @@ module Idml
|
|
|
174
189
|
consume_paragraphs(canvas, state, context, layout_frame, font,
|
|
175
190
|
cursor_y, bottom_limit, char_cursor)
|
|
176
191
|
end
|
|
177
|
-
private_class_method :
|
|
192
|
+
private_class_method :engine_render_single_column
|
|
193
|
+
|
|
194
|
+
# Renders text across N columns within a single frame. Text
|
|
195
|
+
# flows column 1 → column 2 → ... → chain to next frame. Each
|
|
196
|
+
# column acts as a mini-frame with the same height but a
|
|
197
|
+
# narrower width. The chain state threads across columns
|
|
198
|
+
# transparently.
|
|
199
|
+
def self.engine_render_multi_column(canvas, state, context,
|
|
200
|
+
layout_frame, font, col_count)
|
|
201
|
+
gutter = text_column_gutter(context) || 0
|
|
202
|
+
col_frames = build_column_frames(layout_frame, col_count, gutter)
|
|
203
|
+
|
|
204
|
+
col_frames.each do |col_frame|
|
|
205
|
+
break if state_empty?(state)
|
|
206
|
+
|
|
207
|
+
render_column(canvas, state, context, col_frame, font)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
private_class_method :engine_render_multi_column
|
|
211
|
+
|
|
212
|
+
def self.render_column(canvas, state, context, col_frame, font)
|
|
213
|
+
top_y = col_frame.y + col_frame.height - (col_frame.inset_top || 0)
|
|
214
|
+
bottom_limit = TextEngine::VerticalLayout.bottom_limit(col_frame)
|
|
215
|
+
cursor_y = top_y
|
|
216
|
+
char_cursor = state.char_cursor
|
|
217
|
+
|
|
218
|
+
if state.current_paragraph
|
|
219
|
+
cursor_y, char_cursor = resume_paragraph(
|
|
220
|
+
canvas, state, context, col_frame, font,
|
|
221
|
+
cursor_y, bottom_limit, char_cursor
|
|
222
|
+
)
|
|
223
|
+
return if state.current_paragraph
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
consume_paragraphs(canvas, state, context, col_frame, font,
|
|
227
|
+
cursor_y, bottom_limit, char_cursor)
|
|
228
|
+
end
|
|
229
|
+
private_class_method :render_column
|
|
230
|
+
|
|
231
|
+
def self.state_empty?(state)
|
|
232
|
+
state.paragraphs.empty? && state.current_paragraph.nil?
|
|
233
|
+
end
|
|
234
|
+
private_class_method :state_empty?
|
|
235
|
+
|
|
236
|
+
def self.text_column_count(context)
|
|
237
|
+
pref = context.item&.text_frame_preference&.first
|
|
238
|
+
pref&.text_column_count
|
|
239
|
+
end
|
|
240
|
+
private_class_method :text_column_count
|
|
241
|
+
|
|
242
|
+
def self.text_column_gutter(context)
|
|
243
|
+
pref = context.item&.text_frame_preference&.first
|
|
244
|
+
pref&.text_column_gutter
|
|
245
|
+
end
|
|
246
|
+
private_class_method :text_column_gutter
|
|
247
|
+
|
|
248
|
+
# Builds N column-specific Frame structs from the parent
|
|
249
|
+
# layout frame. Each column has the same y/height/insets but
|
|
250
|
+
# a different x and width so VerticalLayout's geometry math
|
|
251
|
+
# produces correct per-column results.
|
|
252
|
+
def self.build_column_frames(layout_frame, col_count, gutter)
|
|
253
|
+
inset_left = layout_frame.inset_left || 0
|
|
254
|
+
inset_right = layout_frame.inset_right || 0
|
|
255
|
+
content_width = layout_frame.width - inset_left - inset_right
|
|
256
|
+
col_width = (content_width - ((col_count - 1) * gutter)) / col_count
|
|
257
|
+
|
|
258
|
+
Array.new(col_count) do |i|
|
|
259
|
+
col_left = layout_frame.x + inset_left + (i * (col_width + gutter))
|
|
260
|
+
TextEngine::Frame.new(
|
|
261
|
+
x: col_left - inset_left,
|
|
262
|
+
y: layout_frame.y,
|
|
263
|
+
width: col_width + inset_left + inset_right,
|
|
264
|
+
height: layout_frame.height,
|
|
265
|
+
inset_top: layout_frame.inset_top,
|
|
266
|
+
inset_bottom: layout_frame.inset_bottom,
|
|
267
|
+
inset_left: inset_left,
|
|
268
|
+
inset_right: inset_right,
|
|
269
|
+
)
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
private_class_method :build_column_frames
|
|
178
273
|
|
|
179
274
|
# Computes the starting cursor_y for vertical justification.
|
|
180
275
|
# IDML's TextFramePreference.VerticalJustification can be:
|
|
@@ -40,14 +40,15 @@ module Idml
|
|
|
40
40
|
condition_filter: @condition_filter,
|
|
41
41
|
style_lookup: @style_lookup,
|
|
42
42
|
}
|
|
43
|
+
images_by_parent = group_images_by_parent(image_refs)
|
|
43
44
|
|
|
44
45
|
canvas.save_graphics_state do
|
|
45
46
|
render_background(canvas, page_width, page_height)
|
|
46
|
-
|
|
47
|
-
render_master_items(canvas, spread, context_base)
|
|
47
|
+
render_master_items(canvas, spread, context_base, images_by_parent)
|
|
48
48
|
spread.each_page_item do |item|
|
|
49
49
|
next unless @layer_filter.visible?(item)
|
|
50
50
|
|
|
51
|
+
render_item_images(canvas, images_by_parent, item.self_attr)
|
|
51
52
|
context = RenderContext.new(context_base.merge(item: item))
|
|
52
53
|
PageItemRenderer.render(canvas, context)
|
|
53
54
|
end
|
|
@@ -62,13 +63,30 @@ module Idml
|
|
|
62
63
|
canvas.fill
|
|
63
64
|
end
|
|
64
65
|
|
|
66
|
+
# Groups image refs by their parent page item's Self so each
|
|
67
|
+
# item's images render when that item is reached in z-order
|
|
68
|
+
# (instead of all upfront as a background layer).
|
|
69
|
+
def group_images_by_parent(image_refs)
|
|
70
|
+
image_refs.each_with_object({}) do |ref, hash|
|
|
71
|
+
parent = ref[:parent_self]
|
|
72
|
+
next unless parent
|
|
73
|
+
|
|
74
|
+
(hash[parent] ||= []) << ref
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def render_item_images(canvas, images_by_parent, item_self)
|
|
79
|
+
refs = images_by_parent[item_self]
|
|
80
|
+
return unless refs
|
|
81
|
+
|
|
82
|
+
render_images(canvas, refs)
|
|
83
|
+
end
|
|
84
|
+
|
|
65
85
|
def render_images(canvas, image_refs)
|
|
66
86
|
return if image_refs.empty?
|
|
67
87
|
|
|
68
88
|
image_refs.each do |ref|
|
|
69
89
|
placement = ref[:placement]
|
|
70
|
-
placement[:scale_x].abs
|
|
71
|
-
placement[:scale_y].abs
|
|
72
90
|
canvas.save_graphics_state do
|
|
73
91
|
apply_image_clip(canvas, ref)
|
|
74
92
|
canvas.draw_image_matrix(ref[:name],
|
|
@@ -88,11 +106,12 @@ module Idml
|
|
|
88
106
|
canvas.clip
|
|
89
107
|
end
|
|
90
108
|
|
|
91
|
-
def render_master_items(canvas, spread, context_base)
|
|
109
|
+
def render_master_items(canvas, spread, context_base, images_by_parent)
|
|
92
110
|
return unless @package
|
|
93
111
|
|
|
94
112
|
resolve_master_spreads(spread).each do |master|
|
|
95
|
-
render_master_page_items(canvas, master, context_base
|
|
113
|
+
render_master_page_items(canvas, master, context_base,
|
|
114
|
+
images_by_parent)
|
|
96
115
|
end
|
|
97
116
|
end
|
|
98
117
|
|
|
@@ -107,11 +126,13 @@ module Idml
|
|
|
107
126
|
masters
|
|
108
127
|
end
|
|
109
128
|
|
|
110
|
-
def render_master_page_items(canvas, master_part, context_base
|
|
129
|
+
def render_master_page_items(canvas, master_part, context_base,
|
|
130
|
+
images_by_parent)
|
|
111
131
|
master_part.master_spread.each do |master_so|
|
|
112
132
|
master_so.each_page_item do |item|
|
|
113
133
|
next unless @layer_filter.visible?(item)
|
|
114
134
|
|
|
135
|
+
render_item_images(canvas, images_by_parent, item.self_attr)
|
|
115
136
|
context = RenderContext.new(context_base.merge(item: item))
|
|
116
137
|
PageItemRenderer.render(canvas, context)
|
|
117
138
|
end
|
data/lib/idml/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: idml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bigdecimal
|