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 +4 -4
- data/CHANGELOG.md +37 -0
- data/lib/hexapdf/composer.rb +2 -1
- data/lib/hexapdf/configuration.rb +1 -0
- data/lib/hexapdf/content/canvas_composer.rb +1 -1
- data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +2 -8
- data/lib/hexapdf/document/layout.rb +15 -6
- data/lib/hexapdf/document.rb +1 -0
- data/lib/hexapdf/layout/box.rb +107 -31
- data/lib/hexapdf/layout/container_box.rb +159 -0
- data/lib/hexapdf/layout/frame.rb +8 -5
- data/lib/hexapdf/layout/list_box.rb +2 -1
- data/lib/hexapdf/layout/width_from_polygon.rb +1 -1
- data/lib/hexapdf/layout.rb +1 -0
- data/lib/hexapdf/type/font_simple.rb +2 -1
- data/lib/hexapdf/type/icon_fit.rb +2 -2
- data/lib/hexapdf/type/page.rb +35 -35
- data/lib/hexapdf/utils.rb +66 -0
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/test_canvas_composer.rb +5 -5
- data/test/hexapdf/document/test_layout.rb +6 -0
- data/test/hexapdf/document/test_pages.rb +5 -5
- data/test/hexapdf/layout/test_box.rb +46 -13
- data/test/hexapdf/layout/test_column_box.rb +4 -4
- data/test/hexapdf/layout/test_container_box.rb +84 -0
- data/test/hexapdf/layout/test_frame.rb +3 -2
- data/test/hexapdf/layout/test_page_style.rb +5 -5
- data/test/hexapdf/test_composer.rb +13 -13
- data/test/hexapdf/test_utils.rb +20 -0
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/test_font_simple.rb +9 -1
- data/test/hexapdf/type/test_page.rb +3 -3
- data/test/hexapdf/type/test_page_tree_node.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b7cec7494ffe5e4f8031e5a05f2a31da13741879ff138513f737048880702389
|
4
|
+
data.tar.gz: 238a71920dcd9bde03497a22f0583bf72f11d358176f28217214f86ca835764d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/hexapdf/composer.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
256
|
-
|
257
|
-
|
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+.
|
data/lib/hexapdf/document.rb
CHANGED
data/lib/hexapdf/layout/box.rb
CHANGED
@@ -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 #
|
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
|
-
#
|
72
|
-
#
|
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
|
-
#
|
75
|
-
#
|
76
|
-
#
|
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
|
-
#
|
80
|
-
#
|
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
|
-
#
|
83
|
-
#
|
84
|
-
#
|
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
|
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
|
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
|
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
|
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
|
193
|
-
# if they were initially set to 0. Otherwise the specified dimensions are
|
194
|
-
|
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 <=
|
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
|
-
#
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
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
|
data/lib/hexapdf/layout/frame.rb
CHANGED
@@ -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
|
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
|
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.
|
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
|
-
|
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
|
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.
|
data/lib/hexapdf/layout.rb
CHANGED
@@ -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)
|
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/
|
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 <
|
46
|
+
class IconFit < Dictionary
|
47
47
|
|
48
48
|
define_type :XXIconFit
|
49
49
|
|