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