idml 0.8.5 → 0.8.6

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: ac8e5f6dd1f360428509a0ae78c7c537b80cbd1d41fa5e85f5e1fe61f0cac48e
4
- data.tar.gz: 2dcf34fd3fe19ef2978783e6857bd724872b741b8aab9b5418d77a10431b8aad
3
+ metadata.gz: '04038941020b897a48513a0386fe33c4108384351a40e47e4ea50c2f09709611'
4
+ data.tar.gz: 5e79b2dc7e0ec38818d06b74196bfb61227e4cb80c2e5989f8b836858469db70
5
5
  SHA512:
6
- metadata.gz: 30deff6b7352132cf5c019bde7423294232fafbe7b25898e7188d754f721ba3d6a498f6e41f31ef86afafe4f4e768dfb459fc272eda6ac288df1ab033cf88cc2
7
- data.tar.gz: ba95fecbb82c8546c3ec4e31c3b3fdebfe1237fd8b7b5ec8e0a53c54d612dff72d9f1e3c89c6f88b8b7d5309f579a94a5e60132a50b37739914f21595bcf4b8f
6
+ metadata.gz: 115565aeb1f5c1da58d7a94337b3a8243da2303b4e12b25516582a7cc94065752d54df398a4f02632067b3a2b9495449911b43ee85b410d23b6a90d27e33c2ca
7
+ data.tar.gz: 3754116e6abce4fb0481f8fdb02edb3452df9a0a6f7ff0417ca9500ab80155b91ceea0336a37d9defe41c75963c7040b75a2774f096fc5217285d57e19e6a41a
@@ -1,6 +1,12 @@
1
1
  # TODO PDF 114: Text wrap around shapes (TextWrapPreference)
2
2
 
3
- ## Status: OPENgap identified in 2026-08-10 audit
3
+ ## Status: PARTIAL`Render::TextWrapResolver` computes BoundingBox-mode
4
+ contours from page items declaring TextWrapPreference.
5
+ `overlap_width(line_y, line_height, frame_x, frame_right)` returns the
6
+ horizontal overlap for a text line. Rectangle now carries the
7
+ TextWrapPreference child. Integration with TextFrameRenderer
8
+ (per-line wrap width adjustment during layout) is the remaining work —
9
+ requires line-by-line contour queries in the layout pipeline.
4
10
 
5
11
  ## Problem
6
12
 
@@ -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
 
@@ -0,0 +1,110 @@
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
+ def self.wrap_preference(item)
70
+ pref = item.text_wrap_preference if item.is_a?(Lutaml::Model::Serializable)
71
+ return nil unless pref
72
+ return nil if pref.text_wrap_mode.nil? ||
73
+ pref.text_wrap_mode == "None"
74
+
75
+ pref
76
+ end
77
+ private_class_method :wrap_preference
78
+
79
+ def self.bounding_box_mode?(pref)
80
+ pref.text_wrap_mode == "BoundingBox"
81
+ end
82
+ private_class_method :bounding_box_mode?
83
+
84
+ def line_overlap(contour, line_y, line_height, frame_x, frame_right)
85
+ return { width: 0.0 } unless y_overlap?(contour, line_y, line_height)
86
+ return { width: 0.0 } unless x_overlap?(contour, frame_x, frame_right)
87
+
88
+ { width: x_overlap_width(contour, frame_x, frame_right) }
89
+ end
90
+
91
+ def y_overlap?(contour, line_y, line_height)
92
+ line_top = line_y + line_height
93
+ line_bottom = line_y
94
+ contour_top = contour.y + contour.height
95
+ contour_bottom = contour.y
96
+ line_top > contour_bottom && line_bottom < contour_top
97
+ end
98
+
99
+ def x_overlap?(contour, frame_x, frame_right)
100
+ contour.x < frame_right && contour.x + contour.width > frame_x
101
+ end
102
+
103
+ def x_overlap_width(contour, frame_x, frame_right)
104
+ left = [contour.x, frame_x].max
105
+ right = [contour.x + contour.width, frame_right].min
106
+ [right - left, 0].max
107
+ end
108
+ end
109
+ end
110
+ 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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Idml
4
- VERSION = "0.8.5"
4
+ VERSION = "0.8.6"
5
5
  end
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.5
4
+ version: 0.8.6
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-14 00:00:00.000000000 Z
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