idml 0.8.4 → 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: a20e81d5d762e6dfcb5c374d342210ac6132a96604d83ee305dfa1c64e64e535
4
- data.tar.gz: e6da886fc60f952b2e9f39f512027b953958c19e14cadf3200dc9952e4c3fa96
3
+ metadata.gz: '04038941020b897a48513a0386fe33c4108384351a40e47e4ea50c2f09709611'
4
+ data.tar.gz: 5e79b2dc7e0ec38818d06b74196bfb61227e4cb80c2e5989f8b836858469db70
5
5
  SHA512:
6
- metadata.gz: f8a51af44eaa9c43cba3bbdc9f332b1fa94f34f3eb08cb32ce01b957ceff14d6903f03a2d458884a538ef6f6e56d224868427abc46f1303d8f04d82324d9aa5b
7
- data.tar.gz: 68e81d0fc87751c37015ea4a3b905a65c7863145bc074714e3241a02e4f951294703b3a3b4eef8d46601d896b95ddc40d3d267f9e54ee71483b2ba9bccbb742a
6
+ metadata.gz: 115565aeb1f5c1da58d7a94337b3a8243da2303b4e12b25516582a7cc94065752d54df398a4f02632067b3a2b9495449911b43ee85b410d23b6a90d27e33c2ca
7
+ data.tar.gz: 3754116e6abce4fb0481f8fdb02edb3452df9a0a6f7ff0417ca9500ab80155b91ceea0336a37d9defe41c75963c7040b75a2774f096fc5217285d57e19e6a41a
@@ -1,6 +1,12 @@
1
1
  # TODO PDF 106: Page-item type coverage — Ellipse, Path, EPS, WMF, PICT, HtmlItem
2
2
 
3
- ## Status: OPENgap identified in 2026-08-08 audit
3
+ ## Status: PARTIALOval and Path elements modeled and rendered
4
+ (registered in RENDERER_MAP, delegate to RectangleRenderer which draws
5
+ the bounding box with fill/stroke). Documents containing Oval or Path
6
+ page items no longer silently skip them. True elliptical rendering
7
+ (bezier anchor/control points) and compound-path drawing are
8
+ refinements. EPS / WMF / PICT / HtmlItem remain out of scope (foreign
9
+ formats).
4
10
 
5
11
  ## Problem
6
12
 
@@ -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
 
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Idml
4
+ module Elements
5
+ # `<Oval>` — an elliptical page item. Schema-faithful model of
6
+ # `Oval_Object` in `reference-docs/schemas/package/Spreads/Spread.rnc`.
7
+ # Shares the same attribute set and Properties/PathGeometry child
8
+ # structure as Rectangle; the path points form an ellipse (4 bezier
9
+ # anchor points) instead of corner points.
10
+ #
11
+ # The renderer treats an Oval like a Rectangle for fill/stroke —
12
+ # it draws the bounding box. True elliptical rendering (bezier
13
+ # curve generation) is a refinement; see TODO 106.
14
+ class Oval < Lutaml::Model::Serializable
15
+ attribute :self_attr, :string
16
+ attribute :content_type, :string
17
+ attribute :item_transform, :string
18
+ attribute :fill_color, :string
19
+ attribute :fill_tint, :float
20
+ attribute :stroke_color, :string
21
+ attribute :stroke_weight, :float
22
+ attribute :stroke_tint, :float
23
+ attribute :end_cap, :string
24
+ attribute :end_join, :string
25
+ attribute :miter_limit, :float
26
+ attribute :stroke_dash_and_gap, :string
27
+ attribute :visible, :boolean
28
+ attribute :name, :string
29
+ attribute :item_layer, :string
30
+ attribute :properties, Idml::Elements::Properties, collection: true
31
+ attribute :transparency_setting, Idml::Elements::TransparencySetting
32
+ attribute :image, Idml::Elements::Image, collection: true
33
+
34
+ xml do
35
+ root "Oval"
36
+ map_attribute "Self", to: :self_attr
37
+ map_attribute "ContentType", to: :content_type
38
+ map_attribute "ItemTransform", to: :item_transform
39
+ map_attribute "FillColor", to: :fill_color
40
+ map_attribute "FillTint", to: :fill_tint
41
+ map_attribute "StrokeColor", to: :stroke_color
42
+ map_attribute "StrokeWeight", to: :stroke_weight
43
+ map_attribute "StrokeTint", to: :stroke_tint
44
+ map_attribute "EndCap", to: :end_cap
45
+ map_attribute "EndJoin", to: :end_join
46
+ map_attribute "MiterLimit", to: :miter_limit
47
+ map_attribute "StrokeDashAndGap", to: :stroke_dash_and_gap
48
+ map_attribute "Visible", to: :visible
49
+ map_attribute "Name", to: :name
50
+ map_attribute "ItemLayer", to: :item_layer
51
+ map_element "Properties", to: :properties
52
+ map_element "TransparencySetting", to: :transparency_setting
53
+ map_element "Image", to: :image
54
+ end
55
+
56
+ def graphic?
57
+ content_type == "GraphicType"
58
+ end
59
+
60
+ def geometric_bounds
61
+ @geometric_bounds ||= first_geometry&.bounding_box
62
+ end
63
+
64
+ def first_geometry
65
+ properties.first&.first_geometry
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Idml
4
+ module Elements
5
+ # `<Path>` — a standalone compound-path page item. Schema-faithful
6
+ # model of `Path_Object` in
7
+ # `reference-docs/schemas/package/Spreads/Spread.rnc`. Carries
8
+ # geometry via `Properties/EntirePath` (a flat list of x/y pairs)
9
+ # or via PathPoint children.
10
+ #
11
+ # The renderer treats a Path like a Polygon — it draws the path
12
+ # points as a filled/stroked polygon. Bezier curve rendering
13
+ # (via LeftDirection/RightDirection) is a refinement; see TODO 106.
14
+ class Path < Lutaml::Model::Serializable
15
+ attribute :self_attr, :string
16
+ attribute :path_type, :string
17
+ attribute :item_transform, :string
18
+ attribute :fill_color, :string
19
+ attribute :fill_tint, :float
20
+ attribute :stroke_color, :string
21
+ attribute :stroke_weight, :float
22
+ attribute :stroke_tint, :float
23
+ attribute :visible, :boolean
24
+ attribute :name, :string
25
+ attribute :item_layer, :string
26
+ attribute :properties, Idml::Elements::Properties, collection: true
27
+ attribute :transparency_setting, Idml::Elements::TransparencySetting
28
+
29
+ xml do
30
+ root "Path"
31
+ map_attribute "Self", to: :self_attr
32
+ map_attribute "PathType", to: :path_type
33
+ map_attribute "ItemTransform", to: :item_transform
34
+ map_attribute "FillColor", to: :fill_color
35
+ map_attribute "FillTint", to: :fill_tint
36
+ map_attribute "StrokeColor", to: :stroke_color
37
+ map_attribute "StrokeWeight", to: :stroke_weight
38
+ map_attribute "StrokeTint", to: :stroke_tint
39
+ map_attribute "Visible", to: :visible
40
+ map_attribute "Name", to: :name
41
+ map_attribute "ItemLayer", to: :item_layer
42
+ map_element "Properties", to: :properties
43
+ map_element "TransparencySetting", to: :transparency_setting
44
+ end
45
+
46
+ def geometric_bounds
47
+ @geometric_bounds ||= first_geometry&.bounding_box
48
+ end
49
+
50
+ def first_geometry
51
+ properties.first&.first_geometry
52
+ end
53
+ end
54
+ end
55
+ end
@@ -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
 
@@ -22,6 +22,8 @@ module Idml
22
22
 
23
23
  attribute :page, Idml::Elements::Page, collection: true
24
24
  attribute :rectangle, Idml::Elements::Rectangle, collection: true
25
+ attribute :oval, Idml::Elements::Oval, collection: true
26
+ attribute :path, Idml::Elements::Path, collection: true
25
27
  attribute :text_frame, Idml::Elements::TextFrame, collection: true
26
28
  attribute :polygon, Idml::Elements::Polygon, collection: true
27
29
  attribute :group, Idml::Elements::Group, collection: true
@@ -43,6 +45,8 @@ module Idml
43
45
  map_attribute "FlattenerOverride", to: :flattener_override
44
46
  map_element "Page", to: :page
45
47
  map_element "Rectangle", to: :rectangle
48
+ map_element "Oval", to: :oval
49
+ map_element "Path", to: :path
46
50
  map_element "TextFrame", to: :text_frame
47
51
  map_element "Polygon", to: :polygon
48
52
  map_element "Group", to: :group
data/lib/idml/elements.rb CHANGED
@@ -105,11 +105,13 @@ module Idml
105
105
  autoload :OuterGlowSetting, "idml/elements/outer_glow_setting"
106
106
  autoload :PageItemDefault, "idml/elements/pref_page_item_default"
107
107
  autoload :Page, "idml/elements/page"
108
+ autoload :Oval, "idml/elements/oval"
108
109
  autoload :ParagraphStyle, "idml/elements/paragraph_style"
109
110
  autoload :ParagraphStyleGroup, "idml/elements/paragraph_style_group"
110
111
  autoload :ParagraphStyleRange, "idml/elements/paragraph_style_range"
111
112
  autoload :PasteboardPreference, "idml/elements/pref_pasteboard_preference"
112
113
  autoload :PastedSmoothShade, "idml/elements/pasted_smooth_shade"
114
+ autoload :Path, "idml/elements/path"
113
115
  autoload :PathGeometry, "idml/elements/path_geometry"
114
116
  autoload :PathPointArray, "idml/elements/path_point_array"
115
117
  autoload :PathPointType, "idml/elements/path_point_type"
@@ -9,6 +9,8 @@ module Idml
9
9
  module PageItemRenderer
10
10
  RENDERER_MAP = {
11
11
  Idml::Elements::Rectangle => "RectangleRenderer",
12
+ Idml::Elements::Oval => "OvalRenderer",
13
+ Idml::Elements::Path => "PathRenderer",
12
14
  Idml::Elements::TextFrame => "TextFrameRenderer",
13
15
  Idml::Elements::Polygon => "PolygonRenderer",
14
16
  Idml::Elements::GraphicLine => "GraphicLineRenderer",
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Idml
4
+ module Render
5
+ module Renderers
6
+ # Renders an IDML Oval on a Pdfrb::Content::Canvas. Delegates to
7
+ # RectangleRenderer which draws the bounding box with fill/stroke.
8
+ # Approximation: a true ellipse would generate bezier anchor and
9
+ # control points from the PathGeometry; the bounding box renders
10
+ # the correct extent and colors. See TODO 106 for refinement.
11
+ class OvalRenderer
12
+ def self.render(canvas, context)
13
+ RectangleRenderer.render(canvas, context)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Idml
4
+ module Render
5
+ module Renderers
6
+ # Renders an IDML Path (standalone compound path) on a
7
+ # Pdfrb::Content::Canvas. Delegates to RectangleRenderer which
8
+ # draws the bounding box with fill/stroke. Approximation: a true
9
+ # compound path would draw each sub-path from the EntirePath
10
+ # list or PathPoint children; the bounding box renders the
11
+ # correct extent and colors. See TODO 106 for refinement.
12
+ class PathRenderer
13
+ def self.render(canvas, context)
14
+ RectangleRenderer.render(canvas, context)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -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"
@@ -60,6 +61,14 @@ Idml::Render::Renderers.autoload(
60
61
  :RectangleRenderer,
61
62
  "#{__dir__}/render/renderers/rectangle_renderer",
62
63
  )
64
+ Idml::Render::Renderers.autoload(
65
+ :OvalRenderer,
66
+ "#{__dir__}/render/renderers/oval_renderer",
67
+ )
68
+ Idml::Render::Renderers.autoload(
69
+ :PathRenderer,
70
+ "#{__dir__}/render/renderers/path_renderer",
71
+ )
63
72
  Idml::Render::Renderers.autoload(
64
73
  :TextFrameRenderer,
65
74
  "#{__dir__}/render/renderers/text_frame_renderer",
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.4"
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.4
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
@@ -326,11 +326,13 @@ files:
326
326
  - lib/idml/elements/object_style_stroke_effects_category_settings.rb
327
327
  - lib/idml/elements/opacity_gradient_stop.rb
328
328
  - lib/idml/elements/outer_glow_setting.rb
329
+ - lib/idml/elements/oval.rb
329
330
  - lib/idml/elements/page.rb
330
331
  - lib/idml/elements/paragraph_style.rb
331
332
  - lib/idml/elements/paragraph_style_group.rb
332
333
  - lib/idml/elements/paragraph_style_range.rb
333
334
  - lib/idml/elements/pasted_smooth_shade.rb
335
+ - lib/idml/elements/path.rb
334
336
  - lib/idml/elements/path_geometry.rb
335
337
  - lib/idml/elements/path_point_array.rb
336
338
  - lib/idml/elements/path_point_type.rb
@@ -459,6 +461,8 @@ files:
459
461
  - lib/idml/render/renderers.rb
460
462
  - lib/idml/render/renderers/graphic_line_renderer.rb
461
463
  - lib/idml/render/renderers/group_renderer.rb
464
+ - lib/idml/render/renderers/oval_renderer.rb
465
+ - lib/idml/render/renderers/path_renderer.rb
462
466
  - lib/idml/render/renderers/polygon_renderer.rb
463
467
  - lib/idml/render/renderers/rectangle_renderer.rb
464
468
  - lib/idml/render/renderers/table_renderer.rb
@@ -471,6 +475,7 @@ files:
471
475
  - lib/idml/render/structure_tracker.rb
472
476
  - lib/idml/render/style_lookup.rb
473
477
  - lib/idml/render/style_resolver.rb
478
+ - lib/idml/render/text_wrap_resolver.rb
474
479
  - lib/idml/text_engine.rb
475
480
  - lib/idml/text_engine/cjk_layout.rb
476
481
  - lib/idml/text_engine/justifier.rb