hexapdf 0.44.0 → 0.46.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 +106 -47
- data/examples/019-acro_form.rb +5 -0
- data/examples/027-composer_optional_content.rb +6 -4
- data/examples/030-pdfa.rb +12 -11
- data/lib/hexapdf/cli/inspect.rb +5 -0
- data/lib/hexapdf/composer.rb +23 -1
- data/lib/hexapdf/configuration.rb +8 -0
- data/lib/hexapdf/content/canvas.rb +3 -3
- data/lib/hexapdf/content/canvas_composer.rb +1 -0
- data/lib/hexapdf/digital_signature/cms_handler.rb +31 -3
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +9 -1
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +5 -1
- data/lib/hexapdf/document/layout.rb +63 -30
- data/lib/hexapdf/document.rb +24 -2
- data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
- data/lib/hexapdf/importer.rb +15 -5
- data/lib/hexapdf/layout/box.rb +48 -36
- data/lib/hexapdf/layout/column_box.rb +3 -11
- data/lib/hexapdf/layout/container_box.rb +4 -4
- data/lib/hexapdf/layout/frame.rb +7 -6
- data/lib/hexapdf/layout/inline_box.rb +17 -23
- data/lib/hexapdf/layout/list_box.rb +27 -42
- data/lib/hexapdf/layout/page_style.rb +23 -16
- data/lib/hexapdf/layout/style.rb +5 -5
- data/lib/hexapdf/layout/table_box.rb +14 -10
- data/lib/hexapdf/layout/text_box.rb +60 -36
- data/lib/hexapdf/layout/text_fragment.rb +1 -1
- data/lib/hexapdf/layout/text_layouter.rb +7 -8
- data/lib/hexapdf/parser.rb +5 -1
- data/lib/hexapdf/rectangle.rb +4 -4
- data/lib/hexapdf/revisions.rb +1 -1
- data/lib/hexapdf/stream.rb +3 -3
- data/lib/hexapdf/tokenizer.rb +3 -2
- data/lib/hexapdf/type/acro_form/button_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/field.rb +8 -0
- data/lib/hexapdf/type/acro_form/form.rb +2 -1
- data/lib/hexapdf/type/acro_form/text_field.rb +2 -0
- 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/digital_signature/common.rb +66 -84
- data/test/hexapdf/digital_signature/signing/test_default_handler.rb +7 -0
- data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +9 -0
- data/test/hexapdf/digital_signature/test_cms_handler.rb +41 -1
- data/test/hexapdf/digital_signature/test_handler.rb +2 -1
- data/test/hexapdf/document/test_layout.rb +44 -5
- data/test/hexapdf/layout/test_box.rb +23 -5
- data/test/hexapdf/layout/test_frame.rb +21 -2
- data/test/hexapdf/layout/test_inline_box.rb +17 -28
- data/test/hexapdf/layout/test_list_box.rb +8 -8
- data/test/hexapdf/layout/test_page_style.rb +7 -2
- data/test/hexapdf/layout/test_table_box.rb +8 -1
- data/test/hexapdf/layout/test_text_box.rb +51 -29
- data/test/hexapdf/layout/test_text_layouter.rb +0 -3
- data/test/hexapdf/test_composer.rb +14 -5
- data/test/hexapdf/test_document.rb +27 -0
- data/test/hexapdf/test_importer.rb +17 -0
- data/test/hexapdf/test_revisions.rb +54 -41
- data/test/hexapdf/test_serializer.rb +1 -0
- data/test/hexapdf/type/acro_form/test_form.rb +9 -0
- metadata +2 -2
@@ -43,14 +43,12 @@ module HexaPDF
|
|
43
43
|
# An InlineBox wraps a regular Box so that it can be used as an item for a Line. This enables
|
44
44
|
# inline graphics.
|
45
45
|
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
46
|
+
# When an inline box gets placed on a line, the method #fit_wrapped_box is called to fit the
|
47
|
+
# wrapped box. This allows the wrapped box to correctly set its width and height which are
|
48
|
+
# needed by the TextLayouter algorithm.
|
49
49
|
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
# wrapped box and its height, or if not set, a practically infinite height. In the latter case
|
53
|
-
# the height *must* be set during fitting.
|
50
|
+
# Note: It is *mandatory* that the wrapped box sets its width and height without relying on the
|
51
|
+
# dimensions of the frame's current region.
|
54
52
|
class InlineBox
|
55
53
|
|
56
54
|
# Creates an InlineBox that wraps a basic Box. All arguments (except +valign+) and the block
|
@@ -74,7 +72,6 @@ module HexaPDF
|
|
74
72
|
# The +valign+ argument can be used to specify the vertical alignment of the box relative to
|
75
73
|
# other items in the Line.
|
76
74
|
def initialize(box, valign: :baseline)
|
77
|
-
raise HexaPDF::Error, "Width of box not set" if box.width == 0
|
78
75
|
@box = box
|
79
76
|
@valign = valign
|
80
77
|
end
|
@@ -102,8 +99,7 @@ module HexaPDF
|
|
102
99
|
# Draws the wrapped box. If the box has margins specified, the x and y offsets are correctly
|
103
100
|
# adjusted.
|
104
101
|
def draw(canvas, x, y)
|
105
|
-
|
106
|
-
y - @fit_result.y + box.style.margin.bottom) { @fit_result.draw(canvas) }
|
102
|
+
@fit_result.draw(canvas, dx: x, dy: y)
|
107
103
|
end
|
108
104
|
|
109
105
|
# The minimum x-coordinate which is always 0.
|
@@ -129,19 +125,17 @@ module HexaPDF
|
|
129
125
|
# Fits the wrapped box.
|
130
126
|
#
|
131
127
|
# If the +frame+ argument is +nil+, a custom frame is created. Otherwise the given +frame+ is
|
132
|
-
# used for the fitting operation.
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
@fit_result =
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
raise HexaPDF::Error, "Box for inline use has no valid height set after fitting"
|
144
|
-
end
|
128
|
+
# used for creating an appropriate child frame for the fitting operation.
|
129
|
+
#
|
130
|
+
# After this operation the caller is responsible for checking the actual width and height of
|
131
|
+
# the inline box and whether it really fits.
|
132
|
+
def fit_wrapped_box(frame = nil)
|
133
|
+
@fit_result = box.fit(100_000, 100_000, frame || Frame.new(0, 0, 100_000, 100_000))
|
134
|
+
margin = box.style.margin if box.style.margin?
|
135
|
+
@fit_result.x = margin&.left.to_i
|
136
|
+
@fit_result.y = margin&.bottom.to_i
|
137
|
+
@fit_result.mask = Geom2D::Rectangle(0, 0, @fit_result.x + box.width + margin&.right.to_i,
|
138
|
+
@fit_result.y + box.height + margin&.top.to_i)
|
145
139
|
end
|
146
140
|
|
147
141
|
end
|
@@ -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
|
@@ -253,7 +243,7 @@ module HexaPDF
|
|
253
243
|
break if !box_fitter.success? || height <= 0
|
254
244
|
end
|
255
245
|
|
256
|
-
|
246
|
+
update_content_height { @results.sum(&:height) + (@results.count - 1) * item_spacing }
|
257
247
|
|
258
248
|
if @results.size == @children.size && @results.all? {|r| r.box_fitter.success? }
|
259
249
|
fit_result.success!
|
@@ -326,37 +316,32 @@ module HexaPDF
|
|
326
316
|
def item_marker_box(document, index)
|
327
317
|
return @marker_type.call(document, self, index) if @marker_type.kind_of?(Proc)
|
328
318
|
|
329
|
-
unless (
|
319
|
+
unless (items = @item_marker_items)
|
330
320
|
marker_style = {
|
331
|
-
|
332
|
-
|
321
|
+
font_size: style.font_size || 10,
|
322
|
+
fill_color: style.fill_color,
|
333
323
|
}
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
TextFragment.create(text, marker_style)
|
354
|
-
else
|
355
|
-
raise HexaPDF::Error, "Unknown list marker type #{@marker_type.inspect}"
|
356
|
-
end
|
357
|
-
@item_marker_fragment = fragment unless @marker_type == :decimal
|
324
|
+
marker_style[:font] = style.font if style.font?
|
325
|
+
items = case @marker_type
|
326
|
+
when :disc
|
327
|
+
document.layout.text_fragments("•", style: marker_style)
|
328
|
+
when :circle
|
329
|
+
document.layout.text_fragments("❍", style: marker_style,
|
330
|
+
font_size: style.font_size / 2.0,
|
331
|
+
text_rise: -style.font_size / 1.8)
|
332
|
+
when :square
|
333
|
+
document.layout.text_fragments("■", style: marker_style,
|
334
|
+
font_size: style.font_size / 2.0,
|
335
|
+
text_rise: -style.font_size / 1.8)
|
336
|
+
when :decimal
|
337
|
+
text = (@start_number + index).to_s << "."
|
338
|
+
document.layout.text_fragments(text, style: marker_style)
|
339
|
+
else
|
340
|
+
raise HexaPDF::Error, "Unknown list marker type #{@marker_type.inspect}"
|
341
|
+
end
|
342
|
+
@item_marker_items = items unless @marker_type == :decimal
|
358
343
|
end
|
359
|
-
TextBox.new(items:
|
344
|
+
TextBox.new(items: items, style: {text_align: :right, padding: [0, 5, 0, 0]})
|
360
345
|
end
|
361
346
|
|
362
347
|
# Draws the list items onto the canvas at position [x, y].
|
@@ -41,7 +41,10 @@ require 'hexapdf/layout/frame'
|
|
41
41
|
module HexaPDF
|
42
42
|
module Layout
|
43
43
|
|
44
|
-
# A PageStyle defines the
|
44
|
+
# A PageStyle defines the dimensions of a page, its initial look, the Frame for object placement
|
45
|
+
# and which page style should be used next.
|
46
|
+
#
|
47
|
+
# This class is used by HexaPDF::Composer to style the individual pages.
|
45
48
|
class PageStyle
|
46
49
|
|
47
50
|
# The page size.
|
@@ -65,14 +68,15 @@ module HexaPDF
|
|
65
68
|
# The callable object is given a canvas and the page style as arguments. It needs to draw the
|
66
69
|
# initial content of the page. Note that the graphics state of the canvas is *not* saved
|
67
70
|
# before executing the template code and restored afterwards. If this is needed, the object
|
68
|
-
# needs to do it itself.
|
71
|
+
# needs to do it itself. The #next_style attribute can optionally be set.
|
69
72
|
#
|
70
|
-
# Furthermore
|
71
|
-
#
|
73
|
+
# Furthermore, the callable object should set the #frame that defines the area on the page
|
74
|
+
# where content should be placed. The #create_frame method can be used for easily creating a
|
75
|
+
# rectangular frame.
|
72
76
|
#
|
73
77
|
# Example:
|
74
78
|
#
|
75
|
-
# page_style.template = lambda do |canvas, style
|
79
|
+
# page_style.template = lambda do |canvas, style|
|
76
80
|
# box = canvas.context.box
|
77
81
|
# canvas.fill_color("fd0") do
|
78
82
|
# canvas.rectangle(0, 0, box.width, box.height).fill
|
@@ -81,22 +85,24 @@ module HexaPDF
|
|
81
85
|
# end
|
82
86
|
attr_accessor :template
|
83
87
|
|
84
|
-
# The
|
85
|
-
# placed.
|
88
|
+
# The Frame object that defines the area for the last page created with #create_page where
|
89
|
+
# content should be placed.
|
86
90
|
#
|
87
|
-
# This
|
88
|
-
#
|
89
|
-
#
|
90
|
-
# is set during #create_page.
|
91
|
+
# This value is usually updated during execution of the #template. If the value is not
|
92
|
+
# updated, a frame covering the page except for a default margin on all sides is set during
|
93
|
+
# #create_page.
|
91
94
|
attr_accessor :frame
|
92
95
|
|
93
96
|
# Defines the name of the page style that should be used for the next page.
|
94
97
|
#
|
98
|
+
# Note that this value can be different each time a new page is created via #create_page.
|
99
|
+
#
|
95
100
|
# If this attribute is +nil+ (the default), it means that this style should be used again.
|
96
101
|
attr_accessor :next_style
|
97
102
|
|
98
103
|
# Creates a new page style instance for the given page size, orientation and next style
|
99
|
-
# values. If a block is given, it is used as template for defining the initial content
|
104
|
+
# values. If a block is given, it is used as #template for defining the initial content of a
|
105
|
+
# page.
|
100
106
|
#
|
101
107
|
# Example:
|
102
108
|
#
|
@@ -113,14 +119,15 @@ module HexaPDF
|
|
113
119
|
@next_style = next_style
|
114
120
|
end
|
115
121
|
|
116
|
-
# Creates a new page in the given document
|
122
|
+
# Creates a new page in the given document using this page style and returns it.
|
117
123
|
#
|
118
|
-
# If #frame has not
|
119
|
-
#
|
124
|
+
# If the #frame has not changed during execution of the #template, a default frame covering
|
125
|
+
# the whole page except a margin of 36 is assigned.
|
120
126
|
def create_page(document)
|
127
|
+
frame_before = @frame
|
121
128
|
page = document.pages.create(media_box: page_size, orientation: orientation)
|
122
129
|
template&.call(page.canvas, self)
|
123
|
-
self.frame
|
130
|
+
self.frame = create_frame(page, 36) if @frame.equal?(frame_before)
|
124
131
|
page
|
125
132
|
end
|
126
133
|
|
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
|
#
|
@@ -1369,8 +1369,8 @@ module HexaPDF
|
|
1369
1369
|
# composer.text('Text underneath')
|
1370
1370
|
#
|
1371
1371
|
# :fill_frame_horizontal::
|
1372
|
-
# The mask covers the box including the margin around the box
|
1373
|
-
#
|
1372
|
+
# The mask covers the box including the margin around the box, the space to the left and
|
1373
|
+
# right in the frame and the space to the top of the current region.
|
1374
1374
|
#
|
1375
1375
|
# Examples:
|
1376
1376
|
#
|
@@ -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
|
@@ -72,11 +106,6 @@ module HexaPDF
|
|
72
106
|
true
|
73
107
|
end
|
74
108
|
|
75
|
-
# :nodoc:
|
76
|
-
def draw(canvas, x, y)
|
77
|
-
super(canvas, x + @x_offset, y)
|
78
|
-
end
|
79
|
-
|
80
109
|
# :nodoc:
|
81
110
|
def empty?
|
82
111
|
super && (!@result || @result.lines.empty?)
|
@@ -89,40 +118,35 @@ module HexaPDF
|
|
89
118
|
# Depending on the 'position' style property, the text is either fit into the current region
|
90
119
|
# of the frame using +available_width+ and +available_height+, or fit to the shape of the
|
91
120
|
# frame starting from the top (when 'position' is set to :flow).
|
92
|
-
def fit_content(
|
121
|
+
def fit_content(_available_width, _available_height, frame)
|
93
122
|
frame = frame.child_frame(box: self)
|
94
|
-
@
|
123
|
+
@x_offset = 0
|
95
124
|
|
96
|
-
|
97
|
-
|
125
|
+
if style.position == :flow
|
126
|
+
height = (@initial_height > 0 ? @initial_height : frame.shape.bbox.height) - reserved_height
|
127
|
+
@result = @tl.fit(@items, frame.width_specification(reserved_height_top), height,
|
128
|
+
apply_first_text_indent: !split_box?, frame: frame)
|
129
|
+
min_x = +Float::INFINITY
|
130
|
+
max_x = -Float::INFINITY
|
131
|
+
@result.lines.each do |line|
|
132
|
+
min_x = [min_x, line.x_offset].min
|
133
|
+
max_x = [max_x, line.x_offset + line.width].max
|
134
|
+
end
|
135
|
+
@width = (min_x.finite? ? max_x - min_x : 0) + reserved_width
|
136
|
+
fit_result.x = @x_offset = min_x
|
137
|
+
@height = @initial_height > 0 ? @initial_height : @result.height + reserved_height
|
138
|
+
else
|
139
|
+
@result = @tl.fit(@items, @width - reserved_width, @height - reserved_height,
|
98
140
|
apply_first_text_indent: !split_box?, frame: frame)
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
width
|
109
|
-
elsif style.position == :flow
|
110
|
-
min_x = +Float::INFINITY
|
111
|
-
max_x = -Float::INFINITY
|
112
|
-
@result.lines.each do |line|
|
113
|
-
min_x = [min_x, line.x_offset].min
|
114
|
-
max_x = [max_x, line.x_offset + line.width].max
|
115
|
-
end
|
116
|
-
min_x.finite? ? (@x_offset = min_x; max_x - min_x) : 0
|
117
|
-
else
|
118
|
-
@result.lines.max_by(&:width)&.width || 0
|
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
|
141
|
+
if style.text_align == :left && @initial_width == 0
|
142
|
+
@width = (@result.lines.max_by(&:width)&.width || 0) + reserved_width
|
143
|
+
end
|
144
|
+
if style.text_valign == :top && @initial_height == 0
|
145
|
+
@height = @result.height + reserved_height
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
if style.last_line_gap && @result.lines.last && @initial_height == 0
|
126
150
|
@height += style.line_spacing.gap(@result.lines.last, @result.lines.last)
|
127
151
|
end
|
128
152
|
|
@@ -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/parser.rb
CHANGED
@@ -71,6 +71,10 @@ module HexaPDF
|
|
71
71
|
end
|
72
72
|
|
73
73
|
# Returns +true+ if the PDF file is a linearized file.
|
74
|
+
#
|
75
|
+
# Note: The method uses heuristics to determine whether a PDF file is linearized. In case of
|
76
|
+
# slightly invalid or damaged PDFs that HexaPDF can recover from it is possible that this method
|
77
|
+
# returns +true+ even though the PDF isn't actually linearized.
|
74
78
|
def linearized?
|
75
79
|
@linearized ||=
|
76
80
|
begin
|
@@ -293,7 +297,7 @@ module HexaPDF
|
|
293
297
|
next
|
294
298
|
elsif type == 'n'
|
295
299
|
if pos == 0 || gen > 65535
|
296
|
-
maybe_raise("Invalid in use cross-reference entry",
|
300
|
+
maybe_raise("Invalid in use cross-reference entry for object number #{oid}",
|
297
301
|
pos: @tokenizer.pos)
|
298
302
|
xref.add_free_entry(oid, gen)
|
299
303
|
else
|
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/revisions.rb
CHANGED
@@ -97,7 +97,7 @@ module HexaPDF
|
|
97
97
|
merge_revision = offset
|
98
98
|
end
|
99
99
|
|
100
|
-
if merge_revision == offset
|
100
|
+
if merge_revision == offset && !revisions.empty?
|
101
101
|
xref_section.merge!(revisions.first.xref_section)
|
102
102
|
offset = trailer[:Prev] # Get possible next offset before overwriting trailer
|
103
103
|
trailer = revisions.first.trailer
|
data/lib/hexapdf/stream.rb
CHANGED
@@ -51,6 +51,9 @@ module HexaPDF
|
|
51
51
|
# normalized to arrays on assignment to ease further processing.
|
52
52
|
class StreamData
|
53
53
|
|
54
|
+
# The source.
|
55
|
+
attr_reader :source
|
56
|
+
|
54
57
|
# The filter(s) that need to be applied for getting the decoded stream data.
|
55
58
|
attr_reader :filter
|
56
59
|
|
@@ -110,9 +113,6 @@ module HexaPDF
|
|
110
113
|
|
111
114
|
protected
|
112
115
|
|
113
|
-
# The source.
|
114
|
-
attr_reader :source
|
115
|
-
|
116
116
|
# The optional offset into the bytes provided by source.
|
117
117
|
attr_reader :offset
|
118
118
|
|
data/lib/hexapdf/tokenizer.rb
CHANGED
@@ -82,6 +82,7 @@ module HexaPDF
|
|
82
82
|
# correctable situations are only raised if the return value of calling the object is +true+.
|
83
83
|
def initialize(io, on_correctable_error: nil)
|
84
84
|
@io = io
|
85
|
+
@io_chunk = String.new(''.b)
|
85
86
|
@ss = StringScanner.new(''.b)
|
86
87
|
@original_pos = -1
|
87
88
|
@on_correctable_error = on_correctable_error || proc { false }
|
@@ -439,9 +440,9 @@ module HexaPDF
|
|
439
440
|
@io.seek(@next_read_pos)
|
440
441
|
return false if @io.eof?
|
441
442
|
|
442
|
-
@ss << @io.read(8192)
|
443
|
+
@ss << @io.read(8192, @io_chunk)
|
443
444
|
if @ss.pos > 8192 && @ss.string.length > 16384
|
444
|
-
@ss.string.
|
445
|
+
@ss.string.replace(@ss.string.byteslice(8192..-1))
|
445
446
|
@ss.pos -= 8192
|
446
447
|
@original_pos += 8192
|
447
448
|
end
|
@@ -65,6 +65,8 @@ module HexaPDF
|
|
65
65
|
#
|
66
66
|
# == Type Specific Field Flags
|
67
67
|
#
|
68
|
+
# See the class description for Field for the general field flags.
|
69
|
+
#
|
68
70
|
# :no_toggle_to_off:: Only used with radio buttons fields. If this flag is set, one button
|
69
71
|
# needs to be selected at all times. Otherwise, clicking on the selected
|
70
72
|
# button deselects it.
|
@@ -51,6 +51,8 @@ module HexaPDF
|
|
51
51
|
#
|
52
52
|
# == Type Specific Field Flags
|
53
53
|
#
|
54
|
+
# See the class description for Field for the general field flags.
|
55
|
+
#
|
54
56
|
# :combo:: If set, the field represents a combo box.
|
55
57
|
#
|
56
58
|
# :edit:: If set, the combo box includes an editable text box for entering arbitrary values.
|