hexapdf 0.35.0 → 0.36.0

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: 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