hexapdf 0.44.0 → 0.45.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 +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
|