idml 0.8.5 → 0.8.7
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/114-text-wrap-around-shapes.md +15 -1
- data/lib/idml/elements/graphic_line.rb +2 -0
- data/lib/idml/elements/oval.rb +2 -0
- data/lib/idml/elements/path.rb +2 -0
- data/lib/idml/elements/polygon.rb +2 -0
- data/lib/idml/elements/rectangle.rb +2 -0
- data/lib/idml/render/render_context.rb +1 -0
- data/lib/idml/render/renderers/text_frame_renderer.rb +23 -0
- data/lib/idml/render/spread_renderer.rb +5 -0
- data/lib/idml/render/text_wrap_resolver.rb +129 -0
- data/lib/idml/render.rb +1 -0
- data/lib/idml/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ea725628da813f202ff5f6c8c9659e1de20b24d8eb9d92c6cd8cc43aad12948c
|
|
4
|
+
data.tar.gz: 3ae66c7b8c22ba9660da4a8bae4a8f2314cb13e90aa72f0c673350415ab77cfc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e6ec5c19fb106644c373ee8a3cd21526cfaff1ce88c7d56d7c65b6376fe508ba984203d2d7d7249d426fc9486d21f5d7dbc1f758c026dd6f60c923631e31a4d8
|
|
7
|
+
data.tar.gz: 82cd84ff102aafce7c0766e1bb3aea5393a20f4d959753273ad163f15a9b83ab22a323c8b369548b45a3cc6d3e0ec5857eac1663ccdb601e9adb093c7067121e
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
# TODO PDF 114: Text wrap around shapes (TextWrapPreference)
|
|
2
2
|
|
|
3
|
-
## Status:
|
|
3
|
+
## Status: DONE for BoundingBox mode — `Render::TextWrapResolver` computes
|
|
4
|
+
wrap contours from page items declaring TextWrapPreference with
|
|
5
|
+
TextWrapMode != "None". SpreadRenderer builds the resolver and passes
|
|
6
|
+
it via RenderContext. TextFrameRenderer's `text_wrap_overlap` queries
|
|
7
|
+
overlap at each run's y position and reduces the wrap width — text
|
|
8
|
+
flows around the shape instead of running over it.
|
|
9
|
+
|
|
10
|
+
Shape page items (Rectangle, Oval, Polygon, GraphicLine, Path) all
|
|
11
|
+
carry the TextWrapPreference child element. Non-shape items (Page,
|
|
12
|
+
Link) are filtered by WRAPPABLE_TYPES to avoid NoMethodError.
|
|
13
|
+
|
|
14
|
+
Per-run approximation: all lines in a run get the same reduced width.
|
|
15
|
+
True per-line adjustment (when a run spans the contour boundary)
|
|
16
|
+
requires line-count-aware layout. Shape mode (contour following) and
|
|
17
|
+
Inverse mode remain unsupported.
|
|
4
18
|
|
|
5
19
|
## Problem
|
|
6
20
|
|
|
@@ -18,6 +18,7 @@ module Idml
|
|
|
18
18
|
attribute :item_layer, :string
|
|
19
19
|
attribute :properties, Idml::Elements::Properties, collection: true
|
|
20
20
|
attribute :transparency_setting, Idml::Elements::TransparencySetting
|
|
21
|
+
attribute :text_wrap_preference, Idml::Elements::TextWrapPreference
|
|
21
22
|
|
|
22
23
|
xml do
|
|
23
24
|
root "GraphicLine"
|
|
@@ -34,6 +35,7 @@ module Idml
|
|
|
34
35
|
map_attribute "ItemLayer", to: :item_layer
|
|
35
36
|
map_element "Properties", to: :properties
|
|
36
37
|
map_element "TransparencySetting", to: :transparency_setting
|
|
38
|
+
map_element "TextWrapPreference", to: :text_wrap_preference
|
|
37
39
|
end
|
|
38
40
|
|
|
39
41
|
def geometric_bounds
|
data/lib/idml/elements/oval.rb
CHANGED
|
@@ -29,6 +29,7 @@ module Idml
|
|
|
29
29
|
attribute :item_layer, :string
|
|
30
30
|
attribute :properties, Idml::Elements::Properties, collection: true
|
|
31
31
|
attribute :transparency_setting, Idml::Elements::TransparencySetting
|
|
32
|
+
attribute :text_wrap_preference, Idml::Elements::TextWrapPreference
|
|
32
33
|
attribute :image, Idml::Elements::Image, collection: true
|
|
33
34
|
|
|
34
35
|
xml do
|
|
@@ -50,6 +51,7 @@ module Idml
|
|
|
50
51
|
map_attribute "ItemLayer", to: :item_layer
|
|
51
52
|
map_element "Properties", to: :properties
|
|
52
53
|
map_element "TransparencySetting", to: :transparency_setting
|
|
54
|
+
map_element "TextWrapPreference", to: :text_wrap_preference
|
|
53
55
|
map_element "Image", to: :image
|
|
54
56
|
end
|
|
55
57
|
|
data/lib/idml/elements/path.rb
CHANGED
|
@@ -25,6 +25,7 @@ module Idml
|
|
|
25
25
|
attribute :item_layer, :string
|
|
26
26
|
attribute :properties, Idml::Elements::Properties, collection: true
|
|
27
27
|
attribute :transparency_setting, Idml::Elements::TransparencySetting
|
|
28
|
+
attribute :text_wrap_preference, Idml::Elements::TextWrapPreference
|
|
28
29
|
|
|
29
30
|
xml do
|
|
30
31
|
root "Path"
|
|
@@ -41,6 +42,7 @@ module Idml
|
|
|
41
42
|
map_attribute "ItemLayer", to: :item_layer
|
|
42
43
|
map_element "Properties", to: :properties
|
|
43
44
|
map_element "TransparencySetting", to: :transparency_setting
|
|
45
|
+
map_element "TextWrapPreference", to: :text_wrap_preference
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
def geometric_bounds
|
|
@@ -20,6 +20,7 @@ module Idml
|
|
|
20
20
|
attribute :item_layer, :string
|
|
21
21
|
attribute :properties, Idml::Elements::Properties, collection: true
|
|
22
22
|
attribute :transparency_setting, Idml::Elements::TransparencySetting
|
|
23
|
+
attribute :text_wrap_preference, Idml::Elements::TextWrapPreference
|
|
23
24
|
attribute :image, Idml::Elements::Image, collection: true
|
|
24
25
|
|
|
25
26
|
xml do
|
|
@@ -39,6 +40,7 @@ module Idml
|
|
|
39
40
|
map_attribute "ItemLayer", to: :item_layer
|
|
40
41
|
map_element "Properties", to: :properties
|
|
41
42
|
map_element "TransparencySetting", to: :transparency_setting
|
|
43
|
+
map_element "TextWrapPreference", to: :text_wrap_preference
|
|
42
44
|
map_element "Image", to: :image
|
|
43
45
|
end
|
|
44
46
|
|
|
@@ -24,6 +24,7 @@ module Idml
|
|
|
24
24
|
attribute :item_layer, :string
|
|
25
25
|
attribute :properties, Idml::Elements::Properties, collection: true
|
|
26
26
|
attribute :transparency_setting, Idml::Elements::TransparencySetting
|
|
27
|
+
attribute :text_wrap_preference, Idml::Elements::TextWrapPreference
|
|
27
28
|
attribute :image, Idml::Elements::Image, collection: true
|
|
28
29
|
|
|
29
30
|
xml do
|
|
@@ -46,6 +47,7 @@ module Idml
|
|
|
46
47
|
map_attribute "ItemLayer", to: :item_layer
|
|
47
48
|
map_element "Properties", to: :properties
|
|
48
49
|
map_element "TransparencySetting", to: :transparency_setting
|
|
50
|
+
map_element "TextWrapPreference", to: :text_wrap_preference
|
|
49
51
|
map_element "Image", to: :image
|
|
50
52
|
end
|
|
51
53
|
|
|
@@ -436,6 +436,8 @@ module Idml
|
|
|
436
436
|
rendered_count)
|
|
437
437
|
run_wrap = drop_cap_wrap_width(wrap_width, dc_width,
|
|
438
438
|
dc_lines, rendered_count)
|
|
439
|
+
run_wrap -= text_wrap_overlap(context, layout_frame, cursor_y,
|
|
440
|
+
run)
|
|
439
441
|
positioned, next_y = layout_run(
|
|
440
442
|
canvas, run, paragraph, context, layout_frame, font,
|
|
441
443
|
run_wrap, cursor_y, space_before,
|
|
@@ -450,6 +452,27 @@ module Idml
|
|
|
450
452
|
end
|
|
451
453
|
private_class_method :iterate_paragraph_runs
|
|
452
454
|
|
|
455
|
+
# Computes the text-wrap overlap for a run at its current y
|
|
456
|
+
# position. Uses the run's point_size as the line height
|
|
457
|
+
# approximation. Returns 0.0 when no resolver is wired or no
|
|
458
|
+
# contours overlap. Per-run approximation: all lines in the
|
|
459
|
+
# run get the same reduced width (correct when the run is
|
|
460
|
+
# fully inside or outside the contour; approximate when it
|
|
461
|
+
# spans the boundary).
|
|
462
|
+
def self.text_wrap_overlap(context, layout_frame, cursor_y, run)
|
|
463
|
+
resolver = context.text_wrap_resolver
|
|
464
|
+
return 0.0 unless resolver
|
|
465
|
+
|
|
466
|
+
size = run.point_size || DEFAULT_SIZE
|
|
467
|
+
frame_x = layout_frame.x + (layout_frame.inset_left || 0)
|
|
468
|
+
frame_right = layout_frame.x + layout_frame.width -
|
|
469
|
+
(layout_frame.inset_right || 0)
|
|
470
|
+
overlap = resolver.overlap_width(cursor_y, size,
|
|
471
|
+
frame_x, frame_right)
|
|
472
|
+
[overlap, 0.0].max
|
|
473
|
+
end
|
|
474
|
+
private_class_method :text_wrap_overlap
|
|
475
|
+
|
|
453
476
|
# Strips drop cap characters from the first run so they don't
|
|
454
477
|
# render twice (once as the enlarged drop cap, once as normal
|
|
455
478
|
# text). Returns a new run list with the first run's text
|
|
@@ -39,6 +39,7 @@ module Idml
|
|
|
39
39
|
chain_controller: StoryChainController.new,
|
|
40
40
|
condition_filter: @condition_filter,
|
|
41
41
|
style_lookup: @style_lookup,
|
|
42
|
+
text_wrap_resolver: build_text_wrap_resolver(spread, page_height),
|
|
42
43
|
}
|
|
43
44
|
images_by_parent = group_images_by_parent(image_refs)
|
|
44
45
|
|
|
@@ -144,6 +145,10 @@ module Idml
|
|
|
144
145
|
|
|
145
146
|
ColorResolver.new(@package.graphic)
|
|
146
147
|
end
|
|
148
|
+
|
|
149
|
+
def build_text_wrap_resolver(spread, page_height)
|
|
150
|
+
TextWrapResolver.build(spread, page_height: page_height)
|
|
151
|
+
end
|
|
147
152
|
end
|
|
148
153
|
end
|
|
149
154
|
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Idml
|
|
4
|
+
module Render
|
|
5
|
+
# Resolves text-wrap contours from page items that declare
|
|
6
|
+
# TextWrapPreference. For BoundingBox mode (the common case),
|
|
7
|
+
# the contour is the item's geometric bounds expanded by the
|
|
8
|
+
# wrap offsets. TextFrameRenderer queries this resolver per
|
|
9
|
+
# text line to compute the effective wrap width — lines that
|
|
10
|
+
# overlap a contour get their width reduced by the overlap.
|
|
11
|
+
#
|
|
12
|
+
# Supported: BoundingBox mode (rectangle intersection).
|
|
13
|
+
# Unsupported: Shape mode (contour following), Inverse mode.
|
|
14
|
+
class TextWrapResolver
|
|
15
|
+
# A wrap contour: a PDF-coordinate rectangle the text avoids.
|
|
16
|
+
Contour = Struct.new(
|
|
17
|
+
:x, :y, :width, :height,
|
|
18
|
+
keyword_init: true
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
OFFSET_ATTRS = %i[
|
|
22
|
+
offset_top offset_left offset_bottom offset_right
|
|
23
|
+
].freeze
|
|
24
|
+
|
|
25
|
+
def self.build(spread, page_height: 792)
|
|
26
|
+
contours = []
|
|
27
|
+
spread.each_page_item do |item|
|
|
28
|
+
contour = contour_for(item, page_height)
|
|
29
|
+
contours << contour if contour
|
|
30
|
+
end
|
|
31
|
+
new(contours)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def initialize(contours)
|
|
35
|
+
@contours = contours
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
attr_reader :contours
|
|
39
|
+
|
|
40
|
+
# Returns the total horizontal overlap of all contours with a
|
|
41
|
+
# text line at the given y range within the given x range.
|
|
42
|
+
# Sum, not max — multiple overlapping contours compound.
|
|
43
|
+
# Returns 0.0 when there's no overlap.
|
|
44
|
+
def overlap_width(line_y, line_height, frame_x, frame_right)
|
|
45
|
+
@contours.sum do |contour|
|
|
46
|
+
overlap = line_overlap(contour, line_y, line_height,
|
|
47
|
+
frame_x, frame_right)
|
|
48
|
+
overlap[:width]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.contour_for(item, page_height)
|
|
53
|
+
pref = wrap_preference(item)
|
|
54
|
+
return nil unless pref
|
|
55
|
+
return nil unless bounding_box_mode?(pref)
|
|
56
|
+
|
|
57
|
+
bounds = item.geometric_bounds
|
|
58
|
+
return nil unless bounds
|
|
59
|
+
|
|
60
|
+
rect = Geometry.placement_rect(bounds, item.item_transform,
|
|
61
|
+
page_height)
|
|
62
|
+
Contour.new(
|
|
63
|
+
x: rect[:x], y: rect[:y],
|
|
64
|
+
width: rect[:width], height: rect[:height]
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
private_class_method :contour_for
|
|
68
|
+
|
|
69
|
+
# Page item types that can declare TextWrapPreference per the
|
|
70
|
+
# Spread schema. Used to guard the attribute read — querying
|
|
71
|
+
# non-shape items (Page, Link) would raise NoMethodError.
|
|
72
|
+
WRAPPABLE_TYPES = [
|
|
73
|
+
Idml::Elements::Rectangle,
|
|
74
|
+
Idml::Elements::Oval,
|
|
75
|
+
Idml::Elements::Polygon,
|
|
76
|
+
Idml::Elements::GraphicLine,
|
|
77
|
+
Idml::Elements::Path,
|
|
78
|
+
Idml::Elements::Group,
|
|
79
|
+
].freeze
|
|
80
|
+
|
|
81
|
+
def self.wrap_preference(item)
|
|
82
|
+
return nil unless wrappable?(item)
|
|
83
|
+
|
|
84
|
+
pref = item.text_wrap_preference
|
|
85
|
+
return nil if pref.nil? ||
|
|
86
|
+
pref.text_wrap_mode.nil? ||
|
|
87
|
+
pref.text_wrap_mode == "None"
|
|
88
|
+
|
|
89
|
+
pref
|
|
90
|
+
end
|
|
91
|
+
private_class_method :wrap_preference
|
|
92
|
+
|
|
93
|
+
def self.wrappable?(item)
|
|
94
|
+
WRAPPABLE_TYPES.any? { |type| item.is_a?(type) }
|
|
95
|
+
end
|
|
96
|
+
private_class_method :wrappable?
|
|
97
|
+
|
|
98
|
+
def self.bounding_box_mode?(pref)
|
|
99
|
+
pref.text_wrap_mode == "BoundingBox"
|
|
100
|
+
end
|
|
101
|
+
private_class_method :bounding_box_mode?
|
|
102
|
+
|
|
103
|
+
def line_overlap(contour, line_y, line_height, frame_x, frame_right)
|
|
104
|
+
return { width: 0.0 } unless y_overlap?(contour, line_y, line_height)
|
|
105
|
+
return { width: 0.0 } unless x_overlap?(contour, frame_x, frame_right)
|
|
106
|
+
|
|
107
|
+
{ width: x_overlap_width(contour, frame_x, frame_right) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def y_overlap?(contour, line_y, line_height)
|
|
111
|
+
line_top = line_y + line_height
|
|
112
|
+
line_bottom = line_y
|
|
113
|
+
contour_top = contour.y + contour.height
|
|
114
|
+
contour_bottom = contour.y
|
|
115
|
+
line_top > contour_bottom && line_bottom < contour_top
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def x_overlap?(contour, frame_x, frame_right)
|
|
119
|
+
contour.x < frame_right && contour.x + contour.width > frame_x
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def x_overlap_width(contour, frame_x, frame_right)
|
|
123
|
+
left = [contour.x, frame_x].max
|
|
124
|
+
right = [contour.x + contour.width, frame_right].min
|
|
125
|
+
[right - left, 0].max
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
data/lib/idml/render.rb
CHANGED
|
@@ -21,6 +21,7 @@ module Idml
|
|
|
21
21
|
autoload :StoryChainController, "#{__dir__}/render/story_chain_controller"
|
|
22
22
|
autoload :LayerFilter, "#{__dir__}/render/layer_filter"
|
|
23
23
|
autoload :ConditionFilter, "#{__dir__}/render/condition_filter"
|
|
24
|
+
autoload :TextWrapResolver, "#{__dir__}/render/text_wrap_resolver"
|
|
24
25
|
autoload :Placement, "#{__dir__}/render/placement"
|
|
25
26
|
autoload :ImageCollector, "#{__dir__}/render/image_collector"
|
|
26
27
|
autoload :StructureTracker, "#{__dir__}/render/structure_tracker"
|
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.7
|
|
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-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bigdecimal
|
|
@@ -475,6 +475,7 @@ files:
|
|
|
475
475
|
- lib/idml/render/structure_tracker.rb
|
|
476
476
|
- lib/idml/render/style_lookup.rb
|
|
477
477
|
- lib/idml/render/style_resolver.rb
|
|
478
|
+
- lib/idml/render/text_wrap_resolver.rb
|
|
478
479
|
- lib/idml/text_engine.rb
|
|
479
480
|
- lib/idml/text_engine/cjk_layout.rb
|
|
480
481
|
- lib/idml/text_engine/justifier.rb
|