hexapdf 0.35.0 → 0.36.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 85a526812bcfbeae9fbe103513c3fac73ac2d736b2ac965305c8c1164fc976b7
4
- data.tar.gz: f999566105d0ac42c5037bf642256fd6c9a0c515bffb2329ef32456009b46dbd
3
+ metadata.gz: b7cec7494ffe5e4f8031e5a05f2a31da13741879ff138513f737048880702389
4
+ data.tar.gz: 238a71920dcd9bde03497a22f0583bf72f11d358176f28217214f86ca835764d
5
5
  SHA512:
6
- metadata.gz: 77d31b7c95a8ffd8c1189b062aac8dd7adc75a78ca3f1d8c91033401a1311b446a387c8c1b52932d5573d7721b5a6d4210433ac44d500fa3389dd17daf1be865
7
- data.tar.gz: 686e6e041a2723a54f1ff5acf0e46c9f7231b4ee5220f4dfe127579549c28c093f28c60ba4470d4be9fba0ccad0202905a57132f1e64596412308ab49b3ab1ed
6
+ metadata.gz: 7850251dba07dbae11c280bc87852c91aa72e166da7983c4b4a1508a4c76d99306eaacbc108dad8f0ba54246a2fe6648714cd8bfd5f6d9bddeb0ec67abab9867
7
+ data.tar.gz: dace9a43ef57a0d27d33218ef4dc6503a961edfc2c96c04c16ebeb0d8675e09373c21908d159c992c4d2125499bdd1818acdf1150c86106e23888552210f4fc8
data/CHANGELOG.md CHANGED
@@ -1,3 +1,40 @@
1
+ ## 0.36.0 - 2024-01-20
2
+
3
+ ### Added
4
+
5
+ * [HexaPDF::Layout::ContainerBox] for grouping child boxes together
6
+
7
+ ### Changed
8
+
9
+ * [HexaPDF::Layout::Frame::FitResult#draw] to allow drawing at an offset
10
+ * [HexaPDF::Layout::Box#fit] to delegate the actual content fitting to the
11
+ `#fit_content` method
12
+ * [HexaPDF::Document::Layout#box] to allow using the block as drawing block for
13
+ the base box class
14
+
15
+ ### Fixed
16
+
17
+ * [HexaPDF::Type::FontSimple#to_utf8] to work in case the font's encoding cannot
18
+ be retrieved
19
+
20
+
21
+ ## 0.35.1 - 2024-01-11
22
+
23
+ ### Added
24
+
25
+ * [HexaPDF::Utils] module functions for float comparisons and using them instead
26
+ of the geom2d ones
27
+
28
+ ### Changed
29
+
30
+ * Pre-defined paper sizes of the ISO A, B and C series to be more precise
31
+
32
+ ### Fixed
33
+
34
+ * [HexaPDF::Layout::Box#fit] to use float comparison
35
+ * [HexaPDF::Type::IconFit] to use correct superclass
36
+
37
+
1
38
  ## 0.35.0 - 2024-01-06
2
39
 
3
40
  ### Added
@@ -384,7 +384,8 @@ module HexaPDF
384
384
  # composer.lorem_ipsum(sentences: 1, margin: [0, 0, 5])
385
385
  # composer.list(item_spacing: 2) do |list|
386
386
  # composer.document.config['layout.boxes.map'].each do |name, klass|
387
- # list.formatted_text([{text: name.to_s, fill_color: "hp-blue-dark"}, "\n#{klass}"])
387
+ # list.formatted_text([{text: name.to_s, fill_color: "hp-blue-dark"}, "\n#{klass}"],
388
+ # font_size: 8)
388
389
  # end
389
390
  # end
390
391
  #
@@ -545,6 +545,7 @@ module HexaPDF
545
545
  column: 'HexaPDF::Layout::ColumnBox',
546
546
  list: 'HexaPDF::Layout::ListBox',
547
547
  table: 'HexaPDF::Layout::TableBox',
548
+ container: 'HexaPDF::Layout::ContainerBox',
548
549
  },
549
550
  'page.default_media_box' => :A4,
550
551
  'page.default_media_orientation' => :portrait,
@@ -118,7 +118,7 @@ module HexaPDF
118
118
  # composer.list(item_spacing: 2) do |list|
119
119
  # composer.document.config['layout.boxes.map'].each do |name, klass|
120
120
  # list.formatted_text([{text: name.to_s, fill_color: "hp-blue-dark"},
121
- # {text: "\n#{klass}", font_size: 7}])
121
+ # {text: "\n#{klass}"}, font_size: 7])
122
122
  # end
123
123
  # end
124
124
  # end
@@ -62,8 +62,7 @@ module HexaPDF
62
62
  # https://web.archive.org/web/20160310153722/https://www.w3.org/TR/SVG/implnote.html).
63
63
  class EndpointArc
64
64
 
65
- EPSILON = 1e-10 # :nodoc:
66
-
65
+ include Utils
67
66
  include Utils::MathHelpers
68
67
 
69
68
  # Creates and configures a new endpoint arc object.
@@ -283,7 +282,7 @@ module HexaPDF
283
282
 
284
283
  # F.6.5.2
285
284
  sqrt = (rxs * rys - rxs * y1ps - rys * x1ps) / (rxs * y1ps + rys * x1ps)
286
- sqrt = 0 if sqrt.abs < EPSILON
285
+ sqrt = 0 if sqrt.abs < Utils::EPSILON
287
286
  sqrt = Math.sqrt(sqrt)
288
287
  sqrt *= -1 unless @large_arc == @clockwise
289
288
  cxp = sqrt * rx * y1p / ry
@@ -303,11 +302,6 @@ module HexaPDF
303
302
  inclination: @inclination, clockwise: @clockwise, max_curves: @max_curves}
304
303
  end
305
304
 
306
- # Compares two float numbers if they are within a certain delta.
307
- def float_equal(a, b)
308
- (a - b).abs < EPSILON
309
- end
310
-
311
305
  # Computes the angle in degrees between the x-axis and the vector.
312
306
  def compute_angle_to_x_axis(vx, vy)
313
307
  (vy < 0 ? -1 : 1) * rad_to_deg(Math.acos(vx / Math.sqrt(vx**2 + vy**2)))
@@ -238,10 +238,12 @@ module HexaPDF
238
238
  #
239
239
  # The +name+ argument refers to the registered name of the box class that is looked up in the
240
240
  # 'layout.boxes.map' configuration option. The +box_options+ are passed as-is to the
241
- # initialization method of that box class
241
+ # initialization method of that box class.
242
242
  #
243
243
  # If a block is provided, a ChildrenCollector is yielded and the collected children are passed
244
- # to the box initialization method via the :children keyword argument.
244
+ # to the box initialization method via the :children keyword argument. There is one exception
245
+ # to this rule in case +name+ is +base+: The provided block is passed to the initialization
246
+ # method of the base box class to function as drawing method.
245
247
  #
246
248
  # See #text_box for details on +width+, +height+ and +style+ (note that there is no
247
249
  # +style_properties+ argument).
@@ -252,12 +254,19 @@ module HexaPDF
252
254
  # layout.box(:column) do |column| # column box with one child
253
255
  # column.lorem_ipsum
254
256
  # end
255
- def box(name, width: 0, height: 0, style: nil, **box_options, &block)
256
- if block_given? && !box_options.key?(:children)
257
- box_options[:children] = ChildrenCollector.collect(self, &block)
257
+ # layout.box(width: 100) do |canvas, box|
258
+ # canvas.line(0, 0, box.content_width, box.content_height).stroke
259
+ # end
260
+ def box(name = :base, width: 0, height: 0, style: nil, **box_options, &block)
261
+ if block_given?
262
+ if name == :base
263
+ box_block = block
264
+ elsif !box_options.key?(:children)
265
+ box_options[:children] = ChildrenCollector.collect(self, &block)
266
+ end
258
267
  end
259
268
  box_class_for_name(name).new(width: width, height: height,
260
- style: retrieve_style(style), **box_options)
269
+ style: retrieve_style(style), **box_options, &box_block)
261
270
  end
262
271
 
263
272
  # Creates an array of HexaPDF::Layout::TextFragment objects for the given +text+.
@@ -53,6 +53,7 @@ require 'hexapdf/image_loader'
53
53
  require 'hexapdf/font_loader'
54
54
  require 'hexapdf/layout'
55
55
  require 'hexapdf/digital_signature'
56
+ require 'hexapdf/utils'
56
57
 
57
58
  begin
58
59
  require 'hexapdf/cext'
@@ -60,31 +60,62 @@ module HexaPDF
60
60
  # instantiated from the common convenience method HexaPDF::Document::Layout#box. To use this
61
61
  # facility subclasses need to be registered with the configuration option 'layout.boxes.map'.
62
62
  #
63
- # The methods #fit, #supports_position_flow?, #split or #split_content, #empty?, and #draw or
64
- # #draw_content need to be customized according to the subclass's use case.
65
- #
66
- # #fit:: This method should return +true+ if fitting was successful. Additionally, the
67
- # @fit_successful instance variable needs to be set to the fit result as it is used in
68
- # #split.
63
+ # The methods #supports_position_flow?, #empty?, #fit or #fit_content, #split or #split_content,
64
+ # and #draw or #draw_content need to be customized according to the subclass's use case (also
65
+ # see the documentation of the methods besides the informatione below):
69
66
  #
70
67
  # #supports_position_flow?::
71
- # If the subclass supports the value :flow of the 'position' style property, this method
72
- # needs to be overridden to return +true+.
68
+ # If the subclass supports the value :flow of the 'position' style property, this method
69
+ # needs to be overridden to return +true+.
70
+ #
71
+ # #empty?::
72
+ # This method should return +true+ if the subclass won't draw anything when #draw is called.
73
+ #
74
+ # #fit::
75
+ # This method should return +true+ if fitting was successful. Additionally, the
76
+ # @fit_successful instance variable needs to be set to the fit result as it is used in
77
+ # #split.
78
+ #
79
+ # The default implementation provides code common to most use-cases and delegates the
80
+ # specifics to the #fit_content method which needs to return +true+ if fitting was
81
+ # successful.
82
+ #
83
+ # #split::
84
+ # This method splits the content so that the current region is used as good as possible. The
85
+ # default implementation should be fine for most use-cases, so only #split_content needs to
86
+ # be implemented. The method #create_split_box should be used for getting a basic cloned
87
+ # box.
88
+ #
89
+ # #draw::
90
+ # This method draws the content and the default implementation already handles things like
91
+ # drawing the border and background. So it should not be overridden. The box specific
92
+ # drawing commands should be implemented in the #draw_content method.
93
+ #
94
+ # This base class provides various private helper methods for use in the above methods:
95
+ #
96
+ # +reserved_width+, +reserved_height+::
97
+ # Returns the width respectively the height of the reserved space inside the box that is
98
+ # used for the border and padding.
99
+ #
100
+ # +reserved_width_left+, +reserved_width_right+, +reserved_height_top+,
101
+ # +reserved_height_bottom+::
102
+ # Returns the reserved space inside the box at the specified edge (left, right, top,
103
+ # bottom).
73
104
  #
74
- # #split:: This method splits the content so that the current region is used as good as
75
- # possible. The default implementation should be fine for most use-cases, so only
76
- # #split_content needs to be implemented. The method #create_split_box should be used
77
- # for getting a basic cloned box.
105
+ # +update_content_width+, +update_content_height+::
106
+ # Takes a block that should return the content width respectively height and sets the box's
107
+ # width respectively height accordingly.
78
108
  #
79
- # #empty?:: This method should return +true+ if the subclass won't draw anything when #draw is
80
- # called.
109
+ # +create_split_box+::
110
+ # Creates a new box based on this one and resets the internal data back to their original
111
+ # values.
81
112
  #
82
- # #draw:: This method draws the content and the default implementation already handles things
83
- # like drawing the border and background. Therefore it's best to implement #draw_content
84
- # which should just draw the content.
113
+ # The keyword argument +split_box_value+ (defaults to +true+) is used to set the
114
+ # +@split_box+ variable to make the new box aware that it is a split box. This can be set to
115
+ # any other truthy value to convey more meaning.
85
116
  class Box
86
117
 
87
- include Geom2D::Utils
118
+ include HexaPDF::Utils
88
119
 
89
120
  # Creates a new Box object, using the provided block as drawing block (see ::new).
90
121
  #
@@ -162,7 +193,8 @@ module HexaPDF
162
193
  @split_box = false
163
194
  end
164
195
 
165
- # Returns +true+ if this is a split box, i.e. the rest of another box after it was split.
196
+ # Returns the set truthy value if this is a split box, i.e. the rest of another box after it
197
+ # was split.
166
198
  def split_box?
167
199
  @split_box
168
200
  end
@@ -184,17 +216,34 @@ module HexaPDF
184
216
  height < 0 ? 0 : height
185
217
  end
186
218
 
187
- # Fits the box into the Frame and returns +true+ if fitting was successful.
219
+ # Fits the box into the *frame* and returns +true+ if fitting was successful.
188
220
  #
189
221
  # The arguments +available_width+ and +available_height+ are the width and height of the
190
- # current region of the frame. The frame itself is provided as third argument.
222
+ # current region of the frame, adjusted for this box. The frame itself is provided as third
223
+ # argument.
191
224
  #
192
- # The default implementation uses the available width and height for the box width and height
193
- # if they were initially set to 0. Otherwise the specified dimensions are used.
194
- def fit(available_width, available_height, _frame)
225
+ # The default implementation uses the given available width and height for the box width and
226
+ # height if they were initially set to 0. Otherwise the intially specified dimensions are
227
+ # used. Then the #fit_content method is called which allows sub-classes to fit their content.
228
+ #
229
+ # The following variables are set that may later be used during splitting or drawing:
230
+ #
231
+ # * (@fit_x, @fit_y): The lower-left corner of the content box where fitting was done. Can be
232
+ # used to adjust the drawing position in #draw/#draw_content if necessary.
233
+ # * @fit_successful: +true+ if fitting was successful.
234
+ def fit(available_width, available_height, frame)
195
235
  @width = (@initial_width > 0 ? @initial_width : available_width)
196
236
  @height = (@initial_height > 0 ? @initial_height : available_height)
197
- @fit_successful = (@width <= available_width && @height <= available_height)
237
+ @fit_successful = (float_compare(@width, available_width) <= 0 &&
238
+ float_compare(@height, available_height) <= 0)
239
+ return unless @fit_successful
240
+
241
+ @fit_successful = fit_content(available_width, available_height, frame)
242
+
243
+ @fit_x = frame.x + reserved_width_left
244
+ @fit_y = frame.y - @height + reserved_height_bottom
245
+
246
+ @fit_successful
198
247
  end
199
248
 
200
249
  # Tries to split the box into two, the first of which needs to fit into the current region of
@@ -236,6 +285,8 @@ module HexaPDF
236
285
  # arguments. Subclasses can specify an on-demand drawing method by setting the +@draw_block+
237
286
  # instance variable to +nil+ or a valid block. This is useful to avoid unnecessary set-up
238
287
  # operations when the block does nothing.
288
+ #
289
+ # Alternatively, if a #draw_content method is defined, this method is called.
239
290
  def draw(canvas, x, y)
240
291
  if (oc = properties['optional_content'])
241
292
  canvas.optional_content(oc)
@@ -315,12 +366,26 @@ module HexaPDF
315
366
  result
316
367
  end
317
368
 
318
- # Draws the content of the box at position [x, y] which is the bottom-left corner of the
319
- # content box.
320
- def draw_content(canvas, x, y)
321
- if @draw_block
322
- canvas.translate(x, y) { @draw_block.call(canvas, self) }
323
- end
369
+ # Updates the width of the box using the content width returned by the block.
370
+ def update_content_width
371
+ return if @initial_width > 0
372
+ @width = yield + reserved_width
373
+ end
374
+
375
+ # Updates the height of the box using the content height returned by the block.
376
+ def update_content_height
377
+ return if @initial_height > 0
378
+ @height = yield + reserved_height
379
+ end
380
+
381
+ # Fits the content of the box and returns whether fitting was successful.
382
+ #
383
+ # This is just a stub implementation that returns +true+. Subclasses should override it to
384
+ # provide the box specific behaviour.
385
+ #
386
+ # See #fit for details.
387
+ def fit_content(available_width, available_height, frame)
388
+ true
324
389
  end
325
390
 
326
391
  # Splits the content of the box.
@@ -334,6 +399,17 @@ module HexaPDF
334
399
  [nil, self]
335
400
  end
336
401
 
402
+ # Draws the content of the box at position [x, y] which is the bottom-left corner of the
403
+ # content box.
404
+ #
405
+ # This implementation uses the drawing block provided on initialization, if set, to draw the
406
+ # contents. Subclasses should override it to provide box specific behaviour.
407
+ def draw_content(canvas, x, y)
408
+ if @draw_block
409
+ canvas.translate(x, y) { @draw_block.call(canvas, self) }
410
+ end
411
+ end
412
+
337
413
  # Creates a new box based on this one and resets the data back to their original values.
338
414
  #
339
415
  # The variable +@split_box+ is set to +split_box_value+ (defaults to +true+) to make the new
@@ -0,0 +1,159 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2023 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+ require 'hexapdf/layout/box'
37
+ require 'hexapdf/layout/box_fitter'
38
+ require 'hexapdf/layout/frame'
39
+
40
+ module HexaPDF
41
+ module Layout
42
+
43
+ # This is a simple container box for laying out a number of boxes together. It is registered
44
+ # under the :container name.
45
+ #
46
+ # The box does not support the value :flow for the style property position, so the child boxes
47
+ # are laid out in the current region only. Since the boxes should be laid out together, if any
48
+ # box doesn't fit, the whole container doesn't fit. Splitting the container is also not possible
49
+ # for the same reason.
50
+ #
51
+ # By default the child boxes are laid out from top to bottom by default. By appropriately
52
+ # setting the style properties 'mask_mode', 'align' and 'valign', it is possible to lay out the
53
+ # children bottom to top, left to right, or right to left:
54
+ #
55
+ # * The standard top to bottom layout:
56
+ #
57
+ # #>pdf-composer100
58
+ # composer.container do |container|
59
+ # container.box(:base, height: 20, style: {background_color: "hp-blue-dark"})
60
+ # container.box(:base, height: 20, style: {background_color: "hp-blue"})
61
+ # container.box(:base, height: 20, style: {background_color: "hp-blue-light"})
62
+ # end
63
+ #
64
+ # * The bottom to top layout (using valign = :bottom to fill up from the bottom and mask_mode =
65
+ # :fill_horizontal to only remove the area to the left and right of the box):
66
+ #
67
+ # #>pdf-composer100
68
+ # composer.container do |container|
69
+ # container.box(:base, height: 20, style: {background_color: "hp-blue-dark",
70
+ # mask_mode: :fill_horizontal, valign: :bottom})
71
+ # container.box(:base, height: 20, style: {background_color: "hp-blue",
72
+ # mask_mode: :fill_horizontal, valign: :bottom})
73
+ # container.box(:base, height: 20, style: {background_color: "hp-blue-light",
74
+ # mask_mode: :fill_horizontal, valign: :bottom})
75
+ # end
76
+ #
77
+ # * The left to right layout (using mask_mode = :fill_vertical to fill the area to the top and
78
+ # bottom of the box):
79
+ #
80
+ # #>pdf-composer100
81
+ # composer.container do |container|
82
+ # container.box(:base, width: 20, style: {background_color: "hp-blue-dark",
83
+ # mask_mode: :fill_vertical})
84
+ # container.box(:base, width: 20, style: {background_color: "hp-blue",
85
+ # mask_mode: :fill_vertical})
86
+ # container.box(:base, width: 20, style: {background_color: "hp-blue-light",
87
+ # mask_mode: :fill_vertical})
88
+ # end
89
+ #
90
+ # * The right to left layout (using align = :right to fill up from the right and mask_mode =
91
+ # :fill_vertical to fill the area to the top and bottom of the box):
92
+ #
93
+ # #>pdf-composer100
94
+ # composer.container do |container|
95
+ # container.box(:base, width: 20, style: {background_color: "hp-blue-dark",
96
+ # mask_mode: :fill_vertical, align: :right})
97
+ # container.box(:base, width: 20, style: {background_color: "hp-blue",
98
+ # mask_mode: :fill_vertical, align: :right})
99
+ # container.box(:base, width: 20, style: {background_color: "hp-blue-light",
100
+ # mask_mode: :fill_vertical, align: :right})
101
+ # end
102
+ class ContainerBox < Box
103
+
104
+ # The child boxes of this ContainerBox. They need to be finalized before #fit is called.
105
+ attr_reader :children
106
+
107
+ # Creates a new container box, optionally accepting an array of child boxes.
108
+ #
109
+ # Example:
110
+ #
111
+ # #>pdf-composer100
112
+ # composer.text("A paragraph here")
113
+ # composer.container(height: 40, style: {border: {width: 1}, padding: 5,
114
+ # align: :center}) do |container|
115
+ # container.text("Some", mask_mode: :fill_vertical)
116
+ # container.text("text", mask_mode: :fill_vertical, valign: :center)
117
+ # container.text("here", mask_mode: :fill_vertical, valign: :bottom)
118
+ # end
119
+ # composer.text("Another paragraph")
120
+ def initialize(children: [], **kwargs)
121
+ super(**kwargs)
122
+ @children = children
123
+ end
124
+
125
+ # Returns +true+ if no box was fitted into the container.
126
+ def empty?
127
+ super && (!@box_fitter || @box_fitter.fit_results.empty?)
128
+ end
129
+
130
+ private
131
+
132
+ # Fits the children into the container.
133
+ def fit_content(available_width, available_height, frame)
134
+ my_frame = Frame.new(frame.x + reserved_width_left, frame.y - @height + reserved_height_bottom,
135
+ content_width, content_height, context: frame.context)
136
+ @box_fitter = BoxFitter.new([my_frame])
137
+ children.each {|box| @box_fitter.fit(box) }
138
+
139
+ if @box_fitter.fit_successful?
140
+ update_content_width do
141
+ result = @box_fitter.fit_results.max_by {|result| result.mask.x + result.mask.width }
142
+ children.size > 0 ? result.mask.x + result.mask.width - my_frame.left : 0
143
+ end
144
+ update_content_height { @box_fitter.content_heights.max }
145
+ true
146
+ end
147
+ end
148
+
149
+ # Draws the image onto the canvas at position [x, y].
150
+ def draw_content(canvas, x, y)
151
+ dx = x - @fit_x
152
+ dy = y - @fit_y
153
+ @box_fitter.fit_results.each {|result| result.draw(canvas, dx: dx, dy: dy) }
154
+ end
155
+
156
+ end
157
+
158
+ end
159
+ end
@@ -86,7 +86,7 @@ module HexaPDF
86
86
  # It is also possible to provide a different initial shape on initialization.
87
87
  class Frame
88
88
 
89
- include Geom2D::Utils
89
+ include HexaPDF::Utils
90
90
 
91
91
  # Stores the result of fitting a box in a Frame.
92
92
  class FitResult
@@ -128,17 +128,20 @@ module HexaPDF
128
128
  @success
129
129
  end
130
130
 
131
- # Draws the #box onto the canvas at (#x, #y).
131
+ # Draws the #box onto the canvas at (#x + *dx*, #y + *dy*).
132
+ #
133
+ # The relative offset (dx, dy) is useful when rendering results that were accumulated and
134
+ # then need to be moved because the container holding them changes its position.
132
135
  #
133
136
  # The configuration option "debug" can be used to add visual debug output with respect to
134
137
  # box placement.
135
- def draw(canvas)
138
+ def draw(canvas, dx: 0, dy: 0)
136
139
  doc = canvas.context.document
137
140
  if doc.config['debug']
138
141
  name = "#{box.class} (#{x.to_i},#{y.to_i}-#{box.width.to_i}x#{box.height.to_i})"
139
142
  ocg = doc.optional_content.ocg(name)
140
143
  canvas.optional_content(ocg) do
141
- canvas.save_graphics_state do
144
+ canvas.translate(dx, dy) do
142
145
  canvas.fill_color("green").stroke_color("darkgreen").
143
146
  opacity(fill_alpha: 0.1, stroke_alpha: 0.2).
144
147
  draw(:geom2d, object: mask, path_only: true).fill_stroke
@@ -146,7 +149,7 @@ module HexaPDF
146
149
  end
147
150
  doc.optional_content.default_configuration.add_ocg_to_ui(ocg, path: 'Debug')
148
151
  end
149
- box.draw(canvas, x, y)
152
+ box.draw(canvas, x + dx, y + dy)
150
153
  end
151
154
 
152
155
  end
@@ -232,7 +232,8 @@ module HexaPDF
232
232
 
233
233
  if index != 0 || !split_box? || @split_box == :show_first_marker
234
234
  box = item_marker_box(frame.document, index)
235
- break unless box.fit(content_indentation, height, nil)
235
+ marker_frame = Frame.new(0, 0, content_indentation, height, context: frame.context)
236
+ break unless box.fit(content_indentation, height, marker_frame)
236
237
  item_result.marker = box
237
238
  item_result.marker_pos_x = item_frame.x - content_indentation
238
239
  item_result.height = box.height
@@ -42,7 +42,7 @@ module HexaPDF
42
42
  # Utility class for generating width specifications for TextLayouter#fit from polygons.
43
43
  class WidthFromPolygon
44
44
 
45
- include Geom2D::Utils
45
+ include HexaPDF::Utils
46
46
 
47
47
  # Creates a new object for the given polygon (or polygon set) and immediately prepares it so
48
48
  # that #call can be used.
@@ -58,6 +58,7 @@ module HexaPDF
58
58
  autoload(:ListBox, 'hexapdf/layout/list_box')
59
59
  autoload(:PageStyle, 'hexapdf/layout/page_style')
60
60
  autoload(:TableBox, 'hexapdf/layout/table_box')
61
+ autoload(:ContainerBox, 'hexapdf/layout/container_box')
61
62
 
62
63
  end
63
64
 
@@ -95,7 +95,8 @@ module HexaPDF
95
95
  # Returns the UTF-8 string for the given character code, or calls the configuration option
96
96
  # 'font.on_missing_unicode_mapping' if no mapping was found.
97
97
  def to_utf8(code)
98
- to_unicode_cmap&.to_unicode(code) || encoding.unicode(code) || missing_unicode_mapping(code)
98
+ to_unicode_cmap&.to_unicode(code) || (encoding.unicode(code) rescue nil) ||
99
+ missing_unicode_mapping(code)
99
100
  end
100
101
 
101
102
  # Returns the unscaled width of the given code point in glyph units, or 0 if the width for
@@ -34,7 +34,7 @@
34
34
  # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
35
  #++
36
36
 
37
- require 'hexapdf/type/annotation'
37
+ require 'hexapdf/dictionary'
38
38
 
39
39
  module HexaPDF
40
40
  module Type
@@ -43,7 +43,7 @@ module HexaPDF
43
43
  # rectangle.
44
44
  #
45
45
  # See: PDF2.0 s12.7.8.3.2
46
- class IconFit < Annotation
46
+ class IconFit < Dictionary
47
47
 
48
48
  define_type :XXIconFit
49
49