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