hexapdf 0.45.0 → 0.47.0

Sign up to get free protection for your applications and to get access to all the features.
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