hexapdf 0.45.0 → 0.47.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 +120 -47
- data/examples/019-acro_form.rb +5 -0
- data/lib/hexapdf/cli/inspect.rb +5 -0
- data/lib/hexapdf/composer.rb +1 -1
- data/lib/hexapdf/configuration.rb +19 -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 +48 -27
- data/lib/hexapdf/document.rb +24 -2
- data/lib/hexapdf/encryption/standard_security_handler.rb +32 -26
- data/lib/hexapdf/importer.rb +15 -5
- data/lib/hexapdf/layout/box.rb +25 -28
- data/lib/hexapdf/layout/frame.rb +1 -1
- data/lib/hexapdf/layout/inline_box.rb +17 -23
- data/lib/hexapdf/layout/list_box.rb +24 -29
- data/lib/hexapdf/layout/page_style.rb +23 -16
- data/lib/hexapdf/layout/style.rb +2 -2
- data/lib/hexapdf/layout/table_box.rb +57 -10
- data/lib/hexapdf/layout/text_box.rb +2 -6
- data/lib/hexapdf/parser.rb +5 -1
- data/lib/hexapdf/revisions.rb +1 -1
- data/lib/hexapdf/stream.rb +3 -3
- data/lib/hexapdf/task/optimize.rb +4 -4
- data/lib/hexapdf/tokenizer.rb +3 -2
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +8 -4
- 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 +10 -6
- data/lib/hexapdf/type/acro_form/signature_field.rb +2 -1
- data/lib/hexapdf/type/acro_form/text_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +11 -3
- data/lib/hexapdf/type/annotations/widget.rb +4 -2
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +1 -0
- data/test/data/standard-security-handler/bothpwd-aes-256bit-V5-R5.pdf +43 -0
- data/test/data/standard-security-handler/nopwd-aes-256bit-V5-R5.pdf +44 -0
- data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5-R5.pdf +43 -0
- data/test/data/standard-security-handler/userpwd-aes-256bit-V5-R5.pdf +0 -0
- 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/digital_signature/test_signatures.rb +4 -4
- data/test/hexapdf/document/test_layout.rb +28 -5
- data/test/hexapdf/encryption/test_standard_security_handler.rb +5 -2
- data/test/hexapdf/layout/test_box.rb +12 -5
- data/test/hexapdf/layout/test_frame.rb +12 -2
- data/test/hexapdf/layout/test_inline_box.rb +17 -28
- data/test/hexapdf/layout/test_list_box.rb +5 -5
- data/test/hexapdf/layout/test_page_style.rb +7 -2
- data/test/hexapdf/layout/test_table_box.rb +52 -0
- data/test/hexapdf/layout/test_text_box.rb +3 -9
- data/test/hexapdf/layout/test_text_layouter.rb +0 -3
- data/test/hexapdf/task/test_optimize.rb +2 -0
- data/test/hexapdf/test_document.rb +30 -3
- data/test/hexapdf/test_importer.rb +24 -0
- data/test/hexapdf/test_revisions.rb +54 -41
- data/test/hexapdf/test_writer.rb +11 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +22 -5
- data/test/hexapdf/type/acro_form/test_form.rb +9 -5
- data/test/hexapdf/type/acro_form/test_signature_field.rb +3 -1
- data/test/hexapdf/type/acro_form/test_variable_text_field.rb +14 -1
- data/test/hexapdf/type/annotations/test_widget.rb +4 -0
- metadata +6 -2
|
@@ -243,7 +243,7 @@ module HexaPDF
|
|
|
243
243
|
break if !box_fitter.success? || height <= 0
|
|
244
244
|
end
|
|
245
245
|
|
|
246
|
-
|
|
246
|
+
update_content_height { @results.sum(&:height) + (@results.count - 1) * item_spacing }
|
|
247
247
|
|
|
248
248
|
if @results.size == @children.size && @results.all? {|r| r.box_fitter.success? }
|
|
249
249
|
fit_result.success!
|
|
@@ -316,37 +316,32 @@ module HexaPDF
|
|
|
316
316
|
def item_marker_box(document, index)
|
|
317
317
|
return @marker_type.call(document, self, index) if @marker_type.kind_of?(Proc)
|
|
318
318
|
|
|
319
|
-
unless (
|
|
319
|
+
unless (items = @item_marker_items)
|
|
320
320
|
marker_style = {
|
|
321
|
-
|
|
322
|
-
|
|
321
|
+
font_size: style.font_size || 10,
|
|
322
|
+
fill_color: style.fill_color,
|
|
323
323
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
TextFragment.create(text, marker_style)
|
|
344
|
-
else
|
|
345
|
-
raise HexaPDF::Error, "Unknown list marker type #{@marker_type.inspect}"
|
|
346
|
-
end
|
|
347
|
-
@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
|
|
348
343
|
end
|
|
349
|
-
TextBox.new(items:
|
|
344
|
+
TextBox.new(items: items, style: {text_align: :right, padding: [0, 5, 0, 0]})
|
|
350
345
|
end
|
|
351
346
|
|
|
352
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
|
@@ -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
|
#
|
|
@@ -382,7 +382,14 @@ module HexaPDF
|
|
|
382
382
|
def fit_rows(start_row, available_height, column_info, frame)
|
|
383
383
|
height = available_height
|
|
384
384
|
last_fitted_row_index = -1
|
|
385
|
+
row_heights = {}
|
|
386
|
+
zero_height_rows = {}
|
|
387
|
+
row_spans = []
|
|
388
|
+
|
|
385
389
|
@cells[start_row..-1].each.with_index(start_row) do |columns, row_index|
|
|
390
|
+
# 1. Fit all columns of the row and record the max height of all non-row-span cells. If
|
|
391
|
+
# a row has zero height (usually because it only has row-span cells), record that
|
|
392
|
+
# information. Additionally store all cells with row-spans.
|
|
386
393
|
row_fit = true
|
|
387
394
|
row_height = 0
|
|
388
395
|
columns.each_with_index do |cell, col_index|
|
|
@@ -396,27 +403,67 @@ module HexaPDF
|
|
|
396
403
|
row_fit = false
|
|
397
404
|
break
|
|
398
405
|
end
|
|
399
|
-
cell.
|
|
400
|
-
|
|
401
|
-
|
|
406
|
+
if row_height < cell.preferred_height && cell.row_span == 1
|
|
407
|
+
row_height = cell.preferred_height
|
|
408
|
+
end
|
|
409
|
+
row_spans << cell if cell.row_span > 1
|
|
402
410
|
end
|
|
403
411
|
|
|
404
|
-
if
|
|
405
|
-
seen = {}
|
|
406
|
-
columns.each do |cell|
|
|
407
|
-
next if seen[cell]
|
|
408
|
-
cell.update_height(cell.row == row_index ? row_height : cell.height + row_height)
|
|
409
|
-
seen[cell] = true
|
|
410
|
-
end
|
|
412
|
+
zero_height_rows[row_index] = true if row_height == 0
|
|
411
413
|
|
|
414
|
+
if row_fit
|
|
415
|
+
# 2. If all cells of the row fit, we subtract the recorded row height of the
|
|
416
|
+
# non-row-span cells from the available height for the next pass.
|
|
412
417
|
last_fitted_row_index = row_index
|
|
418
|
+
row_heights[row_index] = row_height
|
|
413
419
|
available_height -= row_height
|
|
420
|
+
|
|
421
|
+
# 3. We look at all row-span cells that end at the current row index. If the row-span
|
|
422
|
+
# cell is larger than the sum of the row heights, we proportionally enlarge the
|
|
423
|
+
# stored height of each spanned row and subtract the difference from the available
|
|
424
|
+
# height for the next pass. If the row span contains initially zero-height rows,
|
|
425
|
+
# only those rows are enlarged. Row-span cells themselves are not updated at this
|
|
426
|
+
# point!
|
|
427
|
+
row_spans.each do |cell|
|
|
428
|
+
upper_row_index = cell.row + cell.row_span - 1
|
|
429
|
+
next unless upper_row_index == row_index
|
|
430
|
+
|
|
431
|
+
rows = cell.row.upto(upper_row_index)
|
|
432
|
+
row_span_height = rows.sum {|ri| row_heights[ri] }
|
|
433
|
+
if row_span_height < cell.preferred_height
|
|
434
|
+
zero_height_rows_in_span = rows.select {|ri| zero_height_rows[ri] }
|
|
435
|
+
rows = zero_height_rows_in_span if zero_height_rows_in_span.size > 0
|
|
436
|
+
adjustment = (cell.preferred_height - row_span_height) / rows.size.to_f
|
|
437
|
+
rows.each {|ri| row_heights[ri] += adjustment }
|
|
438
|
+
available_height -= cell.preferred_height - row_span_height
|
|
439
|
+
end
|
|
440
|
+
end
|
|
414
441
|
else
|
|
415
442
|
last_fitted_row_index = columns.min_by(&:row).row - 1 if height != available_height
|
|
416
443
|
break
|
|
417
444
|
end
|
|
418
445
|
end
|
|
419
446
|
|
|
447
|
+
if last_fitted_row_index >= 0
|
|
448
|
+
# 4. Once all possible rows have been fitted and the heights of the rows are fixed, the
|
|
449
|
+
# final height and top-left corner of each cell needs to be set.
|
|
450
|
+
running_height = 0
|
|
451
|
+
@cells[start_row..last_fitted_row_index].each.with_index(start_row) do |columns, row_index|
|
|
452
|
+
columns.each_with_index do |cell, col_index|
|
|
453
|
+
next if cell.row != row_index || cell.column != col_index
|
|
454
|
+
cell.left = column_info[cell.column].first
|
|
455
|
+
cell.top = running_height
|
|
456
|
+
if cell.row_span == 1
|
|
457
|
+
cell.update_height(row_heights[row_index])
|
|
458
|
+
else
|
|
459
|
+
new_height = cell.row.upto(cell.row + cell.row_span - 1).sum {|ri| row_heights[ri] }
|
|
460
|
+
cell.update_height(new_height)
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
running_height += row_heights[row_index]
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
|
|
420
467
|
[height - available_height, last_fitted_row_index < start_row ? -1 : last_fitted_row_index]
|
|
421
468
|
end
|
|
422
469
|
|
|
@@ -106,11 +106,6 @@ module HexaPDF
|
|
|
106
106
|
true
|
|
107
107
|
end
|
|
108
108
|
|
|
109
|
-
# :nodoc:
|
|
110
|
-
def draw(canvas, x, y)
|
|
111
|
-
super(canvas, x + @x_offset, y)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
109
|
# :nodoc:
|
|
115
110
|
def empty?
|
|
116
111
|
super && (!@result || @result.lines.empty?)
|
|
@@ -137,7 +132,8 @@ module HexaPDF
|
|
|
137
132
|
min_x = [min_x, line.x_offset].min
|
|
138
133
|
max_x = [max_x, line.x_offset + line.width].max
|
|
139
134
|
end
|
|
140
|
-
@width = (min_x.finite? ?
|
|
135
|
+
@width = (min_x.finite? ? max_x - min_x : 0) + reserved_width
|
|
136
|
+
fit_result.x = @x_offset = min_x
|
|
141
137
|
@height = @initial_height > 0 ? @initial_height : @result.height + reserved_height
|
|
142
138
|
else
|
|
143
139
|
@result = @tl.fit(@items, @width - reserved_width, @height - reserved_height,
|
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/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
|
|
|
@@ -214,13 +214,13 @@ module HexaPDF
|
|
|
214
214
|
end
|
|
215
215
|
end
|
|
216
216
|
|
|
217
|
-
# Deletes field entries of the object that are optional and currently set
|
|
218
|
-
# value.
|
|
217
|
+
# Deletes field entries (except for /Type) of the object that are optional and currently set
|
|
218
|
+
# to their default value.
|
|
219
219
|
def self.delete_fields_with_defaults(obj)
|
|
220
220
|
return unless obj.kind_of?(HexaPDF::Dictionary) && !obj.null?
|
|
221
221
|
obj.each do |name, value|
|
|
222
|
-
if (field = obj.class.field(name)) && !field.required? &&
|
|
223
|
-
|
|
222
|
+
if name != :Type && (field = obj.class.field(name)) && !field.required? &&
|
|
223
|
+
field.default? && value == field.default
|
|
224
224
|
obj.delete(name)
|
|
225
225
|
end
|
|
226
226
|
end
|
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
|
|
@@ -134,11 +134,12 @@ module HexaPDF
|
|
|
134
134
|
if !normal_appearance.kind_of?(HexaPDF::Dictionary) || normal_appearance.kind_of?(HexaPDF::Stream)
|
|
135
135
|
(@widget[:AP] ||= {})[:N] = {Off: nil}
|
|
136
136
|
normal_appearance = @widget[:AP][:N]
|
|
137
|
-
normal_appearance[@field
|
|
137
|
+
normal_appearance[@field.field_value&.to_sym || :Yes] = nil
|
|
138
138
|
end
|
|
139
139
|
on_name = (normal_appearance.value.keys - [:Off]).first
|
|
140
140
|
unless on_name
|
|
141
|
-
|
|
141
|
+
on_name = @field.field_value&.to_sym || :Yes
|
|
142
|
+
normal_appearance[on_name] = nil
|
|
142
143
|
end
|
|
143
144
|
|
|
144
145
|
@widget[:AS] = (@field[:V] == on_name ? on_name : :Off)
|
|
@@ -226,8 +227,11 @@ module HexaPDF
|
|
|
226
227
|
|
|
227
228
|
form = (@widget[:AP] ||= {})[:N] ||= @document.add({Type: :XObject, Subtype: :Form})
|
|
228
229
|
# Wrap existing object in Form class in case the PDF writer didn't include the /Subtype
|
|
229
|
-
# key; we can do this since we know this has to be a
|
|
230
|
-
|
|
230
|
+
# key or the type of the object is wrong; we can do this since we know this has to be a
|
|
231
|
+
# Form object
|
|
232
|
+
unless form.type == :XObject && form[:Subtype] == :Form
|
|
233
|
+
form = @document.wrap(form, type: :XObject, subtype: :Form)
|
|
234
|
+
end
|
|
231
235
|
form.value.replace({Type: :XObject, Subtype: :Form, BBox: [0, 0, width, height],
|
|
232
236
|
Matrix: matrix, Resources: HexaPDF::Object.deep_copy(default_resources)})
|
|
233
237
|
form.contents = ''
|
|
@@ -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.
|
|
@@ -76,6 +76,8 @@ module HexaPDF
|
|
|
76
76
|
#
|
|
77
77
|
# :no_export:: The field should *not* be exported by a submit-form action.
|
|
78
78
|
#
|
|
79
|
+
# Also see the class description of the subclasses for additional, type specific field flags.
|
|
80
|
+
#
|
|
79
81
|
# == Field Type Implementation Notes
|
|
80
82
|
#
|
|
81
83
|
# If an AcroForm field type adds additional inheritable dictionary fields, it has to set the
|
|
@@ -124,6 +126,8 @@ module HexaPDF
|
|
|
124
126
|
#
|
|
125
127
|
# Returns an array of flag names representing the set bit flags.
|
|
126
128
|
#
|
|
129
|
+
# See the class description for a list of available flags.
|
|
130
|
+
#
|
|
127
131
|
|
|
128
132
|
##
|
|
129
133
|
# :method: flagged?
|
|
@@ -133,6 +137,8 @@ module HexaPDF
|
|
|
133
137
|
# Returns +true+ if the given flag is set. The argument can either be the flag name or the
|
|
134
138
|
# bit index.
|
|
135
139
|
#
|
|
140
|
+
# See the class description for a list of available flags.
|
|
141
|
+
#
|
|
136
142
|
|
|
137
143
|
##
|
|
138
144
|
# :method: flag
|
|
@@ -142,6 +148,8 @@ module HexaPDF
|
|
|
142
148
|
# Sets the given flags, given as flag names or bit indices. If +clear_existing+ is +true+,
|
|
143
149
|
# all prior flags will be cleared.
|
|
144
150
|
#
|
|
151
|
+
# See the class description for a list of available flags.
|
|
152
|
+
#
|
|
145
153
|
bit_field(:flags, {read_only: 0, required: 1, no_export: 2},
|
|
146
154
|
lister: "flags", getter: "flagged?", setter: "flag", unsetter: "unflag",
|
|
147
155
|
value_getter: "self[:Ff]", value_setter: "self[:Ff]")
|
|
@@ -186,9 +186,14 @@ module HexaPDF
|
|
|
186
186
|
# or +font_color+ is specified but +font+ isn't, the font Helvetica is used.
|
|
187
187
|
#
|
|
188
188
|
# If no font is set on the text field, the default font properties of the AcroForm form
|
|
189
|
-
# are used. Note that field specific or form specific font properties have to be
|
|
190
|
-
# Otherwise there
|
|
191
|
-
#
|
|
189
|
+
# are used. Note that field specific or form specific font properties have to be
|
|
190
|
+
# set. Otherwise there might be problems when creating a visual appearance with other
|
|
191
|
+
# PDF libraries/viewers.
|
|
192
|
+
#
|
|
193
|
+
# If HexaPDF is used to create a visual appearance of the field value and neither field
|
|
194
|
+
# specific nor form specific font properties are available, the configuration option
|
|
195
|
+
# 'acro_form.fallback_default_appearance' defines whether and which field specific font
|
|
196
|
+
# properties are set and used.
|
|
192
197
|
#
|
|
193
198
|
# +font_options+::
|
|
194
199
|
# A hash with font options like :variant that should be used.
|
|
@@ -388,13 +393,14 @@ module HexaPDF
|
|
|
388
393
|
page_annots = page[:Annots].to_a - to_delete
|
|
389
394
|
page[:Annots].value.replace(page_annots)
|
|
390
395
|
end
|
|
391
|
-
to_delete.each {|widget| document.delete(widget) }
|
|
392
396
|
|
|
393
397
|
if field[:Parent]
|
|
394
398
|
field[:Parent][:Kids].delete(field)
|
|
395
399
|
else
|
|
396
400
|
self[:Fields].delete(field)
|
|
397
401
|
end
|
|
402
|
+
|
|
403
|
+
to_delete.each {|widget| document.delete(widget) }
|
|
398
404
|
document.delete(field)
|
|
399
405
|
end
|
|
400
406
|
|
|
@@ -624,8 +630,6 @@ module HexaPDF
|
|
|
624
630
|
if font_name && !(self[:DR][:Font] && self[:DR][:Font][font_name])
|
|
625
631
|
yield("The font specified in /DA is not in the /DR resource dictionary")
|
|
626
632
|
end
|
|
627
|
-
else
|
|
628
|
-
set_default_appearance_string
|
|
629
633
|
end
|
|
630
634
|
|
|
631
635
|
create_appearances if document.config['acro_form.create_appearances']
|
|
@@ -199,7 +199,8 @@ module HexaPDF
|
|
|
199
199
|
|
|
200
200
|
# Returns the associated signature dictionary or +nil+ if the signature is not filled in.
|
|
201
201
|
def field_value
|
|
202
|
-
self[:V]
|
|
202
|
+
val = self[:V]
|
|
203
|
+
val.instance_of?(Dictionary) ? document.wrap(val, type: :Sig) : val
|
|
203
204
|
end
|
|
204
205
|
|
|
205
206
|
# Sets the signature dictionary as value of this signature field.
|
|
@@ -50,6 +50,8 @@ module HexaPDF
|
|
|
50
50
|
#
|
|
51
51
|
# == Type Specific Field Flags
|
|
52
52
|
#
|
|
53
|
+
# See the class description for Field for the general field flags.
|
|
54
|
+
#
|
|
53
55
|
# :multiline:: If set, the text field may contain multiple lines.
|
|
54
56
|
#
|
|
55
57
|
# :password:: The field is a password field. This changes the behaviour of the PDF reader
|
|
@@ -106,7 +106,7 @@ module HexaPDF
|
|
|
106
106
|
font_params[2] = HexaPDF::Content::ColorSpace.prenormalized_device_color(params)
|
|
107
107
|
end
|
|
108
108
|
end
|
|
109
|
-
HexaPDF::Content::Parser.parse(appearance_string.sub(/\/\//, '/'), &block)
|
|
109
|
+
HexaPDF::Content::Parser.parse(appearance_string.to_s.sub(/\/\//, '/'), &block)
|
|
110
110
|
block_given? ? nil : font_params
|
|
111
111
|
end
|
|
112
112
|
|
|
@@ -154,13 +154,21 @@ module HexaPDF
|
|
|
154
154
|
# font_size, font_color].
|
|
155
155
|
#
|
|
156
156
|
# The default appearance string is taken from the given +widget+ of the field, falls back to
|
|
157
|
-
# the field itself
|
|
157
|
+
# the field itself and then the default appearance string of the form. If it still not
|
|
158
|
+
# available, a standard default appearance string is set (see
|
|
159
|
+
# #set_default_appearance_string) and used.
|
|
158
160
|
#
|
|
159
161
|
# The reason why a specific widget of the field can be specified is because the widgets of a
|
|
160
162
|
# field might differ in their visual representation.
|
|
161
163
|
def parse_default_appearance_string(widget = self)
|
|
162
164
|
da = widget[:DA] || self[:DA] || (document.acro_form && document.acro_form[:DA])
|
|
163
|
-
|
|
165
|
+
unless da
|
|
166
|
+
if (args = document.config['acro_form.fallback_default_appearance'])
|
|
167
|
+
da = set_default_appearance_string(**args)
|
|
168
|
+
else
|
|
169
|
+
raise HexaPDF::Error, "No default appearance string set"
|
|
170
|
+
end
|
|
171
|
+
end
|
|
164
172
|
self.class.parse_appearance_string(da)
|
|
165
173
|
end
|
|
166
174
|
|
|
@@ -259,7 +259,7 @@ module HexaPDF
|
|
|
259
259
|
# auto-sized checkmark (i.e. :check for for check boxes) or circle (:circle for radio
|
|
260
260
|
# buttons). This also means that multiple invocations will reset *all* prior values.
|
|
261
261
|
#
|
|
262
|
-
# Note: The marker is called "normal caption" in the PDF
|
|
262
|
+
# Note: The marker is called "normal caption" in the PDF 2.0 spec and the /CA entry of the
|
|
263
263
|
# associated appearance characteristics dictionary. The marker size and color are set using
|
|
264
264
|
# the /DA key on the widget (although /DA is not defined for widget, this is how Acrobat
|
|
265
265
|
# does it).
|
|
@@ -305,7 +305,9 @@ module HexaPDF
|
|
|
305
305
|
size = 0
|
|
306
306
|
color = HexaPDF::Content::ColorSpace.prenormalized_device_color([0])
|
|
307
307
|
if (da = self[:DA] || field[:DA])
|
|
308
|
-
_,
|
|
308
|
+
_, da_size, da_color = AcroForm::VariableTextField.parse_appearance_string(da)
|
|
309
|
+
size = da_size || size
|
|
310
|
+
color = da_color || color
|
|
309
311
|
end
|
|
310
312
|
|
|
311
313
|
MarkerStyle.new(style, size, color)
|
data/lib/hexapdf/version.rb
CHANGED
data/lib/hexapdf/writer.rb
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
%PDF-1.7
|
|
2
|
+
%����
|
|
3
|
+
1 0 obj
|
|
4
|
+
<< /Extensions << /ADBE << /BaseVersion /1.7 /ExtensionLevel 3 >> >> /Pages 3 0 R /Type /Catalog >>
|
|
5
|
+
endobj
|
|
6
|
+
2 0 obj
|
|
7
|
+
<< /ModDate <aa1d1637459ea17c7fc9709f0c0c7b64761ff74e905b3765f33425776aa3010163cd5e898cfa7c5fc16159359375c323> >>
|
|
8
|
+
endobj
|
|
9
|
+
3 0 obj
|
|
10
|
+
<< /Count 1 /Kids [ 4 0 R ] /MediaBox [ 0 0 612 446 ] /Type /Pages >>
|
|
11
|
+
endobj
|
|
12
|
+
4 0 obj
|
|
13
|
+
<< /Contents 5 0 R /Parent 3 0 R /Resources 6 0 R /Type /Page >>
|
|
14
|
+
endobj
|
|
15
|
+
5 0 obj
|
|
16
|
+
<< /Length 80 /Filter /FlateDecode >>
|
|
17
|
+
stream
|
|
18
|
+
J<~S�)��9�M�$S�z��g�j���r�.R�"PO���_���X���^�GP�&z�g�*$�2SΈ�z�Kl��5ĩendstream
|
|
19
|
+
endobj
|
|
20
|
+
6 0 obj
|
|
21
|
+
<< /Font << /F1 7 0 R >> /ProcSet [ /PDF /Text ] >>
|
|
22
|
+
endobj
|
|
23
|
+
7 0 obj
|
|
24
|
+
<< /BaseFont /Helvetica /Name /F1 /Subtype /Type1 /Type /Font >>
|
|
25
|
+
endobj
|
|
26
|
+
8 0 obj
|
|
27
|
+
<< /CF << /StdCF << /AuthEvent /DocOpen /CFM /AESV3 /Length 32 >> >> /Filter /Standard /Length 256 /O <c60934c0925e8d3ceebd0609102d9407dcf22d7fb3d87d030fce633af6d2ed99c1c3382bee5e8afc0d55ff8bca441d48> /OE <3fa2e3ecf34ddcb38b44af372a43268dda32111f58dc79da74d960b8fa206ead> /P -4 /Perms <b6c6ab65529f0a3322f03909e8a5547a> /R 5 /StmF /StdCF /StrF /StdCF /U <18fbd94777c28531c495a3116d273de9f8f3ed338b31c07687ca0ba06812842843765a66484d098194bcef0f9ecaaace> /UE <496a81bbb3207dfd12ddca4c16b60a411c6a18e638205824295e09afde826b4a> /V 5 >>
|
|
28
|
+
endobj
|
|
29
|
+
xref
|
|
30
|
+
0 9
|
|
31
|
+
0000000000 65535 f
|
|
32
|
+
0000000015 00000 n
|
|
33
|
+
0000000130 00000 n
|
|
34
|
+
0000000259 00000 n
|
|
35
|
+
0000000344 00000 n
|
|
36
|
+
0000000424 00000 n
|
|
37
|
+
0000000574 00000 n
|
|
38
|
+
0000000641 00000 n
|
|
39
|
+
0000000721 00000 n
|
|
40
|
+
trailer << /Info 2 0 R /Root 1 0 R /Size 9 /ID [<6790ffa610024e78369114311fc0df96><ed6c0810cb0d19599ac62042a0487749>] /Encrypt 8 0 R >>
|
|
41
|
+
startxref
|
|
42
|
+
1268
|
|
43
|
+
%%EOF
|