idml 0.8.8 → 0.8.9

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: ad2709aae7d11f8a91843141700ab7882b9b7a196480d7b6077fe4f0b6473d16
4
- data.tar.gz: b6fd8d8df9996dabd3fd4ffbfc9fb788930cb3ca04bb6f014ae8b3c9fce5b6af
3
+ metadata.gz: d58f00746f6afb4233e161dfa9cdeb116d714eed0c238ec543a0f039b4167f0e
4
+ data.tar.gz: 1a000e34996525fe34cdc441625c9eaa303f121c836ae3f3b07b73c12712abb1
5
5
  SHA512:
6
- metadata.gz: 20154b884fb1688985cdca3c7a3866e7c55be9059768d4a00eae4058bdd237f3606c964318d224bdd198853bc61a0a9167012b5dc6cc558f4a31537146a45a27
7
- data.tar.gz: 7075c7451518b208e6ce17da854fb9954048ed40825287dc87bf4b4543467a0677a277fdf49f124703c353dfc4faf1b770de936d69acf1b0958998887a1cb101
6
+ metadata.gz: 15001cbf1427dc278c701cd4980b837ff4b015f6c5657eeb5c88c0c8774933934e606439c40fa3c900368ef2959fc1170cd12841a9caadc0c025a199dd18cd68
7
+ data.tar.gz: 41f45b1b2db245e1dd2d5ef40aa60990fc3c957930b3d16a633974d1f267149e8b2c5cf638b7b3549b4318bf8ea62f378fbb5d0f98ad32d4d50fbce0e3cb9505
@@ -1,12 +1,42 @@
1
1
  # TODO PDF 106: Page-item type coverage — Ellipse, Path, EPS, WMF, PICT, HtmlItem
2
2
 
3
- ## Status: PARTIAL Oval 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).
3
+ ## Status: COMPLETE (rendering scope) implemented 2026-08-17
4
+
5
+ ## What was built (2026-08-17 refinement: true contours)
6
+
7
+ - `Render::Contour` draws an item's PathGeometry as true Bézier
8
+ contours: PathPointType anchors, LeftDirection / RightDirection
9
+ control handles, ItemTransform applied per point, y flipped into
10
+ PDF space. One subpath per GeometryPathType (compound paths);
11
+ closed unless PathOpen="true"; degenerate handles fall back to the
12
+ anchor. OvalRenderer, PathRenderer, and PolygonRenderer all
13
+ delegate here — ovals render as real ellipses (rotation preserved
14
+ through the transform), polygons as their actual outlines, Paths
15
+ as compound contours. Bounding-box rectangle fallback when an
16
+ item has no PathGeometry but has geometric_bounds.
17
+ - `Render::ShapePaint` — the single owner of the IDML → PDF paint
18
+ mapping (solid fill, gradient shading fill, stroke styling, and
19
+ the PDF paint-op sequencing including fill_stroke on one path).
20
+ RectangleRenderer now delegates to it; the fill/stroke/gradient
21
+ logic it previously duplicated across shape renderers lives in
22
+ one place.
23
+ - Oval/Path modeled + registered in RENDERER_MAP (earlier
24
+ milestone); Polygon renders contours instead of its bounding box.
25
+
26
+ ## Out of scope (unchanged)
27
+
28
+ - EPS / WMF / PICT / HtmlItem / Movie / Sound / FormField — foreign
29
+ or interactive formats, not modeled. Image placement goes through
30
+ ImageCollector (separate, model-driven path).
31
+
32
+ ## Acceptance criteria
33
+
34
+ - [x] SpreadObject has `oval` + `path` collections (Ellipse is the
35
+ Oval element in the schema's naming).
36
+ - [x] OvalRenderer / PathRenderer registered in RENDERER_MAP.
37
+ - [x] True Bézier rendering from PathGeometry anchors + control
38
+ points (TODO 106 refinement complete).
39
+ - [x] Compound Path objects render with multiple sub-paths.
10
40
 
11
41
  ## Problem
12
42
 
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Idml
4
+ module Render
5
+ # Renders a page item's PathGeometry as true Bézier contours on
6
+ # a pdfrb canvas. Shared by OvalRenderer, PathRenderer, and
7
+ # PolygonRenderer: every item whose Properties carry a
8
+ # PathGeometry draws its actual outline — PathPointType anchors
9
+ # with LeftDirection / RightDirection control handles,
10
+ # ItemTransform applied, y flipped into PDF space — instead of
11
+ # its bounding box. Paint op selection (fill / stroke /
12
+ # gradient) lives in ShapePaint. Items without usable geometry
13
+ # fall back to the bounding-box rectangle.
14
+ module Contour
15
+ # Renders the item's contours with fill/stroke from
16
+ # ShapePaint. Returns true when painted.
17
+ def self.render(canvas, context)
18
+ item = context.item
19
+ return false if item.visible == false
20
+
21
+ paths = geometry_paths(item)
22
+ return rectangle_fallback(canvas, item, context) if paths.empty?
23
+
24
+ ShapePaint.paint(canvas, item, context) do |c|
25
+ draw_path(c, paths, item.item_transform, context.page_height)
26
+ end
27
+ end
28
+
29
+ def self.rectangle_fallback(canvas, item, context)
30
+ box = Placement.box(item, context.page_height)
31
+ return false unless box
32
+
33
+ ShapePaint.paint(canvas, item, context) do |c|
34
+ c.rectangle(box[:x], box[:y], box[:width], box[:height])
35
+ end
36
+ end
37
+ private_class_method :rectangle_fallback
38
+
39
+ # All GeometryPathType children across the item's Properties.
40
+ def self.geometry_paths(item)
41
+ item.properties.flat_map do |props|
42
+ props.path_geometry.flat_map(&:geometry_path_type)
43
+ end
44
+ end
45
+
46
+ # Constructs the full path on the canvas: one subpath per
47
+ # GeometryPathType, each starting at its first anchor and
48
+ # curving through every subsequent anchor via the points'
49
+ # control handles. Closed paths (PathOpen false or absent)
50
+ # loop back to the first anchor and close.
51
+ def self.draw_path(canvas, paths, item_transform, page_height)
52
+ transform = Geometry.parse_transform(item_transform)
53
+ paths.each do |path|
54
+ draw_subpath(canvas, path, transform, page_height)
55
+ end
56
+ end
57
+
58
+ def self.draw_subpath(canvas, path, transform, page_height)
59
+ points = path.points
60
+ return if points.length < 2
61
+
62
+ open = path.path_open == true
63
+ anchors = points.map do |point|
64
+ pdf_pair(point.anchor, transform, page_height)
65
+ end
66
+ canvas.move_to(anchors.first[0], anchors.first[1])
67
+ draw_segments(canvas, points, anchors, transform, page_height, open)
68
+ canvas.close_path unless open
69
+ end
70
+ private_class_method :draw_subpath
71
+
72
+ def self.draw_segments(canvas, points, anchors, transform,
73
+ page_height, open)
74
+ segment_count = open ? points.length - 1 : points.length
75
+ segment_count.times do |index|
76
+ from = points[index]
77
+ to = points[(index + 1) % points.length]
78
+ c1 = control_point(from, :right_direction, from.anchor,
79
+ transform, page_height)
80
+ c2 = control_point(to, :left_direction, to.anchor,
81
+ transform, page_height)
82
+ to_anchor = anchors[(index + 1) % points.length]
83
+ canvas.curve_to(c1[0], c1[1], c2[0], c2[1],
84
+ to_anchor[0], to_anchor[1])
85
+ end
86
+ end
87
+ private_class_method :draw_segments
88
+
89
+ # Control point for one end of a segment: the point's
90
+ # direction handle when present, else the anchor itself
91
+ # (degenerate control = straight-ish curve).
92
+ def self.control_point(point, direction_attr, fallback, transform,
93
+ page_height)
94
+ raw = point.public_send(direction_attr)
95
+ raw = nil if raw && raw.strip.empty?
96
+ pdf_pair(raw || fallback, transform, page_height)
97
+ end
98
+ private_class_method :control_point
99
+
100
+ # Parses an IDML "x y" pair, applies the item transform, and
101
+ # flips y into PDF space (origin bottom-left).
102
+ def self.pdf_pair(raw, transform, page_height)
103
+ x, y = raw.to_s.split(/\s+/).map(&:to_f)
104
+ tx, ty = Geometry.apply_transform(transform, x, y)
105
+ [tx, page_height - ty]
106
+ end
107
+ private_class_method :pdf_pair
108
+ end
109
+ end
110
+ end
@@ -3,14 +3,13 @@
3
3
  module Idml
4
4
  module Render
5
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.
6
+ # Renders an IDML Oval on a Pdfrb::Content::Canvas as a true
7
+ # Bézier ellipse from its PathGeometry (which preserves any
8
+ # rotation in the ItemTransform). Falls back to the
9
+ # bounding-box rectangle when the item carries no geometry.
11
10
  class OvalRenderer
12
11
  def self.render(canvas, context)
13
- RectangleRenderer.render(canvas, context)
12
+ Contour.render(canvas, context)
14
13
  end
15
14
  end
16
15
  end
@@ -4,14 +4,13 @@ module Idml
4
4
  module Render
5
5
  module Renderers
6
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.
7
+ # Pdfrb::Content::Canvas as its actual Bézier contours — one
8
+ # subpath per GeometryPathType, closed or open per PathOpen.
9
+ # Falls back to the bounding-box rectangle when the item
10
+ # carries no geometry.
12
11
  class PathRenderer
13
12
  def self.render(canvas, context)
14
- RectangleRenderer.render(canvas, context)
13
+ Contour.render(canvas, context)
15
14
  end
16
15
  end
17
16
  end
@@ -3,46 +3,15 @@
3
3
  module Idml
4
4
  module Render
5
5
  module Renderers
6
+ # Renders an IDML Polygon on a Pdfrb::Content::Canvas as its
7
+ # actual Bézier contours from PathGeometry (control handles
8
+ # default to the anchors, so plain polygons render straight
9
+ # edges). Falls back to the bounding-box rectangle when the
10
+ # item carries no geometry.
6
11
  class PolygonRenderer
7
12
  def self.render(canvas, context)
8
- poly = context.item
9
- return if poly.visible == false
10
-
11
- box = Placement.box(poly, context.page_height)
12
- return unless box
13
-
14
- Blending.wrap(canvas, poly.transparency_setting) do
15
- render_fill(canvas, poly, context, box)
16
- render_stroke(canvas, poly, context, box)
17
- end
18
- end
19
-
20
- def self.render_fill(canvas, poly, context, box)
21
- return unless poly.fill_color && poly.fill_color != "Color/None"
22
-
23
- color = context.color_resolver&.resolve(poly.fill_color)
24
- return unless color
25
-
26
- canvas.fill_color(ColorHelper.to_canvas(color))
27
- canvas.rectangle(box[:x], box[:y], box[:width], box[:height])
28
- canvas.fill
29
- end
30
- private_class_method :render_fill
31
-
32
- def self.render_stroke(canvas, poly, context, box)
33
- return unless StrokeStyle.strokeable?(poly)
34
-
35
- color = context.color_resolver&.resolve(poly.stroke_color)
36
- return unless color
37
-
38
- StrokeStyle.apply(canvas, poly) do
39
- canvas.stroke_color(ColorHelper.to_canvas(color))
40
- canvas.line_width = poly.stroke_weight
41
- canvas.rectangle(box[:x], box[:y], box[:width], box[:height])
42
- canvas.stroke
43
- end
13
+ Contour.render(canvas, context)
44
14
  end
45
- private_class_method :render_stroke
46
15
  end
47
16
  end
48
17
  end
@@ -3,7 +3,9 @@
3
3
  module Idml
4
4
  module Render
5
5
  module Renderers
6
- # Renders an IDML Rectangle on a Pdfrb::Content::Canvas.
6
+ # Renders an IDML Rectangle on a Pdfrb::Content::Canvas: an
7
+ # axis-aligned rectangle from the item's placement box, with
8
+ # fill / stroke / gradient painting delegated to ShapePaint.
7
9
  class RectangleRenderer
8
10
  def self.render(canvas, context)
9
11
  rect = context.item
@@ -12,101 +14,10 @@ module Idml
12
14
  box = Placement.box(rect, context.page_height)
13
15
  return unless box
14
16
 
15
- Blending.wrap(canvas, rect.transparency_setting) do
16
- render_fill(canvas, rect, context, box)
17
- render_stroke(canvas, rect, context, box)
17
+ ShapePaint.paint(canvas, rect, context) do |c|
18
+ c.rectangle(box[:x], box[:y], box[:width], box[:height])
18
19
  end
19
20
  end
20
-
21
- def self.render_fill(canvas, rect, context, box)
22
- return unless rect.fill_color
23
- return if rect.fill_color == "Color/None"
24
-
25
- if GradientResolver.gradient?(rect.fill_color)
26
- render_gradient_fill(canvas, rect, context, box)
27
- return
28
- end
29
-
30
- color = context.color_resolver&.resolve(rect.fill_color)
31
- return unless color
32
-
33
- canvas.fill_color(ColorHelper.to_canvas(color))
34
- canvas.rectangle(box[:x], box[:y], box[:width], box[:height])
35
- canvas.fill
36
- end
37
- private_class_method :render_fill
38
-
39
- def self.render_gradient_fill(canvas, rect, context, box)
40
- gradient = gradient_for(rect, context)
41
- return unless gradient
42
- return unless gradient.gradient_stop.length >= 2
43
-
44
- stops = gradient_stops(gradient, context)
45
- return unless stops.length >= 2
46
-
47
- shading = if gradient.type == "Radial"
48
- radial_shading(canvas, box, stops)
49
- else
50
- axial_shading(canvas, box, stops)
51
- end
52
- canvas.rectangle(box[:x], box[:y], box[:width], box[:height])
53
- canvas.fill_shading(shading)
54
- end
55
- private_class_method :render_gradient_fill
56
-
57
- def self.axial_shading(canvas, box, stops)
58
- canvas.document.shadings.add_axial(
59
- from: [box[:x], box[:y] + box[:height]],
60
- to: [box[:x] + box[:width], box[:y]],
61
- stops: stops,
62
- )
63
- end
64
- private_class_method :axial_shading
65
-
66
- def self.radial_shading(canvas, box, stops)
67
- cx = box[:x] + (box[:width] / 2.0)
68
- cy = box[:y] + (box[:height] / 2.0)
69
- radius = [box[:width], box[:height]].max / 2.0
70
- canvas.document.shadings.add_radial(
71
- from: [cx, cy, 0.0],
72
- to: [cx, cy, radius],
73
- stops: stops,
74
- )
75
- end
76
- private_class_method :radial_shading
77
-
78
- def self.gradient_for(rect, context)
79
- graphic = context.package&.graphic
80
- return nil unless graphic
81
-
82
- graphic.gradient.find { |g| g.self_attr == rect.fill_color }
83
- end
84
- private_class_method :gradient_for
85
-
86
- def self.gradient_stops(gradient, context)
87
- gradient.gradient_stop.filter_map do |stop|
88
- color = context.color_resolver&.resolve(stop.stop_color)
89
- next unless color
90
-
91
- [stop.location || 0.0, ColorHelper.to_canvas(color)]
92
- end
93
- end
94
- private_class_method :gradient_stops
95
-
96
- def self.render_stroke(canvas, rect, context, box)
97
- return unless StrokeStyle.strokeable?(rect)
98
-
99
- color = context.color_resolver&.resolve(rect.stroke_color)
100
- return unless color
101
-
102
- StrokeStyle.apply(canvas, rect) do
103
- canvas.stroke_color(ColorHelper.to_canvas(color))
104
- canvas.line_width = rect.stroke_weight
105
- canvas.rectangle(box[:x], box[:y], box[:width], box[:height])
106
- canvas.stroke
107
- end
108
- end
109
- private_class_method :render_stroke
110
21
  end
111
22
  end
112
23
  end
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Idml
4
+ module Render
5
+ # Paints a page item's fill and stroke on a pdfrb canvas,
6
+ # delegating path construction to the caller's block. The
7
+ # single owner of the IDML → PDF paint mapping — solid fill,
8
+ # gradient shading fill, stroke styling — shared by every
9
+ # shape renderer. Callers choose the geometry (axis-aligned
10
+ # rectangle, Bézier contour); this module chooses the paint
11
+ # and the PDF paint op sequencing (fill, stroke, or
12
+ # fill-and-stroke on one path).
13
+ module ShapePaint
14
+ # Paints the item. The block receives the canvas and must
15
+ # construct the current path (move_to/line_to/curve_to/...).
16
+ # The block may be called more than once (a path is consumed
17
+ # by each paint op, so fill-then-stroke rebuilds it).
18
+ # Returns nil when there is nothing to paint; truthy
19
+ # otherwise.
20
+ def self.paint(canvas, item, context, &build_path)
21
+ fill = fill_style(item, context)
22
+ stroke = stroke_style(item, context)
23
+ return unless fill || stroke
24
+
25
+ Blending.wrap(canvas, item.transparency_setting) do
26
+ paint_styles(canvas, item, context, fill, stroke, &build_path)
27
+ end
28
+ end
29
+
30
+ # Returns the fill plan: { kind: :solid, color: } for a
31
+ # resolved color, { kind: :gradient, gradient:, stops: } for
32
+ # a fill referencing a Gradient, nil when nothing fillable.
33
+ def self.fill_style(item, context)
34
+ return nil unless item.fill_color
35
+ return nil if item.fill_color == "Color/None"
36
+
37
+ if GradientResolver.gradient?(item.fill_color)
38
+ gradient_fill_style(item, context)
39
+ else
40
+ solid_fill_style(item, context)
41
+ end
42
+ end
43
+ private_class_method :fill_style
44
+
45
+ def self.gradient_fill_style(item, context)
46
+ gradient = gradient_for(item, context)
47
+ return nil unless gradient
48
+
49
+ stops = gradient_stops(gradient, context)
50
+ return nil unless stops.length >= 2
51
+
52
+ { kind: :gradient, gradient: gradient, stops: stops }
53
+ end
54
+ private_class_method :gradient_fill_style
55
+
56
+ def self.solid_fill_style(item, context)
57
+ color = context.color_resolver&.resolve(item.fill_color)
58
+ return nil unless color
59
+
60
+ { kind: :solid, color: ColorHelper.to_canvas(color) }
61
+ end
62
+ private_class_method :solid_fill_style
63
+
64
+ def self.stroke_style(item, context)
65
+ return nil unless StrokeStyle.strokeable?(item)
66
+
67
+ color = context.color_resolver&.resolve(item.stroke_color)
68
+ return nil unless color
69
+
70
+ { color: ColorHelper.to_canvas(color) }
71
+ end
72
+ private_class_method :stroke_style
73
+
74
+ def self.paint_styles(canvas, item, context, fill, stroke, &)
75
+ if fill && stroke
76
+ paint_fill_and_stroke(canvas, item, context, fill, stroke,
77
+ &)
78
+ elsif fill
79
+ paint_fill(canvas, item, context, fill, &)
80
+ else
81
+ paint_stroke(canvas, item, stroke, &)
82
+ end
83
+ end
84
+ private_class_method :paint_styles
85
+
86
+ def self.paint_fill(canvas, item, context, fill, &)
87
+ yield(canvas)
88
+ if fill[:kind] == :gradient
89
+ canvas.fill_shading(shading_for(canvas, item, context, fill))
90
+ else
91
+ canvas.fill_color(fill[:color])
92
+ canvas.fill
93
+ end
94
+ end
95
+ private_class_method :paint_fill
96
+
97
+ def self.paint_stroke(canvas, item, stroke, &)
98
+ StrokeStyle.apply(canvas, item) do
99
+ canvas.stroke_color(stroke[:color])
100
+ canvas.line_width = item.stroke_weight
101
+ yield(canvas)
102
+ canvas.stroke
103
+ end
104
+ end
105
+ private_class_method :paint_stroke
106
+
107
+ # Fill and stroke on one path where possible (fill_stroke);
108
+ # a gradient fill consumes the path, so the stroke rebuilds
109
+ # it afterward.
110
+ def self.paint_fill_and_stroke(canvas, item, context, fill, stroke,
111
+ &)
112
+ if fill[:kind] == :gradient
113
+ paint_fill(canvas, item, context, fill, &)
114
+ paint_stroke(canvas, item, stroke, &)
115
+ else
116
+ yield(canvas)
117
+ canvas.fill_color(fill[:color])
118
+ StrokeStyle.apply(canvas, item) do
119
+ canvas.stroke_color(stroke[:color])
120
+ canvas.line_width = item.stroke_weight
121
+ canvas.fill_stroke
122
+ end
123
+ end
124
+ end
125
+ private_class_method :paint_fill_and_stroke
126
+
127
+ def self.shading_for(canvas, item, context, fill)
128
+ box = Placement.box(item, context.page_height)
129
+ stops = fill[:stops]
130
+ if fill[:gradient].type == "Radial"
131
+ canvas.document.shadings.add_radial(
132
+ from: radial_center(box, 0.0),
133
+ to: radial_center(box, [box[:width], box[:height]].max / 2.0),
134
+ stops: stops,
135
+ )
136
+ else
137
+ canvas.document.shadings.add_axial(
138
+ from: [box[:x], box[:y] + box[:height]],
139
+ to: [box[:x] + box[:width], box[:y]],
140
+ stops: stops,
141
+ )
142
+ end
143
+ end
144
+ private_class_method :shading_for
145
+
146
+ def self.radial_center(box, radius)
147
+ cx = box[:x] + (box[:width] / 2.0)
148
+ cy = box[:y] + (box[:height] / 2.0)
149
+ [cx, cy, radius]
150
+ end
151
+ private_class_method :radial_center
152
+
153
+ def self.gradient_for(item, context)
154
+ graphic = context.package&.graphic
155
+ return nil unless graphic
156
+
157
+ graphic.gradient.find { |g| g.self_attr == item.fill_color }
158
+ end
159
+ private_class_method :gradient_for
160
+
161
+ def self.gradient_stops(gradient, context)
162
+ gradient.gradient_stop.filter_map do |stop|
163
+ color = context.color_resolver&.resolve(stop.stop_color)
164
+ next unless color
165
+
166
+ [stop.location || 0.0, ColorHelper.to_canvas(color)]
167
+ end
168
+ end
169
+ private_class_method :gradient_stops
170
+ end
171
+ end
172
+ end
data/lib/idml/render.rb CHANGED
@@ -23,6 +23,8 @@ module Idml
23
23
  autoload :ConditionFilter, "#{__dir__}/render/condition_filter"
24
24
  autoload :TextWrapResolver, "#{__dir__}/render/text_wrap_resolver"
25
25
  autoload :Footnote, "#{__dir__}/render/footnote"
26
+ autoload :ShapePaint, "#{__dir__}/render/shape_paint"
27
+ autoload :Contour, "#{__dir__}/render/contour"
26
28
  autoload :Placement, "#{__dir__}/render/placement"
27
29
  autoload :ImageCollector, "#{__dir__}/render/image_collector"
28
30
  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.8"
4
+ VERSION = "0.8.9"
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.8
4
+ version: 0.8.9
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-16 00:00:00.000000000 Z
11
+ date: 2026-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bigdecimal
@@ -439,6 +439,7 @@ files:
439
439
  - lib/idml/render/color_helper.rb
440
440
  - lib/idml/render/color_resolver.rb
441
441
  - lib/idml/render/condition_filter.rb
442
+ - lib/idml/render/contour.rb
442
443
  - lib/idml/render/drop_cap.rb
443
444
  - lib/idml/render/font_reference_resolver.rb
444
445
  - lib/idml/render/font_setup.rb
@@ -469,6 +470,7 @@ files:
469
470
  - lib/idml/render/renderers/rectangle_renderer.rb
470
471
  - lib/idml/render/renderers/table_renderer.rb
471
472
  - lib/idml/render/renderers/text_frame_renderer.rb
473
+ - lib/idml/render/shape_paint.rb
472
474
  - lib/idml/render/spread_renderer.rb
473
475
  - lib/idml/render/story_chain_controller.rb
474
476
  - lib/idml/render/story_threader.rb