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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +120 -47
  3. data/examples/019-acro_form.rb +5 -0
  4. data/lib/hexapdf/cli/inspect.rb +5 -0
  5. data/lib/hexapdf/composer.rb +1 -1
  6. data/lib/hexapdf/configuration.rb +19 -0
  7. data/lib/hexapdf/digital_signature/cms_handler.rb +31 -3
  8. data/lib/hexapdf/digital_signature/signing/default_handler.rb +9 -1
  9. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +5 -1
  10. data/lib/hexapdf/document/layout.rb +48 -27
  11. data/lib/hexapdf/document.rb +24 -2
  12. data/lib/hexapdf/encryption/standard_security_handler.rb +32 -26
  13. data/lib/hexapdf/importer.rb +15 -5
  14. data/lib/hexapdf/layout/box.rb +25 -28
  15. data/lib/hexapdf/layout/frame.rb +1 -1
  16. data/lib/hexapdf/layout/inline_box.rb +17 -23
  17. data/lib/hexapdf/layout/list_box.rb +24 -29
  18. data/lib/hexapdf/layout/page_style.rb +23 -16
  19. data/lib/hexapdf/layout/style.rb +2 -2
  20. data/lib/hexapdf/layout/table_box.rb +57 -10
  21. data/lib/hexapdf/layout/text_box.rb +2 -6
  22. data/lib/hexapdf/parser.rb +5 -1
  23. data/lib/hexapdf/revisions.rb +1 -1
  24. data/lib/hexapdf/stream.rb +3 -3
  25. data/lib/hexapdf/task/optimize.rb +4 -4
  26. data/lib/hexapdf/tokenizer.rb +3 -2
  27. data/lib/hexapdf/type/acro_form/appearance_generator.rb +8 -4
  28. data/lib/hexapdf/type/acro_form/button_field.rb +2 -0
  29. data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
  30. data/lib/hexapdf/type/acro_form/field.rb +8 -0
  31. data/lib/hexapdf/type/acro_form/form.rb +10 -6
  32. data/lib/hexapdf/type/acro_form/signature_field.rb +2 -1
  33. data/lib/hexapdf/type/acro_form/text_field.rb +2 -0
  34. data/lib/hexapdf/type/acro_form/variable_text_field.rb +11 -3
  35. data/lib/hexapdf/type/annotations/widget.rb +4 -2
  36. data/lib/hexapdf/version.rb +1 -1
  37. data/lib/hexapdf/writer.rb +1 -0
  38. data/test/data/standard-security-handler/bothpwd-aes-256bit-V5-R5.pdf +43 -0
  39. data/test/data/standard-security-handler/nopwd-aes-256bit-V5-R5.pdf +44 -0
  40. data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5-R5.pdf +43 -0
  41. data/test/data/standard-security-handler/userpwd-aes-256bit-V5-R5.pdf +0 -0
  42. data/test/hexapdf/digital_signature/common.rb +66 -84
  43. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +7 -0
  44. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +9 -0
  45. data/test/hexapdf/digital_signature/test_cms_handler.rb +41 -1
  46. data/test/hexapdf/digital_signature/test_handler.rb +2 -1
  47. data/test/hexapdf/digital_signature/test_signatures.rb +4 -4
  48. data/test/hexapdf/document/test_layout.rb +28 -5
  49. data/test/hexapdf/encryption/test_standard_security_handler.rb +5 -2
  50. data/test/hexapdf/layout/test_box.rb +12 -5
  51. data/test/hexapdf/layout/test_frame.rb +12 -2
  52. data/test/hexapdf/layout/test_inline_box.rb +17 -28
  53. data/test/hexapdf/layout/test_list_box.rb +5 -5
  54. data/test/hexapdf/layout/test_page_style.rb +7 -2
  55. data/test/hexapdf/layout/test_table_box.rb +52 -0
  56. data/test/hexapdf/layout/test_text_box.rb +3 -9
  57. data/test/hexapdf/layout/test_text_layouter.rb +0 -3
  58. data/test/hexapdf/task/test_optimize.rb +2 -0
  59. data/test/hexapdf/test_document.rb +30 -3
  60. data/test/hexapdf/test_importer.rb +24 -0
  61. data/test/hexapdf/test_revisions.rb +54 -41
  62. data/test/hexapdf/test_writer.rb +11 -2
  63. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +22 -5
  64. data/test/hexapdf/type/acro_form/test_form.rb +9 -5
  65. data/test/hexapdf/type/acro_form/test_signature_field.rb +3 -1
  66. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +14 -1
  67. data/test/hexapdf/type/annotations/test_widget.rb +4 -0
  68. metadata +6 -2
@@ -243,7 +243,7 @@ module HexaPDF
243
243
  break if !box_fitter.success? || height <= 0
244
244
  end
245
245
 
246
- @height = @results.sum(&:height) + (@results.count - 1) * item_spacing + reserved_height
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 (fragment = @item_marker_fragment)
319
+ unless (items = @item_marker_items)
320
320
  marker_style = {
321
- font: style.font? ? style.font : document.fonts.add("Times"),
322
- font_size: style.font_size || 10, fill_color: style.fill_color
321
+ font_size: style.font_size || 10,
322
+ fill_color: style.fill_color,
323
323
  }
324
- fragment = case @marker_type
325
- when :disc
326
- TextFragment.create("•", marker_style)
327
- when :circle
328
- unless marker_style[:font].decode_codepoint("❍".ord).valid?
329
- marker_style[:font] = document.fonts.add("ZapfDingbats")
330
- end
331
- TextFragment.create("❍", **marker_style,
332
- font_size: style.font_size / 2.0,
333
- text_rise: -style.font_size / 1.8)
334
- when :square
335
- unless marker_style[:font].decode_codepoint("■".ord).valid?
336
- marker_style[:font] = document.fonts.add("ZapfDingbats")
337
- end
338
- TextFragment.create("■", **marker_style,
339
- font_size: style.font_size / 2.0,
340
- text_rise: -style.font_size / 1.8)
341
- when :decimal
342
- text = (@start_number + index).to_s << "."
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: [fragment], style: {text_align: :right, padding: [0, 5, 0, 0]})
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 initial look of a page and the placement of one or more frames.
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 it should set the #frame and #next_style attributes appropriately, if not done
71
- # beforehand. The #create_frame method can be used for easily creating a rectangular frame.
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 HexaPDF::Layout::Frame object that defines the area on the page where content should be
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 can either be set beforehand or during execution of the #template.
88
- #
89
- # If no frame has been set, a frame covering the page except for a default margin on all sides
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 with this page style and returns it.
122
+ # Creates a new page in the given document using this page style and returns it.
117
123
  #
118
- # If #frame has not been set beforehand or during execution of the #template, a default frame
119
- # covering the whole page except a margin of 36 is created.
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 ||= create_frame(page, 36)
130
+ self.frame = create_frame(page, 36) if @frame.equal?(frame_before)
124
131
  page
125
132
  end
126
133
 
@@ -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 and the space to the left
1373
- # and right in the frame.
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.left = column_info[cell.column].first
400
- cell.top = height - available_height
401
- row_height = cell.preferred_height if row_height < cell.preferred_height
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 row_fit
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? ? (@x_offset = min_x; max_x - min_x) : 0) + reserved_width
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,
@@ -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
@@ -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
@@ -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 to their default
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? && field.default? &&
223
- value == field.default
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
@@ -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.slice!(0, 8192)
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[:V] == :Off ? :Yes : @field[:V]] = nil
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
- raise HexaPDF::Error, "Widget of button field doesn't define name for on state"
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 Form object
230
- form = @document.wrap(form, type: :XObject, subtype: :Form) unless form[:Subtype] == :Form
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 set.
190
- # Otherwise there will be an error when trying to generate a visual representation of
191
- # the field value.
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 or, if still not available, the default appearance string of the form.
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
- raise HexaPDF::Error, "No default appearance string set" unless da
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 1.7 spec and the /CA entry of the
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
- _, size, color = HexaPDF::Type::AcroForm::VariableTextField.parse_appearance_string(da)
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)
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '0.45.0'
40
+ VERSION = '0.47.0'
41
41
 
42
42
  end
@@ -163,6 +163,7 @@ module HexaPDF
163
163
 
164
164
  trailer = rev.trailer.value.dup
165
165
  trailer.delete(:XRefStm)
166
+ trailer.delete(:Type)
166
167
  if previous_xref_pos
167
168
  trailer[:Prev] = previous_xref_pos
168
169
  else
@@ -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