hexapdf 0.44.0 → 0.45.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 +19 -0
- data/examples/027-composer_optional_content.rb +6 -4
- data/examples/030-pdfa.rb +12 -11
- data/lib/hexapdf/composer.rb +22 -0
- data/lib/hexapdf/content/canvas.rb +3 -3
- data/lib/hexapdf/content/canvas_composer.rb +1 -0
- data/lib/hexapdf/document/layout.rb +15 -3
- data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
- data/lib/hexapdf/layout/box.rb +24 -9
- data/lib/hexapdf/layout/column_box.rb +3 -11
- data/lib/hexapdf/layout/container_box.rb +4 -4
- data/lib/hexapdf/layout/frame.rb +6 -5
- data/lib/hexapdf/layout/list_box.rb +3 -13
- data/lib/hexapdf/layout/style.rb +3 -3
- data/lib/hexapdf/layout/table_box.rb +14 -10
- data/lib/hexapdf/layout/text_box.rb +59 -31
- data/lib/hexapdf/layout/text_fragment.rb +1 -1
- data/lib/hexapdf/layout/text_layouter.rb +7 -8
- data/lib/hexapdf/rectangle.rb +4 -4
- data/lib/hexapdf/type/form.rb +2 -2
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/test_canvas_composer.rb +13 -8
- data/test/hexapdf/document/test_layout.rb +16 -0
- data/test/hexapdf/layout/test_box.rb +11 -0
- data/test/hexapdf/layout/test_frame.rb +9 -0
- data/test/hexapdf/layout/test_list_box.rb +3 -3
- data/test/hexapdf/layout/test_table_box.rb +8 -1
- data/test/hexapdf/layout/test_text_box.rb +49 -21
- data/test/hexapdf/test_composer.rb +14 -5
- data/test/hexapdf/test_serializer.rb +1 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd31e769d2a906198da2a4194085cb2a884fa3879442b75add168d7f81d67d8b
|
4
|
+
data.tar.gz: 43c2b4ba4df2f997d470835995f2581aa306c639c63ddc892e648d94605f3b22
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f17a303dba8104974564a213c72c0f0862c520964a3255b575544489ccb5a17a468d093d3970817903c4eb798e888592410db18447d3264d535f3a01a4a94c82
|
7
|
+
data.tar.gz: d5727e2f2bfb5ed827ac92eecc8cd9e0ba73044150ba07d03623dccde43dc29db1cddc2fbce24bfd63268522e7965519e188271c2bc9c00162b1a660fc468b74
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
## 0.45.0 - 2024-06-18
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* [HexaPDF::Document::Layout#styles] and [HexaPDF::Composer#styles] for defining
|
6
|
+
multiple styles at once
|
7
|
+
|
8
|
+
### Changed
|
9
|
+
|
10
|
+
* [HexaPDF::Layout::Box#fit] to set width/height correctly for boxes with
|
11
|
+
position `:flow`
|
12
|
+
|
13
|
+
### Fixed
|
14
|
+
|
15
|
+
* Regression in [HexaPDF::Layout::ListBox] that leads to missing markers
|
16
|
+
* [HexaPDF::Content::CanvasComposer#draw_box] to handle truncated boxes
|
17
|
+
* [HexaPDF::Layout::TableBox::Cell] to handle too-big content in all cases
|
18
|
+
|
19
|
+
|
1
20
|
## 0.44.0 - 2024-06-05
|
2
21
|
|
3
22
|
### Added
|
@@ -16,8 +16,10 @@
|
|
16
16
|
require 'hexapdf'
|
17
17
|
|
18
18
|
HexaPDF::Composer.create('composer_optional_content.pdf') do |composer|
|
19
|
-
composer.
|
20
|
-
|
19
|
+
composer.styles(
|
20
|
+
question: {font_size: 16, margin: [0, 0, 16], fill_color: 'hp-blue'},
|
21
|
+
answer: {font: 'ZapfDingbats', fill_color: "green"},
|
22
|
+
)
|
21
23
|
|
22
24
|
all = composer.document.optional_content.ocg('All answers')
|
23
25
|
a1 = composer.document.optional_content.ocg('Answer 1')
|
@@ -38,7 +40,7 @@ HexaPDF::Composer.create('composer_optional_content.pdf') do |composer|
|
|
38
40
|
answers.text('Guido van Rossum')
|
39
41
|
answers.multiple do |answer|
|
40
42
|
answer.text('Yukihiro “Matz” Matsumoto', position: :float)
|
41
|
-
answer.text("\u{a0}\u{a0}
|
43
|
+
answer.text("\u{a0}\u{a0}✔", style: :answer,
|
42
44
|
properties: {'optional_content' => a1})
|
43
45
|
end
|
44
46
|
answers.text('Rob Pike')
|
@@ -54,7 +56,7 @@ HexaPDF::Composer.create('composer_optional_content.pdf') do |composer|
|
|
54
56
|
answers.text('1992')
|
55
57
|
answers.multiple do |answer|
|
56
58
|
answer.text('1993', position: :float)
|
57
|
-
answer.text("\u{a0}\u{a0}
|
59
|
+
answer.text("\u{a0}\u{a0}✔", style: :answer,
|
58
60
|
properties: {'optional_content' => a2})
|
59
61
|
end
|
60
62
|
end
|
data/examples/030-pdfa.rb
CHANGED
@@ -28,17 +28,18 @@ HexaPDF::Composer.create('pdfa.pdf') do |composer|
|
|
28
28
|
}
|
29
29
|
|
30
30
|
# Define all styles
|
31
|
-
composer.
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
31
|
+
composer.styles(
|
32
|
+
base: {font: 'Lato', font_size: 10, line_spacing: 1.3},
|
33
|
+
top: {font_size: 8},
|
34
|
+
top_box: {padding: [100, 0, 0], margin: [0, 0, 10], border: {width: [0, 0, 1]}},
|
35
|
+
header: {font: 'Lato bold', font_size: 20, margin: [50, 0, 20]},
|
36
|
+
line_items: {border: {width: 1, color: "eee"}, margin: [20, 0]},
|
37
|
+
line_item_cell: {font_size: 8},
|
38
|
+
footer: {border: {width: [1, 0, 0], color: "darkgrey"}, padding: [5, 0, 0],
|
39
|
+
valign: :bottom},
|
40
|
+
footer_heading: {font: 'Lato bold', font_size: 8, padding: [0, 0, 8]},
|
41
|
+
footer_text: {font_size: 8, fill_color: "darkgrey"},
|
42
|
+
)
|
42
43
|
|
43
44
|
# Top part
|
44
45
|
composer.box(:container, style: :top_box) do |container|
|
data/lib/hexapdf/composer.rb
CHANGED
@@ -254,6 +254,28 @@ module HexaPDF
|
|
254
254
|
@document.layout.style(name, base: base, **properties)
|
255
255
|
end
|
256
256
|
|
257
|
+
# :call-seq:
|
258
|
+
# composer.styles -> styles
|
259
|
+
# composer.styles(**mapping) -> styles
|
260
|
+
#
|
261
|
+
# Creates multiple named styles at once if +mapping+ is provided, and returns the style mapping.
|
262
|
+
#
|
263
|
+
# See HexaPDF::Document::Layout#styles for details; this method is just a thin wrapper around
|
264
|
+
# that method.
|
265
|
+
#
|
266
|
+
# Example:
|
267
|
+
#
|
268
|
+
# composer.styles(
|
269
|
+
# base: {font_size: 12, leading: 1.2},
|
270
|
+
# header: {font: 'Helvetica', fill_color: "008"},
|
271
|
+
# header1: {base: :header, font_size: 30}
|
272
|
+
# )
|
273
|
+
#
|
274
|
+
# See: HexaPDF::Layout::Style
|
275
|
+
def styles(**mapping)
|
276
|
+
@document.layout.styles(**mapping)
|
277
|
+
end
|
278
|
+
|
257
279
|
# :call-seq:
|
258
280
|
# composer.page_style(name) -> page_style
|
259
281
|
# composer.page_style(name, **attributes, &template_block) -> page_style
|
@@ -1127,7 +1127,7 @@ module HexaPDF
|
|
1127
1127
|
# canvas.rectangle(x, y, width, height, radius: 0) => canvas
|
1128
1128
|
#
|
1129
1129
|
# Appends a rectangle to the current path as a complete subpath (drawn in counterclockwise
|
1130
|
-
# direction), with the bottom
|
1130
|
+
# direction), with the bottom-left corner specified by +x+ and +y+ and the given +width+ and
|
1131
1131
|
# +height+. Returns +self+.
|
1132
1132
|
#
|
1133
1133
|
# If +radius+ is greater than 0, the corners are rounded with the given radius.
|
@@ -1137,7 +1137,7 @@ module HexaPDF
|
|
1137
1137
|
#
|
1138
1138
|
# If there is no current path when the method is invoked, a new path is automatically begun.
|
1139
1139
|
#
|
1140
|
-
# The current point is set to the bottom
|
1140
|
+
# The current point is set to the bottom-left corner if +radius+ is zero, otherwise it is set
|
1141
1141
|
# to (x, y + radius).
|
1142
1142
|
#
|
1143
1143
|
# Examples:
|
@@ -1720,7 +1720,7 @@ module HexaPDF
|
|
1720
1720
|
# If the filename or the IO specifies a PDF file, the first page of this file is used to
|
1721
1721
|
# create a form XObject which is then drawn.
|
1722
1722
|
#
|
1723
|
-
# The +at+ argument has to be an array containing two numbers specifying the bottom
|
1723
|
+
# The +at+ argument has to be an array containing two numbers specifying the bottom-left
|
1724
1724
|
# corner at which to draw the XObject.
|
1725
1725
|
#
|
1726
1726
|
# If +width+ and +height+ are specified, the drawn XObject will have exactly these
|
@@ -96,6 +96,7 @@ module HexaPDF
|
|
96
96
|
draw_box, box = @frame.split(result)
|
97
97
|
if draw_box
|
98
98
|
@frame.draw(@canvas, result)
|
99
|
+
(box = draw_box; break) unless box
|
99
100
|
elsif !@frame.find_next_region
|
100
101
|
raise HexaPDF::Error, "Frame for canvas composer is full and box doesn't fit anymore"
|
101
102
|
end
|
@@ -175,9 +175,6 @@ module HexaPDF
|
|
175
175
|
|
176
176
|
end
|
177
177
|
|
178
|
-
# The mapping of style name (a Symbol) to Layout::Style instance.
|
179
|
-
attr_reader :styles
|
180
|
-
|
181
178
|
# Creates a new Layout object for the given PDF document.
|
182
179
|
def initialize(document)
|
183
180
|
@document = document
|
@@ -219,6 +216,21 @@ module HexaPDF
|
|
219
216
|
style
|
220
217
|
end
|
221
218
|
|
219
|
+
# :call-seq:
|
220
|
+
# layout.styles -> styles
|
221
|
+
# layout.styles(**mapping) -> styles
|
222
|
+
#
|
223
|
+
# Returns the mapping of style names to Layout::Style instances. If +mapping+ is provided,
|
224
|
+
# also defines the given styles using #style.
|
225
|
+
#
|
226
|
+
# The argument +mapping+ needs to be a hash mapping a style name (a Symbol) to style
|
227
|
+
# properties. The special key +:base+ can be used to define the base style. For details see
|
228
|
+
# #style.
|
229
|
+
def styles(**mapping)
|
230
|
+
mapping.each {|name, properties| style(name, **properties) } unless mapping.empty?
|
231
|
+
@styles
|
232
|
+
end
|
233
|
+
|
222
234
|
# Creates an inline box for use together with text fragments.
|
223
235
|
#
|
224
236
|
# The +valign+ argument ist used to specify the vertical alignment of the box within the text
|
@@ -51,7 +51,7 @@ module HexaPDF
|
|
51
51
|
attr_accessor :name
|
52
52
|
|
53
53
|
# Character bounding box as array of four numbers, specifying the x- and y-coordinates of
|
54
|
-
# the bottom
|
54
|
+
# the bottom-left corner and the x- and y-coordinates of the top-right corner.
|
55
55
|
attr_accessor :bbox
|
56
56
|
|
57
57
|
end
|
@@ -63,7 +63,7 @@ module HexaPDF
|
|
63
63
|
attr_accessor :weight
|
64
64
|
|
65
65
|
# The font bounding box as array of four numbers, specifying the x- and y-coordinates of the
|
66
|
-
# bottom
|
66
|
+
# bottom-left corner and the x- and y-coordinates of the top-right corner.
|
67
67
|
attr_accessor :bounding_box
|
68
68
|
|
69
69
|
# The y-value of the top of the capital H (or 0 or nil if the font doesn't contain a capital
|
data/lib/hexapdf/layout/box.rb
CHANGED
@@ -323,10 +323,12 @@ module HexaPDF
|
|
323
323
|
# current region of the frame, adjusted for this box. The frame itself is provided as third
|
324
324
|
# argument.
|
325
325
|
#
|
326
|
-
#
|
327
|
-
#
|
328
|
-
#
|
329
|
-
#
|
326
|
+
# If the box uses flow positioning, the width is set to the frame's width and the height to
|
327
|
+
# the remaining height in the frame. Otherwise the given available width and height are used
|
328
|
+
# for the width and height if they were initially set to 0. Otherwise the intially specified
|
329
|
+
# dimensions are used. The method returns early if the thus configured box already doesn't
|
330
|
+
# fit. Otherwise, the #fit_content method is called which allows sub-classes to fit their
|
331
|
+
# content.
|
330
332
|
#
|
331
333
|
# The following variables are set that may later be used during splitting or drawing:
|
332
334
|
#
|
@@ -334,10 +336,23 @@ module HexaPDF
|
|
334
336
|
# used to adjust the drawing position in #draw_content if necessary.
|
335
337
|
def fit(available_width, available_height, frame)
|
336
338
|
@fit_result.reset(frame)
|
337
|
-
|
338
|
-
@
|
339
|
-
|
340
|
-
|
339
|
+
position_flow = supports_position_flow? && style.position == :flow
|
340
|
+
@width = if @initial_width > 0
|
341
|
+
@initial_width
|
342
|
+
elsif position_flow
|
343
|
+
frame.width
|
344
|
+
else
|
345
|
+
available_width
|
346
|
+
end
|
347
|
+
@height = if @initial_height > 0
|
348
|
+
@initial_height
|
349
|
+
elsif position_flow
|
350
|
+
frame.y - frame.bottom
|
351
|
+
else
|
352
|
+
available_height
|
353
|
+
end
|
354
|
+
return @fit_result if !position_flow && (float_compare(@width, available_width) > 0 ||
|
355
|
+
float_compare(@height, available_height) > 0)
|
341
356
|
|
342
357
|
fit_content(available_width, available_height, frame)
|
343
358
|
|
@@ -379,7 +394,7 @@ module HexaPDF
|
|
379
394
|
# system is translated so that the origin is at the bottom left corner of the **content box**.
|
380
395
|
#
|
381
396
|
# Subclasses should not rely on the +@draw_block+ but implement the #draw_content method. The
|
382
|
-
# coordinates passed to it are also modified to represent the bottom
|
397
|
+
# coordinates passed to it are also modified to represent the bottom-left corner of the
|
383
398
|
# content box but the coordinate system is not translated.
|
384
399
|
def draw(canvas, x, y)
|
385
400
|
if @fit_result.overflow? && @initial_height > 0 && style.overflow == :error
|
@@ -142,19 +142,11 @@ module HexaPDF
|
|
142
142
|
|
143
143
|
# Fits the column box into the current region of the frame.
|
144
144
|
#
|
145
|
-
def fit_content(
|
145
|
+
def fit_content(_available_width, _available_height, frame)
|
146
146
|
initial_fit_successful = (@equal_height && @columns.size > 1 ? nil : false)
|
147
147
|
tries = 0
|
148
|
-
width =
|
149
|
-
|
150
|
-
else
|
151
|
-
(@initial_width > 0 ? @initial_width : available_width) - reserved_width
|
152
|
-
end
|
153
|
-
height = if style.position == :flow
|
154
|
-
(@initial_height > 0 ? @initial_height : frame.y - frame.bottom) - reserved_height
|
155
|
-
else
|
156
|
-
(@initial_height > 0 ? @initial_height : available_height) - reserved_height
|
157
|
-
end
|
148
|
+
width = @width - reserved_width
|
149
|
+
height = @height - reserved_height
|
158
150
|
|
159
151
|
columns = calculate_columns(width)
|
160
152
|
return if columns.empty?
|
@@ -52,7 +52,7 @@ module HexaPDF
|
|
52
52
|
# setting the style properties 'mask_mode', 'align' and 'valign', it is possible to lay out the
|
53
53
|
# children bottom to top, left to right, or right to left:
|
54
54
|
#
|
55
|
-
# * The standard top
|
55
|
+
# * The standard top-to-bottom layout:
|
56
56
|
#
|
57
57
|
# #>pdf-composer100
|
58
58
|
# composer.container do |container|
|
@@ -61,7 +61,7 @@ module HexaPDF
|
|
61
61
|
# container.box(:base, height: 20, style: {background_color: "hp-blue-light"})
|
62
62
|
# end
|
63
63
|
#
|
64
|
-
# * The bottom
|
64
|
+
# * The bottom-to-top layout (using valign = :bottom to fill up from the bottom and mask_mode =
|
65
65
|
# :fill_horizontal to only remove the area to the left and right of the box):
|
66
66
|
#
|
67
67
|
# #>pdf-composer100
|
@@ -74,7 +74,7 @@ module HexaPDF
|
|
74
74
|
# mask_mode: :fill_horizontal, valign: :bottom})
|
75
75
|
# end
|
76
76
|
#
|
77
|
-
# * The left
|
77
|
+
# * The left-to-right layout (using mask_mode = :fill_vertical to fill the area to the top and
|
78
78
|
# bottom of the box):
|
79
79
|
#
|
80
80
|
# #>pdf-composer100
|
@@ -87,7 +87,7 @@ module HexaPDF
|
|
87
87
|
# mask_mode: :fill_vertical})
|
88
88
|
# end
|
89
89
|
#
|
90
|
-
# * The right
|
90
|
+
# * The right-to-left layout (using align = :right to fill up from the right and mask_mode =
|
91
91
|
# :fill_vertical to fill the area to the top and bottom of the box):
|
92
92
|
#
|
93
93
|
# #>pdf-composer100
|
data/lib/hexapdf/layout/frame.rb
CHANGED
@@ -54,7 +54,7 @@ module HexaPDF
|
|
54
54
|
#
|
55
55
|
# The method #fit is also called for absolutely positioned boxes but since these boxes are not
|
56
56
|
# subject to the normal constraints, the provided available width and height are the width and
|
57
|
-
# height inside the frame to the right and top of the bottom
|
57
|
+
# height inside the frame to the right and top of the bottom-left corner of the box.
|
58
58
|
#
|
59
59
|
# * If the box didn't fit, call #find_next_region to determine the next region for placing the
|
60
60
|
# box. If a new region was found, start over with #fit. Otherwise the frame has no more space
|
@@ -84,10 +84,10 @@ module HexaPDF
|
|
84
84
|
|
85
85
|
include HexaPDF::Utils
|
86
86
|
|
87
|
-
# The x-coordinate of the bottom
|
87
|
+
# The x-coordinate of the bottom-left corner.
|
88
88
|
attr_reader :left
|
89
89
|
|
90
|
-
# The y-coordinate of the bottom
|
90
|
+
# The y-coordinate of the bottom-left corner.
|
91
91
|
attr_reader :bottom
|
92
92
|
|
93
93
|
# The width of the frame.
|
@@ -127,7 +127,7 @@ module HexaPDF
|
|
127
127
|
|
128
128
|
# An array of box objects representing the parent boxes.
|
129
129
|
#
|
130
|
-
# The immediate parent is the last array entry, the top
|
130
|
+
# The immediate parent is the last array entry, the top-most parent the first one. All boxes
|
131
131
|
# that are fitted into this frame have to be child boxes of the immediate parent box.
|
132
132
|
attr_reader :parent_boxes
|
133
133
|
|
@@ -216,6 +216,7 @@ module HexaPDF
|
|
216
216
|
end
|
217
217
|
|
218
218
|
fit_result = box.fit(aw, ah, self)
|
219
|
+
return fit_result if fit_result.failure?
|
219
220
|
|
220
221
|
width = box.width
|
221
222
|
height = box.height
|
@@ -382,7 +383,7 @@ module HexaPDF
|
|
382
383
|
# Since not all text may start at the top of the frame, the offset argument can be used to
|
383
384
|
# specify a vertical offset from the top of the frame where layouting should start.
|
384
385
|
#
|
385
|
-
# To be compatible with TextLayouter, the top
|
386
|
+
# To be compatible with TextLayouter, the top-left corner of the bounding box of the frame's
|
386
387
|
# shape is the origin of the coordinate system for the width specification, with positive
|
387
388
|
# x-values to the right and positive y-values downwards.
|
388
389
|
#
|
@@ -189,19 +189,9 @@ module HexaPDF
|
|
189
189
|
private
|
190
190
|
|
191
191
|
# Fits the list box into the current region of the frame.
|
192
|
-
def fit_content(
|
193
|
-
@width = if @initial_width > 0
|
194
|
-
@initial_width
|
195
|
-
else
|
196
|
-
(style.position == :flow ? frame.width : available_width)
|
197
|
-
end
|
198
|
-
height = if @initial_height > 0
|
199
|
-
@initial_height - reserved_height
|
200
|
-
else
|
201
|
-
(style.position == :flow ? frame.y - frame.bottom : available_height) - reserved_height
|
202
|
-
end
|
203
|
-
|
192
|
+
def fit_content(_available_width, _available_height, frame)
|
204
193
|
width = @width - reserved_width
|
194
|
+
height = @height - reserved_height
|
205
195
|
left = (style.position == :flow ? frame.left : frame.x) + reserved_width_left
|
206
196
|
top = frame.y - reserved_height_top
|
207
197
|
|
@@ -245,7 +235,7 @@ module HexaPDF
|
|
245
235
|
Array(child).each {|ibox| box_fitter.fit(ibox) }
|
246
236
|
item_result.box_fitter = box_fitter
|
247
237
|
item_result.height = [item_result.height.to_i, box_fitter.content_heights[0]].max
|
248
|
-
@results << item_result
|
238
|
+
@results << item_result unless box_fitter.fit_results.empty?
|
249
239
|
|
250
240
|
top -= item_result.height + item_spacing
|
251
241
|
height -= item_result.height + item_spacing
|
data/lib/hexapdf/layout/style.rb
CHANGED
@@ -393,7 +393,7 @@ module HexaPDF
|
|
393
393
|
# The object resolved in this way needs to respond to #call(canvas, box) where +canvas+ is the
|
394
394
|
# HexaPDF::Content::Canvas object on which it should be drawn and +box+ is a box-like object
|
395
395
|
# (e.g. Box or TextFragment). The coordinate system is translated so that the origin is at the
|
396
|
-
# bottom
|
396
|
+
# bottom-left corner of the box during the drawing operations.
|
397
397
|
class Layers
|
398
398
|
|
399
399
|
# Creates a new Layers object popuplated with the given +layers+.
|
@@ -1268,8 +1268,8 @@ module HexaPDF
|
|
1268
1268
|
# composer.lorem_ipsum(position: :flow)
|
1269
1269
|
#
|
1270
1270
|
# [x, y]::
|
1271
|
-
# Position the box with the bottom
|
1272
|
-
# the bottom
|
1271
|
+
# Position the box with the bottom-left corner at the given absolute position relative to
|
1272
|
+
# the bottom-left corner of the frame.
|
1273
1273
|
#
|
1274
1274
|
# Examples:
|
1275
1275
|
#
|
@@ -228,18 +228,22 @@ module HexaPDF
|
|
228
228
|
case children
|
229
229
|
when Box
|
230
230
|
child_result = frame.fit(children)
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
231
|
+
if child_result.success?
|
232
|
+
@preferred_width = child_result.x + child_result.box.width + reserved_width
|
233
|
+
@height = @preferred_height = child_result.box.height + reserved_height
|
234
|
+
@fit_results = [child_result]
|
235
|
+
fit_result.success!
|
236
|
+
end
|
235
237
|
when Array
|
236
238
|
box_fitter = BoxFitter.new([frame])
|
237
239
|
children.each {|box| box_fitter.fit(box) }
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
240
|
+
if box_fitter.success?
|
241
|
+
max_x_result = box_fitter.fit_results.max_by {|result| result.x + result.box.width }
|
242
|
+
@preferred_width = max_x_result.x + max_x_result.box.width + reserved_width
|
243
|
+
@height = @preferred_height = box_fitter.content_heights[0] + reserved_height
|
244
|
+
@fit_results = box_fitter.fit_results
|
245
|
+
fit_result.success!
|
246
|
+
end
|
243
247
|
else
|
244
248
|
@preferred_width = reserved_width
|
245
249
|
@height = @preferred_height = reserved_height
|
@@ -590,7 +594,7 @@ module HexaPDF
|
|
590
594
|
def fit_content(_available_width, _available_height, frame)
|
591
595
|
# Adjust reserved width/height to include space used by the edge cells for their border
|
592
596
|
# since cell borders are drawn on the bounds and not inside.
|
593
|
-
# This uses the top
|
597
|
+
# This uses the top-left and bottom-right cells and so might not be correct in all cases.
|
594
598
|
@cell_tl_border_width = @cells[0, 0].style.border.width
|
595
599
|
cell_br_border_width = @cells[-1, -1].style.border.width
|
596
600
|
rw = (@cell_tl_border_width.left + cell_br_border_width.right) / 2.0
|
@@ -42,12 +42,46 @@ module HexaPDF
|
|
42
42
|
# A TextBox is used for drawing text, either inside a rectangular box or by flowing it around
|
43
43
|
# objects of a Frame.
|
44
44
|
#
|
45
|
+
# The standard usage is through the helper methods Document::Layout#text and
|
46
|
+
# Document::Layout#formatted_text.
|
47
|
+
#
|
45
48
|
# This class uses TextLayouter behind the scenes to do the hard work.
|
46
49
|
#
|
47
50
|
# == Used Box Properties
|
48
51
|
#
|
49
52
|
# The spacing after the last line can be controlled via the style property +last_line_gap+. Also
|
50
53
|
# see TextLayouter#style for other style properties taken into account.
|
54
|
+
#
|
55
|
+
# == Limitations
|
56
|
+
#
|
57
|
+
# When setting the style property 'position' to +:flow+, padding and border to the left and
|
58
|
+
# right as well as a predefined fixed width are not respected and the result will look wrong.
|
59
|
+
#
|
60
|
+
# == Examples
|
61
|
+
#
|
62
|
+
# Showing some text:
|
63
|
+
#
|
64
|
+
# #>pdf-composer
|
65
|
+
# composer.box(:text, items: layout.text_fragments("This is some text."))
|
66
|
+
# # Or easier with the provided convenience method
|
67
|
+
# composer.text("This is also some text")
|
68
|
+
#
|
69
|
+
# It is possible to flow the text around other objects by using the style property
|
70
|
+
# 'position' with the value +:flow+:
|
71
|
+
#
|
72
|
+
# #>pdf-composer
|
73
|
+
# composer.box(:base, width: 30, height: 30,
|
74
|
+
# style: {margin: 5, position: :float, background_color: "hp-blue-light"})
|
75
|
+
# composer.text("This is some text. " * 20, position: :flow)
|
76
|
+
#
|
77
|
+
# While top and bottom padding and border can be used with flow positioning, left and right
|
78
|
+
# padding and border are not supported and the result will look wrong:
|
79
|
+
#
|
80
|
+
# #>pdf-composer
|
81
|
+
# composer.box(:base, width: 30, height: 30,
|
82
|
+
# style: {margin: 5, position: :float, background_color: "hp-blue-light"})
|
83
|
+
# composer.text("This is some text. " * 20, padding: 10, position: :flow,
|
84
|
+
# text_align: :justify)
|
51
85
|
class TextBox < Box
|
52
86
|
|
53
87
|
# Creates a new TextBox object with the given inline items (e.g. TextFragment and InlineBox
|
@@ -89,40 +123,34 @@ module HexaPDF
|
|
89
123
|
# Depending on the 'position' style property, the text is either fit into the current region
|
90
124
|
# of the frame using +available_width+ and +available_height+, or fit to the shape of the
|
91
125
|
# frame starting from the top (when 'position' is set to :flow).
|
92
|
-
def fit_content(
|
126
|
+
def fit_content(_available_width, _available_height, frame)
|
93
127
|
frame = frame.child_frame(box: self)
|
94
|
-
@
|
128
|
+
@x_offset = 0
|
95
129
|
|
96
|
-
|
97
|
-
|
130
|
+
if style.position == :flow
|
131
|
+
height = (@initial_height > 0 ? @initial_height : frame.shape.bbox.height) - reserved_height
|
132
|
+
@result = @tl.fit(@items, frame.width_specification(reserved_height_top), height,
|
98
133
|
apply_first_text_indent: !split_box?, frame: frame)
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
end
|
120
|
-
@height += if @initial_height > 0 || style.text_valign == :center || style.text_valign == :bottom
|
121
|
-
height
|
122
|
-
else
|
123
|
-
@result.height
|
124
|
-
end
|
125
|
-
if style.last_line_gap && @result.lines.last
|
134
|
+
min_x = +Float::INFINITY
|
135
|
+
max_x = -Float::INFINITY
|
136
|
+
@result.lines.each do |line|
|
137
|
+
min_x = [min_x, line.x_offset].min
|
138
|
+
max_x = [max_x, line.x_offset + line.width].max
|
139
|
+
end
|
140
|
+
@width = (min_x.finite? ? (@x_offset = min_x; max_x - min_x) : 0) + reserved_width
|
141
|
+
@height = @initial_height > 0 ? @initial_height : @result.height + reserved_height
|
142
|
+
else
|
143
|
+
@result = @tl.fit(@items, @width - reserved_width, @height - reserved_height,
|
144
|
+
apply_first_text_indent: !split_box?, frame: frame)
|
145
|
+
if style.text_align == :left && @initial_width == 0
|
146
|
+
@width = (@result.lines.max_by(&:width)&.width || 0) + reserved_width
|
147
|
+
end
|
148
|
+
if style.text_valign == :top && @initial_height == 0
|
149
|
+
@height = @result.height + reserved_height
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
if style.last_line_gap && @result.lines.last && @initial_height == 0
|
126
154
|
@height += style.line_spacing.gap(@result.lines.last, @result.lines.last)
|
127
155
|
end
|
128
156
|
|
@@ -52,7 +52,7 @@ module HexaPDF
|
|
52
52
|
# The items of a text fragment may be frozen to indicate that the fragment is potentially used
|
53
53
|
# multiple times.
|
54
54
|
#
|
55
|
-
# The rectangle with the bottom
|
55
|
+
# The rectangle with the bottom-left corner (#x_min, #y_min) and the top-right corner (#x_max,
|
56
56
|
# #y_max) describes the minimum bounding box of the whole text fragment and is usually *not*
|
57
57
|
# equal to the box (0, 0)-(#width, #height).
|
58
58
|
class TextFragment
|
@@ -218,7 +218,10 @@ module HexaPDF
|
|
218
218
|
|
219
219
|
# Breaks are detected at: space, tab, zero-width-space, non-breaking space, hyphen,
|
220
220
|
# soft-hypen and any valid Unicode newline separator
|
221
|
-
|
221
|
+
BREAK_CHARS = {}
|
222
|
+
" \u{A}\u{B}\u{C}\u{D}\u{85}\u{2028}\u{2029}\t\u{200B}\u{00AD}\u{00A0}-".each_char do |c|
|
223
|
+
BREAK_CHARS[c] = true
|
224
|
+
end
|
222
225
|
|
223
226
|
# Breaks the items (an array of InlineBox and TextFragment objects) into atomic pieces
|
224
227
|
# wrapped by Box, Glue or Penalty items, and returns those as an array.
|
@@ -235,7 +238,7 @@ module HexaPDF
|
|
235
238
|
# Collect characters and kerning values until break character is encountered
|
236
239
|
box_items = []
|
237
240
|
while (glyph = item.items[i]) &&
|
238
|
-
(glyph.kind_of?(Numeric) || !
|
241
|
+
(glyph.kind_of?(Numeric) || !BREAK_CHARS.key?(glyph.str))
|
239
242
|
box_items << glyph
|
240
243
|
i += 1
|
241
244
|
end
|
@@ -428,9 +431,7 @@ module HexaPDF
|
|
428
431
|
end
|
429
432
|
|
430
433
|
line = create_unjustified_line
|
431
|
-
last_line_used = true
|
432
|
-
last_line_used = yield(line, nil) if item.nil? && !line.items.empty?
|
433
|
-
|
434
|
+
last_line_used = (item.nil? && !line.items.empty? ? yield(line, nil) : true)
|
434
435
|
item.nil? && last_line_used ? [] : @items[@beginning_of_line_index..-1]
|
435
436
|
end
|
436
437
|
|
@@ -500,9 +501,7 @@ module HexaPDF
|
|
500
501
|
end
|
501
502
|
|
502
503
|
line = create_unjustified_line
|
503
|
-
last_line_used = true
|
504
|
-
last_line_used = yield(line, nil) if item.nil? && !line.items.empty?
|
505
|
-
|
504
|
+
last_line_used = (item.nil? && !line.items.empty? ? yield(line, nil) : true)
|
506
505
|
item.nil? && last_line_used ? [] : @items[@beginning_of_line_index..-1]
|
507
506
|
end
|
508
507
|
|
data/lib/hexapdf/rectangle.rb
CHANGED
@@ -48,8 +48,8 @@ module HexaPDF
|
|
48
48
|
#
|
49
49
|
# [left, bottom, right, top]
|
50
50
|
#
|
51
|
-
# where +left+ is the bottom
|
52
|
-
# is the top
|
51
|
+
# where +left+ is the bottom-left x-coordinate, +bottom+ is the bottom-left y-coordinate, +right+
|
52
|
+
# is the top-right x-coordinate and +top+ is the top-right y-coordinate.
|
53
53
|
#
|
54
54
|
# See: PDF2.0 s7.9.5
|
55
55
|
class Rectangle < HexaPDF::PDFArray
|
@@ -119,8 +119,8 @@ module HexaPDF
|
|
119
119
|
#:nodoc:
|
120
120
|
RECTANGLE_ERROR_MSG = "A PDF rectangle structure must contain an array of four numbers"
|
121
121
|
|
122
|
-
# Ensures that the value is an array containing four numbers that specify the bottom
|
123
|
-
# top
|
122
|
+
# Ensures that the value is an array containing four numbers that specify the bottom-left and
|
123
|
+
# top-right corners.
|
124
124
|
def after_data_change
|
125
125
|
super
|
126
126
|
unless value.size == 4 && all? {|v| v.kind_of?(Numeric) }
|
data/lib/hexapdf/type/form.rb
CHANGED
@@ -159,8 +159,8 @@ module HexaPDF
|
|
159
159
|
# retained without the need for parsing its contents.
|
160
160
|
#
|
161
161
|
# If the bounding box of the form XObject doesn't have its origin at (0, 0), the canvas origin
|
162
|
-
# is translated into the bottom
|
163
|
-
# canvas. This means that the canvas' origin is always at the bottom
|
162
|
+
# is translated into the bottom-left corner so that this detail doesn't matter when using the
|
163
|
+
# canvas. This means that the canvas' origin is always at the bottom-left corner of the
|
164
164
|
# bounding box.
|
165
165
|
#
|
166
166
|
# *Note* that a canvas can only be retrieved for initially empty form XObjects!
|
data/lib/hexapdf/version.rb
CHANGED
@@ -47,21 +47,20 @@ describe HexaPDF::Content::CanvasComposer do
|
|
47
47
|
end
|
48
48
|
|
49
49
|
it "splits the box if possible" do
|
50
|
-
@composer.draw_box(create_box(width:
|
51
|
-
box = create_box(
|
52
|
-
box.define_singleton_method(:
|
53
|
-
|
54
|
-
end
|
50
|
+
@composer.draw_box(create_box(width: 300, height: 300, style: {position: :float}))
|
51
|
+
box = create_box(style: {mask_mode: :box})
|
52
|
+
box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
|
53
|
+
box.define_singleton_method(:split_content) { [box, HexaPDF::Layout::Box.new(height: 100) {}] }
|
55
54
|
@composer.draw_box(box)
|
56
55
|
assert_operators(@composer.canvas.contents,
|
57
56
|
[[:save_graphics_state],
|
58
|
-
[:concatenate_matrix, [1, 0, 0, 1, 0,
|
57
|
+
[:concatenate_matrix, [1, 0, 0, 1, 0, 541.889764]],
|
59
58
|
[:restore_graphics_state],
|
60
59
|
[:save_graphics_state],
|
61
|
-
[:concatenate_matrix, [1, 0, 0, 1,
|
60
|
+
[:concatenate_matrix, [1, 0, 0, 1, 300, 0]],
|
62
61
|
[:restore_graphics_state],
|
63
62
|
[:save_graphics_state],
|
64
|
-
[:concatenate_matrix, [1, 0, 0, 1,
|
63
|
+
[:concatenate_matrix, [1, 0, 0, 1, 0, 441.889764]],
|
65
64
|
[:restore_graphics_state]])
|
66
65
|
end
|
67
66
|
|
@@ -77,6 +76,12 @@ describe HexaPDF::Content::CanvasComposer do
|
|
77
76
|
[:restore_graphics_state]])
|
78
77
|
end
|
79
78
|
|
79
|
+
it "handles truncated boxes correctly" do
|
80
|
+
box = create_box(height: 400, style: {overflow: :truncate})
|
81
|
+
box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
|
82
|
+
assert_same(box, @composer.draw_box(box))
|
83
|
+
end
|
84
|
+
|
80
85
|
it "returns the last drawn box" do
|
81
86
|
box = create_box(height: 400)
|
82
87
|
assert_same(box, @composer.draw_box(box))
|
@@ -141,6 +141,22 @@ describe HexaPDF::Document::Layout do
|
|
141
141
|
end
|
142
142
|
end
|
143
143
|
|
144
|
+
describe "styles" do
|
145
|
+
it "returns the existing styles" do
|
146
|
+
@layout.style(:test, font_size: 20)
|
147
|
+
assert_equal([:base, :test], @layout.styles.keys)
|
148
|
+
end
|
149
|
+
|
150
|
+
it "sets multiple styles at once" do
|
151
|
+
styles = @layout.styles(
|
152
|
+
test: {font_size: 20},
|
153
|
+
test2: {font_size: 30},
|
154
|
+
)
|
155
|
+
assert_same(styles, @layout.styles)
|
156
|
+
assert_equal([:base, :test, :test2], @layout.styles.keys)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
144
160
|
describe "inline_box" do
|
145
161
|
it "takes a box as argument" do
|
146
162
|
box = HexaPDF::Layout::Box.create(width: 10, height: 10)
|
@@ -40,6 +40,9 @@ describe HexaPDF::Layout::Box do
|
|
40
40
|
@frame = Object.new
|
41
41
|
def @frame.x; 0; end
|
42
42
|
def @frame.y; 100; end
|
43
|
+
def @frame.bottom; 40; end
|
44
|
+
def @frame.width; 150; end
|
45
|
+
def @frame.height; 150; end
|
43
46
|
end
|
44
47
|
|
45
48
|
def create_box(**args, &block)
|
@@ -130,6 +133,14 @@ describe HexaPDF::Layout::Box do
|
|
130
133
|
assert_equal(100, box.height)
|
131
134
|
end
|
132
135
|
|
136
|
+
it "use the frame's width and its remaining height for position=:flow boxes" do
|
137
|
+
box = create_box(style: {position: :flow})
|
138
|
+
box.define_singleton_method(:supports_position_flow?) { true }
|
139
|
+
assert(box.fit(100, 100, @frame).success?)
|
140
|
+
assert_equal(150, box.width)
|
141
|
+
assert_equal(60, box.height)
|
142
|
+
end
|
143
|
+
|
133
144
|
it "uses float comparison" do
|
134
145
|
box = create_box(width: 50.0000002, height: 49.9999996)
|
135
146
|
assert(box.fit(50, 50, @frame).success?)
|
@@ -423,6 +423,15 @@ describe HexaPDF::Layout::Frame do
|
|
423
423
|
box = HexaPDF::Layout::Box.create
|
424
424
|
refute(@frame.fit(box).success?)
|
425
425
|
end
|
426
|
+
|
427
|
+
it "doesn't do post-fitting tasks if fitting is a failure" do
|
428
|
+
box = HexaPDF::Layout::Box.create(width: 400)
|
429
|
+
result = @frame.fit(box)
|
430
|
+
assert(result.failure?)
|
431
|
+
assert_nil(result.x)
|
432
|
+
assert_nil(result.y)
|
433
|
+
assert_nil(result.mask)
|
434
|
+
end
|
426
435
|
end
|
427
436
|
|
428
437
|
describe "split" do
|
@@ -134,13 +134,13 @@ describe HexaPDF::Layout::ListBox do
|
|
134
134
|
|
135
135
|
describe "split" do
|
136
136
|
it "splits before a list item if no part of it will fit" do
|
137
|
-
box = create_box(children: @text_boxes[0,
|
138
|
-
assert(box.fit(100,
|
137
|
+
box = create_box(children: @text_boxes[0, 3])
|
138
|
+
assert(box.fit(100, 22, @frame).overflow?)
|
139
139
|
box_a, box_b = box.split
|
140
140
|
assert_same(box, box_a)
|
141
141
|
assert_equal(:show_first_marker, box_b.split_box?)
|
142
142
|
assert_equal(1, box_a.instance_variable_get(:@results)[0].box_fitter.fit_results.size)
|
143
|
-
assert_equal(
|
143
|
+
assert_equal(2, box_b.children.size)
|
144
144
|
assert_equal(2, box_b.start_number)
|
145
145
|
end
|
146
146
|
|
@@ -116,7 +116,14 @@ describe HexaPDF::Layout::TableBox::Cell do
|
|
116
116
|
assert_equal(12, cell.preferred_height)
|
117
117
|
end
|
118
118
|
|
119
|
-
it "doesn't fit
|
119
|
+
it "doesn't fit children that are too big" do
|
120
|
+
cell = create_cell(children: HexaPDF::Layout::Box.create(width: 300, height: 20))
|
121
|
+
assert(cell.fit(100, 100, @frame).failure?)
|
122
|
+
cell = create_cell(children: [HexaPDF::Layout::Box.create(width: 300, height: 20)])
|
123
|
+
assert(cell.fit(100, 100, @frame).failure?)
|
124
|
+
end
|
125
|
+
|
126
|
+
it "doesn't fit anything if the available width or height are too small even if there are no children" do
|
120
127
|
cell = create_cell(children: nil)
|
121
128
|
assert(cell.fit(10, 100, @frame).failure?)
|
122
129
|
assert(cell.fit(100, 10, @frame).failure?)
|
@@ -42,31 +42,32 @@ describe HexaPDF::Layout::TextBox do
|
|
42
42
|
end
|
43
43
|
|
44
44
|
it "respects the set width and height" do
|
45
|
-
box = create_box([@inline_box], width:
|
45
|
+
box = create_box([@inline_box] * 5, width: 44, height: 50,
|
46
|
+
style: {padding: 10, text_align: :right, text_valign: :bottom})
|
46
47
|
assert(box.fit(100, 100, @frame).success?)
|
47
|
-
assert_equal(
|
48
|
+
assert_equal(44, box.width)
|
48
49
|
assert_equal(50, box.height)
|
49
|
-
assert_equal([10], box.instance_variable_get(:@result).lines.map(&:width))
|
50
|
+
assert_equal([20, 20, 10], box.instance_variable_get(:@result).lines.map(&:width))
|
50
51
|
end
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
end
|
53
|
+
describe "style option last_line_gap" do
|
54
|
+
it "is taken into account" do
|
55
|
+
box = create_box([@inline_box] * 5, style: {last_line_gap: true, line_spacing: :double})
|
56
|
+
assert(box.fit(100, 100, @frame).success?)
|
57
|
+
assert_equal(50, box.width)
|
58
|
+
assert_equal(20, box.height)
|
59
|
+
end
|
60
60
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
61
|
+
it "will have no effect for fixed-height boxes" do
|
62
|
+
box = create_box([@inline_box] * 5, height: 40, style: {last_line_gap: true, line_spacing: :double})
|
63
|
+
assert(box.fit(100, 100, @frame).success?)
|
64
|
+
assert_equal(50, box.width)
|
65
|
+
assert_equal(40, box.height)
|
66
|
+
end
|
66
67
|
end
|
67
68
|
|
68
|
-
it "uses the whole available width when aligning to the center or
|
69
|
-
[:center, :right].each do |align|
|
69
|
+
it "uses the whole available width when aligning to the center, right or justified" do
|
70
|
+
[:center, :right, :justify].each do |align|
|
70
71
|
box = create_box([@inline_box], style: {text_align: align})
|
71
72
|
assert(box.fit(100, 100, @frame).success?)
|
72
73
|
assert_equal(100, box.width)
|
@@ -103,6 +104,33 @@ describe HexaPDF::Layout::TextBox do
|
|
103
104
|
assert(box.fit(100, 100, @frame).success?)
|
104
105
|
end
|
105
106
|
|
107
|
+
describe "position :flow" do
|
108
|
+
it "fits into the frame's outline" do
|
109
|
+
@frame.remove_area(Geom2D::Rectangle(0, 80, 20, 20))
|
110
|
+
@frame.remove_area(Geom2D::Rectangle(80, 70, 20, 20))
|
111
|
+
box = create_box([@inline_box] * 20, style: {position: :flow})
|
112
|
+
assert(box.fit(100, 100, @frame).success?)
|
113
|
+
assert_equal(100, box.width)
|
114
|
+
assert_equal(30, box.height)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "respects a set initial height" do
|
118
|
+
box = create_box([@inline_box] * 20, height: 13, style: {position: :flow})
|
119
|
+
assert(box.fit(100, 100, @frame).overflow?)
|
120
|
+
assert_equal(100, box.width)
|
121
|
+
assert_equal(13, box.height)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "respects top/bottom padding/border" do
|
125
|
+
@frame.remove_area(Geom2D::Rectangle(0, 80, 20, 20))
|
126
|
+
box = create_box([@inline_box] * 20, style: {position: :flow, padding: 10, border: {width: 2}})
|
127
|
+
assert(box.fit(100, 100, @frame).success?)
|
128
|
+
assert_equal(124, box.width)
|
129
|
+
assert_equal(54, box.height)
|
130
|
+
assert_equal([80, 100, 20], box.instance_variable_get(:@result).lines.map(&:width))
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
106
134
|
it "fails if no item of the text box fits due to the width" do
|
107
135
|
box = create_box([@inline_box])
|
108
136
|
assert(box.fit(5, 20, @frame).failure?)
|
@@ -167,12 +195,12 @@ describe HexaPDF::Layout::TextBox do
|
|
167
195
|
@frame.remove_area(Geom2D::Rectangle(0, 0, 40, 100))
|
168
196
|
box = create_box([@inline_box], style: {position: :flow, border: {width: 1}})
|
169
197
|
box.fit(60, 100, @frame)
|
170
|
-
box.draw(@canvas, 0,
|
198
|
+
box.draw(@canvas, 0, 88)
|
171
199
|
assert_operators(@canvas.contents, [[:save_graphics_state],
|
172
|
-
[:append_rectangle, [40,
|
200
|
+
[:append_rectangle, [40, 88, 12, 12]],
|
173
201
|
[:clip_path_non_zero],
|
174
202
|
[:end_path],
|
175
|
-
[:append_rectangle, [40.5,
|
203
|
+
[:append_rectangle, [40.5, 88.5, 11.0, 11.0]],
|
176
204
|
[:stroke_path],
|
177
205
|
[:restore_graphics_state],
|
178
206
|
[:save_graphics_state],
|
@@ -119,6 +119,13 @@ describe HexaPDF::Composer do
|
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
122
|
+
describe "styles" do
|
123
|
+
it "delegates to layout.styles" do
|
124
|
+
@composer.styles(base: {font_size: 30}, other: {font_size: 40})
|
125
|
+
assert_equal([:base, :other], @composer.document.layout.styles.keys)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
122
129
|
describe "page_style" do
|
123
130
|
it "returns the page style if no argument or block is given" do
|
124
131
|
page_style = @composer.page_style(:default)
|
@@ -225,8 +232,9 @@ describe HexaPDF::Composer do
|
|
225
232
|
first_page_contents = @composer.canvas.contents
|
226
233
|
@composer.draw_box(create_box(height: 400))
|
227
234
|
|
228
|
-
box = create_box
|
229
|
-
box.define_singleton_method(:
|
235
|
+
box = create_box
|
236
|
+
box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
|
237
|
+
box.define_singleton_method(:split_content) do |*|
|
230
238
|
[box, HexaPDF::Layout::Box.new(height: 100) {}]
|
231
239
|
end
|
232
240
|
@composer.draw_box(box)
|
@@ -235,7 +243,7 @@ describe HexaPDF::Composer do
|
|
235
243
|
[:concatenate_matrix, [1, 0, 0, 1, 36, 405.889764]],
|
236
244
|
[:restore_graphics_state],
|
237
245
|
[:save_graphics_state],
|
238
|
-
[:concatenate_matrix, [1, 0, 0, 1, 36,
|
246
|
+
[:concatenate_matrix, [1, 0, 0, 1, 36, 36]],
|
239
247
|
[:restore_graphics_state]])
|
240
248
|
assert_operators(@composer.canvas.contents,
|
241
249
|
[[:save_graphics_state],
|
@@ -273,9 +281,10 @@ describe HexaPDF::Composer do
|
|
273
281
|
box = create_box(height: 400)
|
274
282
|
assert_same(box, @composer.draw_box(box))
|
275
283
|
|
276
|
-
box = create_box(height: 400)
|
277
284
|
split_box = create_box(height: 100)
|
278
|
-
box
|
285
|
+
box = create_box
|
286
|
+
box.define_singleton_method(:fit_content) {|*| fit_result.overflow! }
|
287
|
+
box.define_singleton_method(:split_content) {|*| [box, split_box] }
|
279
288
|
assert_same(split_box, @composer.draw_box(box))
|
280
289
|
end
|
281
290
|
|
@@ -89,6 +89,7 @@ describe HexaPDF::Serializer do
|
|
89
89
|
assert_serialized('/ ', :"")
|
90
90
|
assert_serialized('/H#c3#b6#c3#9fgang', :Hößgang)
|
91
91
|
assert_serialized('/H#e8lp', "H\xE8lp".force_encoding('BINARY').intern)
|
92
|
+
assert_serialized('/#00#09#0a#0c#0d#20', :"\x00\t\n\f\r ")
|
92
93
|
end
|
93
94
|
|
94
95
|
it "serializes arrays" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hexapdf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.45.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Leitner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-06-
|
11
|
+
date: 2024-06-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cmdparse
|