hexapdf 0.27.0 → 0.29.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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +100 -11
  3. data/examples/019-acro_form.rb +14 -3
  4. data/examples/023-images.rb +30 -0
  5. data/examples/024-digital-signatures.rb +23 -0
  6. data/lib/hexapdf/cli/info.rb +5 -1
  7. data/lib/hexapdf/cli/inspect.rb +2 -2
  8. data/lib/hexapdf/cli/split.rb +2 -2
  9. data/lib/hexapdf/configuration.rb +13 -14
  10. data/lib/hexapdf/content/canvas.rb +8 -3
  11. data/lib/hexapdf/dictionary.rb +1 -5
  12. data/lib/hexapdf/dictionary_fields.rb +6 -2
  13. data/lib/hexapdf/digital_signature/cms_handler.rb +137 -0
  14. data/lib/hexapdf/digital_signature/handler.rb +138 -0
  15. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +96 -0
  16. data/lib/hexapdf/{type → digital_signature}/signature.rb +3 -8
  17. data/lib/hexapdf/digital_signature/signatures.rb +210 -0
  18. data/lib/hexapdf/digital_signature/signing/default_handler.rb +317 -0
  19. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +308 -0
  20. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +148 -0
  21. data/lib/hexapdf/digital_signature/signing.rb +101 -0
  22. data/lib/hexapdf/{type/signature → digital_signature}/verification_result.rb +37 -41
  23. data/lib/hexapdf/digital_signature.rb +56 -0
  24. data/lib/hexapdf/document.rb +27 -24
  25. data/lib/hexapdf/encryption/standard_security_handler.rb +2 -1
  26. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  27. data/lib/hexapdf/importer.rb +32 -27
  28. data/lib/hexapdf/layout/list_box.rb +1 -5
  29. data/lib/hexapdf/object.rb +5 -0
  30. data/lib/hexapdf/parser.rb +13 -0
  31. data/lib/hexapdf/revision.rb +15 -12
  32. data/lib/hexapdf/revisions.rb +4 -0
  33. data/lib/hexapdf/tokenizer.rb +14 -8
  34. data/lib/hexapdf/type/acro_form/appearance_generator.rb +174 -128
  35. data/lib/hexapdf/type/acro_form/button_field.rb +5 -3
  36. data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
  37. data/lib/hexapdf/type/acro_form/field.rb +11 -5
  38. data/lib/hexapdf/type/acro_form/form.rb +33 -7
  39. data/lib/hexapdf/type/acro_form/signature_field.rb +2 -0
  40. data/lib/hexapdf/type/acro_form/text_field.rb +12 -2
  41. data/lib/hexapdf/type/annotations/widget.rb +3 -0
  42. data/lib/hexapdf/type/font_true_type.rb +14 -0
  43. data/lib/hexapdf/type/object_stream.rb +2 -2
  44. data/lib/hexapdf/type/outline.rb +1 -1
  45. data/lib/hexapdf/type/page.rb +56 -46
  46. data/lib/hexapdf/type.rb +0 -1
  47. data/lib/hexapdf/version.rb +1 -1
  48. data/lib/hexapdf/writer.rb +2 -3
  49. data/test/hexapdf/content/test_canvas.rb +5 -0
  50. data/test/hexapdf/{type/signature → digital_signature}/common.rb +34 -4
  51. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
  52. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
  53. data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
  54. data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
  55. data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
  56. data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
  57. data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
  58. data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
  59. data/test/hexapdf/digital_signature/test_signing.rb +53 -0
  60. data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
  61. data/test/hexapdf/document/test_pages.rb +2 -2
  62. data/test/hexapdf/encryption/test_aes.rb +1 -1
  63. data/test/hexapdf/filter/test_predictor.rb +0 -1
  64. data/test/hexapdf/layout/test_box.rb +2 -1
  65. data/test/hexapdf/layout/test_column_box.rb +1 -1
  66. data/test/hexapdf/layout/test_list_box.rb +1 -1
  67. data/test/hexapdf/test_dictionary_fields.rb +2 -1
  68. data/test/hexapdf/test_document.rb +3 -9
  69. data/test/hexapdf/test_importer.rb +13 -6
  70. data/test/hexapdf/test_parser.rb +17 -0
  71. data/test/hexapdf/test_revision.rb +15 -14
  72. data/test/hexapdf/test_revisions.rb +43 -0
  73. data/test/hexapdf/test_stream.rb +1 -1
  74. data/test/hexapdf/test_tokenizer.rb +3 -4
  75. data/test/hexapdf/test_writer.rb +3 -3
  76. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +135 -56
  77. data/test/hexapdf/type/acro_form/test_button_field.rb +6 -1
  78. data/test/hexapdf/type/acro_form/test_choice_field.rb +4 -0
  79. data/test/hexapdf/type/acro_form/test_field.rb +4 -4
  80. data/test/hexapdf/type/acro_form/test_form.rb +18 -0
  81. data/test/hexapdf/type/acro_form/test_signature_field.rb +4 -0
  82. data/test/hexapdf/type/acro_form/test_text_field.rb +13 -0
  83. data/test/hexapdf/type/test_font_true_type.rb +20 -0
  84. data/test/hexapdf/type/test_object_stream.rb +2 -1
  85. data/test/hexapdf/type/test_outline.rb +3 -0
  86. data/test/hexapdf/type/test_page.rb +67 -30
  87. data/test/hexapdf/type/test_page_tree_node.rb +4 -2
  88. metadata +69 -16
  89. data/lib/hexapdf/document/signatures.rb +0 -546
  90. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
  91. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
  92. data/lib/hexapdf/type/signature/handler.rb +0 -140
  93. data/test/hexapdf/document/test_signatures.rb +0 -352
@@ -84,6 +84,8 @@ module HexaPDF
84
84
  # See: PDF1.7 s12.7.4.2
85
85
  class ButtonField < Field
86
86
 
87
+ define_type :XXAcroFormField
88
+
87
89
  define_field :Opt, type: PDFArray, version: '1.4'
88
90
 
89
91
  # All inheritable dictionary fields for button fields.
@@ -206,7 +208,7 @@ module HexaPDF
206
208
  # Note that this will only return useful values if there is at least one correctly set-up
207
209
  # widget.
208
210
  def allowed_values
209
- (each_widget.each_with_object([]) do |widget, result|
211
+ (each_widget.with_object([]) do |widget, result|
210
212
  keys = widget.appearance_dict&.normal_appearance&.value&.keys
211
213
  result.concat(keys) if keys
212
214
  end - [:Off]).uniq
@@ -254,14 +256,14 @@ module HexaPDF
254
256
  normal_appearance = widget.appearance_dict&.normal_appearance
255
257
  next if !force && normal_appearance &&
256
258
  ((!push_button? && normal_appearance.value.length == 2 &&
257
- normal_appearance.value.each_value.all?(HexaPDF::Stream)) ||
259
+ normal_appearance.each.all? {|_, v| v.kind_of?(HexaPDF::Stream) }) ||
258
260
  (push_button? && normal_appearance.kind_of?(HexaPDF::Stream)))
259
261
  if check_box?
260
262
  appearance_generator_class.new(widget).create_check_box_appearances
261
263
  elsif radio_button?
262
264
  appearance_generator_class.new(widget).create_radio_button_appearances
263
265
  else
264
- raise HexaPDF::Error, "Push buttons not yet supported"
266
+ appearance_generator_class.new(widget).create_push_button_appearances
265
267
  end
266
268
  end
267
269
  end
@@ -69,6 +69,8 @@ module HexaPDF
69
69
  # See: PDF1.7 s12.7.4.4
70
70
  class ChoiceField < VariableTextField
71
71
 
72
+ define_type :XXAcroFormField
73
+
72
74
  define_field :Opt, type: PDFArray
73
75
  define_field :TI, type: Integer, default: 0
74
76
  define_field :I, type: PDFArray, version: '1.4'
@@ -242,8 +242,8 @@ module HexaPDF
242
242
  end
243
243
 
244
244
  # :call-seq:
245
- # field.each_widget {|widget| block} -> field
246
- # field.each_widget -> Enumerator
245
+ # field.each_widget(direct_only: true) {|widget| block} -> field
246
+ # field.each_widget(direct_only: true) -> Enumerator
247
247
  #
248
248
  # Yields each widget, i.e. visual representation, of this field.
249
249
  #
@@ -253,11 +253,17 @@ module HexaPDF
253
253
  # 2. One or more widgets are defined as children of this field.
254
254
  # 3. Widgets of *another field instance with the same full field name*.
255
255
  #
256
- # Because of possibility 3 all fields of the form have to be searched to check whether there
257
- # is another field with the same full field name.
256
+ # With the default of +direct_only+ being +true+, only the usual cases 1 and 2 are handled/
257
+ # If case 3 also needs to be handled, set +direct_only+ to +false+ or run the validation on
258
+ # the main AcroForm object (HexaPDF::Document#acro_form) before using this method (this will
259
+ # reduce case 3 to case 2).
260
+ #
261
+ # *Note*: Setting +direct_only+ to +false+ will have a severe performance impact since all
262
+ # fields of the form have to be searched to check whether there is another field with the
263
+ # same full field name.
258
264
  #
259
265
  # See: HexaPDF::Type::Annotations::Widget
260
- def each_widget(direct_only: false, &block) # :yields: widget
266
+ def each_widget(direct_only: true, &block) # :yields: widget
261
267
  return to_enum(__method__, direct_only: direct_only) unless block_given?
262
268
 
263
269
  if embedded_widget?
@@ -125,14 +125,22 @@ module HexaPDF
125
125
  def each_field(terminal_only: true)
126
126
  return to_enum(__method__, terminal_only: terminal_only) unless block_given?
127
127
 
128
- process_field = lambda do |field|
129
- field = document.wrap(field, type: :XXAcroFormField,
130
- subtype: Field.inherited_value(field, :FT))
131
- yield(field) if field.terminal_field? || !terminal_only
132
- field[:Kids].each(&process_field) unless field.terminal_field?
128
+ process_field_array = lambda do |array|
129
+ array.each_with_index do |field, index|
130
+ unless field.respond_to?(:type) && field.type == :XXAcroFormField
131
+ array[index] = field = document.wrap(field, type: :XXAcroFormField,
132
+ subtype: Field.inherited_value(field, :FT))
133
+ end
134
+ if field.terminal_field?
135
+ yield(field)
136
+ else
137
+ yield(field) unless terminal_only
138
+ process_field_array.call(field[:Kids])
139
+ end
140
+ end
133
141
  end
134
142
 
135
- root_fields.each(&process_field)
143
+ process_field_array.call(root_fields)
136
144
  self
137
145
  end
138
146
 
@@ -393,7 +401,7 @@ module HexaPDF
393
401
  fields.each {|field| field.create_appearances if field.respond_to?(:create_appearances) }
394
402
  end
395
403
 
396
- not_flattened = fields.map {|field| field.each_widget.to_a }.flatten
404
+ not_flattened = fields.map {|field| field.each_widget(direct_only: true).to_a }.flatten
397
405
  document.pages.each {|page| not_flattened = page.flatten_annotations(not_flattened) }
398
406
  not_flattened.map!(&:form_field)
399
407
  fields -= not_flattened
@@ -449,6 +457,8 @@ module HexaPDF
449
457
  def perform_validation # :nodoc:
450
458
  super
451
459
 
460
+ seen = {} # used for combining field
461
+
452
462
  validate_array = lambda do |parent, container|
453
463
  container.reject! do |field|
454
464
  if !field.kind_of?(HexaPDF::Object) || !field.kind_of?(HexaPDF::Dictionary) || field.null?
@@ -469,6 +479,22 @@ module HexaPDF
469
479
  field[:Parent] = parent
470
480
  end
471
481
  end
482
+
483
+ # Combine fields with same name
484
+ name = field.full_field_name
485
+ if (other_field = seen[name])
486
+ kids = other_field[:Kids] ||= []
487
+ kids << other_field.send(:extract_widget) if other_field.embedded_widget?
488
+ widgets = field.embedded_widget? ? [field.send(:extract_widget)] : field.each_widget.to_a
489
+ widgets.each do |widget|
490
+ widget[:Parent] = other_field
491
+ kids << widget
492
+ end
493
+ reject = true
494
+ elsif !reject
495
+ seen[name] = field
496
+ end
497
+
472
498
  validate_array.call(field, field[:Kids]) if field.key?(:Kids)
473
499
  reject
474
500
  end
@@ -192,6 +192,8 @@ module HexaPDF
192
192
 
193
193
  end
194
194
 
195
+ define_type :XXAcroFormField
196
+
195
197
  define_field :Lock, type: :SigFieldLock, indirect: true, version: '1.5'
196
198
  define_field :SV, type: :SV, indirect: true, version: '1.5'
197
199
 
@@ -73,6 +73,8 @@ module HexaPDF
73
73
  # See: PDF1.7 s12.7.4.3
74
74
  class TextField < VariableTextField
75
75
 
76
+ define_type :XXAcroFormField
77
+
76
78
  define_field :MaxLen, type: Integer
77
79
 
78
80
  # All inheritable dictionary fields for text fields.
@@ -220,7 +222,15 @@ module HexaPDF
220
222
  current_value = field_value
221
223
  appearance_generator_class = document.config.constantize('acro_form.appearance_generator')
222
224
  each_widget do |widget|
223
- next if !force && widget.cached?(:last_value) && widget.cache(:last_value) == current_value
225
+ is_cached = widget.cached?(:last_value)
226
+ unless force
227
+ if is_cached && widget.cache(:last_value) == current_value
228
+ next
229
+ elsif !is_cached && widget.appearance?
230
+ widget.cache(:last_value, current_value, update: true)
231
+ next
232
+ end
233
+ end
224
234
  widget.cache(:last_value, current_value, update: true)
225
235
  appearance_generator_class.new(widget).create_text_appearances
226
236
  end
@@ -228,7 +238,7 @@ module HexaPDF
228
238
 
229
239
  # Updates the widgets so that they reflect the current field value.
230
240
  def update_widgets
231
- create_appearances
241
+ create_appearances(force: true)
232
242
  end
233
243
 
234
244
  private
@@ -224,6 +224,9 @@ module HexaPDF
224
224
  # The kind of marker that is shown inside the widget. Can either be one of the symbols
225
225
  # +:check+, +:circle+, +:cross+, +:diamond+, +:square+ or +:star+, or a one character
226
226
  # string. The latter is interpreted using the ZapfDingbats font.
227
+ #
228
+ # If an empty string is set, it is treated as if +nil+ was set, i.e. it shows the default
229
+ # marker for the field type.
227
230
  attr_reader :style
228
231
 
229
232
  # The size of the marker in PDF points that is shown inside the widget. The special value
@@ -35,6 +35,7 @@
35
35
  #++
36
36
 
37
37
  require 'hexapdf/type/font_simple'
38
+ require 'hexapdf/font/true_type_wrapper'
38
39
 
39
40
  module HexaPDF
40
41
  module Type
@@ -45,6 +46,19 @@ module HexaPDF
45
46
  define_field :Subtype, type: Symbol, required: true, default: :TrueType
46
47
  define_field :BaseFont, type: Symbol, required: true
47
48
 
49
+ # Overrides the default to provide a font wrapper in case none is set and a complete TrueType
50
+ # is embedded.
51
+ #
52
+ # See: Font#font_wrapper
53
+ def font_wrapper
54
+ if (tmp = super)
55
+ tmp
56
+ elsif (font_file = self.font_file) && self[:BaseFont].to_s !~ /\A[A-Z]{6}\+/
57
+ font = HexaPDF::Font::TrueType::Font.new(StringIO.new(font_file.stream))
58
+ @font_wrapper = HexaPDF::Font::TrueTypeWrapper.new(document, font, subset: true)
59
+ end
60
+ end
61
+
48
62
  private
49
63
 
50
64
  def perform_validation
@@ -179,7 +179,7 @@ module HexaPDF
179
179
  # Due to a bug in Adobe Acrobat, the Catalog may not be in an object stream if the
180
180
  # document is encrypted
181
181
  if obj.nil? || obj.null? || obj.gen != 0 || obj.kind_of?(Stream) || obj == encrypt_dict ||
182
- (encrypt_dict && obj.type == :Catalog) ||
182
+ obj.type == :Catalog ||
183
183
  obj.type == :Sig || obj.type == :DocTimeStamp ||
184
184
  (obj.respond_to?(:key?) && obj.key?(:ByteRange) && obj.key?(:Contents))
185
185
  delete_object(objects[index])
@@ -220,7 +220,7 @@ module HexaPDF
220
220
 
221
221
  # Returns the container with the to-be-stored objects.
222
222
  def objects
223
- @objects ||=
223
+ @objects ||=
224
224
  begin
225
225
  @objects = {}
226
226
  parse_stream
@@ -128,7 +128,7 @@ module HexaPDF
128
128
  node, dir = first ? [first, :Next] : [last, :Prev]
129
129
  node = node[dir] while node.key?(dir)
130
130
  self[dir == :Next ? :Last : :First] = node
131
- elsif !first && !last && self[:Count]
131
+ elsif !first && !last && self[:Count] && self[:Count] != 0
132
132
  yield('Outline dictionary key /Count set but no items exist', true)
133
133
  delete(:Count)
134
134
  end
@@ -267,6 +267,7 @@ module HexaPDF
267
267
  raise ArgumentError, "Page rotation has to be multiple of 90 degrees"
268
268
  end
269
269
 
270
+ # /Rotate and therefore cw_angle is angle in clockwise orientation
270
271
  cw_angle = (self[:Rotate] - angle) % 360
271
272
 
272
273
  if flatten
@@ -274,27 +275,41 @@ module HexaPDF
274
275
  return if cw_angle == 0
275
276
 
276
277
  matrix = case cw_angle
277
- when 90
278
- HexaPDF::Content::TransformationMatrix.new(0, -1, 1, 0)
279
- when 180
280
- HexaPDF::Content::TransformationMatrix.new(-1, 0, 0, -1)
281
- when 270
282
- HexaPDF::Content::TransformationMatrix.new(0, 1, -1, 0)
278
+ when 90 then Content::TransformationMatrix.new(0, -1, 1, 0, -box.bottom, box.right)
279
+ when 180 then Content::TransformationMatrix.new(-1, 0, 0, -1, box.right, box.top)
280
+ when 270 then Content::TransformationMatrix.new(0, 1, -1, 0, box.top, -box.left)
283
281
  end
284
282
 
285
- [:MediaBox, :CropBox, :BleedBox, :TrimBox, :ArtBox].each do |box_name|
286
- next unless key?(box_name)
287
- box = self[box_name]
283
+ rotate_box = lambda do |box|
288
284
  llx, lly, urx, ury = \
289
285
  case cw_angle
290
- when 90
291
- [box.right, box.bottom, box.left, box.top]
292
- when 180
293
- [box.right, box.top, box.left, box.bottom]
294
- when 270
295
- [box.left, box.top, box.right, box.bottom]
286
+ when 90 then [box.right, box.bottom, box.left, box.top]
287
+ when 180 then [box.right, box.top, box.left, box.bottom]
288
+ when 270 then [box.left, box.top, box.right, box.bottom]
296
289
  end
297
- self[box_name].value = matrix.evaluate(llx, lly).concat(matrix.evaluate(urx, ury))
290
+ box.value.replace(matrix.evaluate(llx, lly).concat(matrix.evaluate(urx, ury)))
291
+ end
292
+
293
+ [:MediaBox, :CropBox, :BleedBox, :TrimBox, :ArtBox].each do |box_name|
294
+ next unless key?(box_name)
295
+ rotate_box.call(self[box_name])
296
+ end
297
+
298
+ each_annotation do |annot|
299
+ rotate_box.call(annot[:Rect])
300
+ if (quad_points = annot[:QuadPoints])
301
+ quad_points = quad_points.value if quad_points.respond_to?(:value)
302
+ result = []
303
+ quad_points.each_slice(2) {|x, y| result.concat(matrix.evaluate(x, y)) }
304
+ quad_points.replace(result)
305
+ end
306
+ if (appearance = annot.appearance)
307
+ appearance[:Matrix] = matrix.dup.premultiply(*appearance[:Matrix].value).to_a
308
+ end
309
+ if annot[:Subtype] == :Widget
310
+ app_ch = annot[:MK] ||= document.wrap({}, type: :XXAppearanceCharacteristics)
311
+ app_ch[:R] = (app_ch[:R] + 360 - cw_angle) % 360
312
+ end
298
313
  end
299
314
 
300
315
  before_contents = document.add({}, stream: " q #{matrix.to_a.join(' ')} cm ")
@@ -519,15 +534,15 @@ module HexaPDF
519
534
  # If an annotation is a form field widget, only the widget will be deleted but not the form
520
535
  # field itself.
521
536
  def flatten_annotations(annotations = self[:Annots])
522
- return [] unless key?(:Annots)
537
+ not_flattened = (annotations || []).to_ary
538
+ return not_flattened unless key?(:Annots)
523
539
 
524
- not_flattened = annotations.to_ary
525
540
  annotations = not_flattened & self[:Annots] if annotations != self[:Annots]
526
541
  return not_flattened if annotations.empty?
527
542
 
528
543
  canvas = self.canvas(type: :overlay)
529
- canvas.save_graphics_state
530
544
  if (pos = canvas.graphics_state.ctm.evaluate(0, 0)) != [0, 0]
545
+ canvas.save_graphics_state
531
546
  canvas.translate(-pos[0], -pos[1])
532
547
  end
533
548
 
@@ -546,36 +561,31 @@ module HexaPDF
546
561
 
547
562
  rect = annotation[:Rect]
548
563
  box = appearance.box
549
- matrix = appearance[:Matrix]
550
-
551
- # Adjust position based on matrix
552
- pos = [rect.left - matrix[4], rect.bottom - matrix[5]]
553
-
554
- # In case of a rotation we need to counter the default translation in #xobject by adding
555
- # box.left and box.bottom, and then translate the origin for the rotation
556
- angle = (-Math.atan2(matrix[2], matrix[0]) * 180 / Math::PI).to_i
557
- case angle
558
- when 0
559
- # Nothing to do, no rotation
560
- when 90
561
- pos[0] += box.top + box.left
562
- pos[1] += -box.left + box.bottom
563
- when -90
564
- pos[0] += -box.bottom + box.left
565
- pos[1] += box.right + box.bottom
566
- when 180, -180
567
- pos[0] += box.right + box.left
568
- pos[1] += box.top + box.bottom
569
- else
570
- not_flattened << annotation
571
- next
572
- end
573
564
 
574
- width, height = (angle.abs == 90 ? [rect.height, rect.width] : [rect.width, rect.height])
575
- canvas.xobject(appearance, at: pos, width: width, height: height)
565
+ # PDF1.7 12.5.5 algorithm
566
+ # Step a) Calculate smallest rectangle containing transformed bounding box
567
+ matrix = HexaPDF::Content::TransformationMatrix.new(*appearance[:Matrix].value)
568
+ llx, lly = matrix.evaluate(box.left, box.bottom)
569
+ ulx, uly = matrix.evaluate(box.left, box.top)
570
+ lrx, lry = matrix.evaluate(box.right, box.bottom)
571
+ left, right = [llx, ulx, lrx, lrx + (ulx - llx)].minmax
572
+ bottom, top = [lly, uly, lry, lry + (uly - lly)].minmax
573
+
574
+ # Step b) Fit calculated rectangle to annotation rectangle by translating/scaling
575
+ a = HexaPDF::Content::TransformationMatrix.new
576
+ a.translate(rect.left - left, rect.bottom - bottom)
577
+ a.scale(rect.width.fdiv(right - left), rect.height.fdiv(top - bottom))
578
+
579
+ # Step c) Premultiply form matrix - done implicitly when drawing the XObject
580
+
581
+ canvas.transform(*a) do
582
+ # Use [box.left, box.bottom] to counter default translation in #xobject since that
583
+ # is already taken care of in matrix a
584
+ canvas.xobject(appearance, at: [box.left, box.bottom])
585
+ end
576
586
  to_delete << annotation
577
587
  end
578
- canvas.restore_graphics_state
588
+ canvas.restore_graphics_state unless pos == [0, 0]
579
589
 
580
590
  to_delete.each do |annotation|
581
591
  if annotation[:Subtype] == :Widget
data/lib/hexapdf/type.rb CHANGED
@@ -72,7 +72,6 @@ module HexaPDF
72
72
  autoload(:FontType3, 'hexapdf/type/font_type3')
73
73
  autoload(:IconFit, 'hexapdf/type/icon_fit')
74
74
  autoload(:AcroForm, 'hexapdf/type/acro_form')
75
- autoload(:Signature, 'hexapdf/type/signature')
76
75
  autoload(:Outline, 'hexapdf/type/outline')
77
76
  autoload(:OutlineItem, 'hexapdf/type/outline_item')
78
77
  autoload(:PageLabel, 'hexapdf/type/page_label')
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '0.27.0'
40
+ VERSION = '0.29.0'
41
41
 
42
42
  end
@@ -114,7 +114,7 @@ module HexaPDF
114
114
  @document.catalog[:Version] = @document.version.to_sym
115
115
  end
116
116
  @document.revisions.each do |rev|
117
- rev.each_modified_object {|obj| revision.send(:add_without_check, obj) }
117
+ rev.each_modified_object(all: true) {|obj| revision.send(:add_without_check, obj) }
118
118
  end
119
119
 
120
120
  write_revision(revision, parser.startxref_offset)
@@ -135,8 +135,7 @@ module HexaPDF
135
135
 
136
136
  revision = @document.revisions.add
137
137
  @document.revisions.all[0..-2].each do |rev|
138
- rev.each_modified_object {|obj| revision.send(:add_without_check, obj) }
139
- rev.reset_objects
138
+ rev.each_modified_object(delete: true) {|obj| revision.send(:add_without_check, obj) }
140
139
  end
141
140
  @document.revisions.merge(-2..-1)
142
141
  end
@@ -934,6 +934,11 @@ describe HexaPDF::Content::Canvas do
934
934
  [:restore_graphics_state]])
935
935
  end
936
936
 
937
+ it "correctly serializes the form when no transformation is needed" do
938
+ @canvas.image(@form, at: [100, 50])
939
+ assert_operators(@page.contents, [[:paint_xobject, [:XO1]]])
940
+ end
941
+
937
942
  it "doesn't do anything if the form's width or height is zero" do
938
943
  @form[:BBox] = [100, 50, 100, 200]
939
944
  @canvas.xobject(@form, at: [0, 0])
@@ -6,7 +6,7 @@ module HexaPDF
6
6
  class Certificates
7
7
 
8
8
  def ca_key
9
- @ca_key ||= OpenSSL::PKey::RSA.new(512)
9
+ @ca_key ||= OpenSSL::PKey::RSA.new(2048)
10
10
  end
11
11
 
12
12
  def ca_certificate
@@ -36,13 +36,17 @@ module HexaPDF
36
36
  end
37
37
 
38
38
  def signer_key
39
- @signer_key ||= OpenSSL::PKey::RSA.new(512)
39
+ @signer_key ||= OpenSSL::PKey::RSA.new(2048)
40
+ end
41
+
42
+ def dsa_signer_key
43
+ @dsa_signer_key ||= OpenSSL::PKey::DSA.new(2048)
40
44
  end
41
45
 
42
46
  def signer_certificate
43
47
  @signer_certificate ||=
44
48
  begin
45
- name = OpenSSL::X509::Name.parse('/CN=signer/DC=gettalong')
49
+ name = OpenSSL::X509::Name.parse('/CN=RSA signer/DC=gettalong')
46
50
 
47
51
  signer_cert = OpenSSL::X509::Certificate.new
48
52
  signer_cert.serial = 2
@@ -65,6 +69,30 @@ module HexaPDF
65
69
  end
66
70
  end
67
71
 
72
+ def dsa_signer_certificate
73
+ @dsa_signer_certificate ||=
74
+ begin
75
+ signer_cert = OpenSSL::X509::Certificate.new
76
+ signer_cert.serial = 3
77
+ signer_cert.version = 2
78
+ signer_cert.not_before = Time.now - 86400
79
+ signer_cert.not_after = Time.now + 86400
80
+ signer_cert.public_key = dsa_signer_key.public_key
81
+ signer_cert.subject = OpenSSL::X509::Name.parse('/CN=DSA signer/DC=gettalong')
82
+ signer_cert.issuer = ca_certificate.subject
83
+
84
+ extension_factory = OpenSSL::X509::ExtensionFactory.new
85
+ extension_factory.subject_certificate = signer_cert
86
+ extension_factory.issuer_certificate = ca_certificate
87
+ signer_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
88
+ signer_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE'))
89
+ signer_cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature'))
90
+ signer_cert.sign(ca_key, OpenSSL::Digest.new('SHA1'))
91
+
92
+ signer_cert
93
+ end
94
+ end
95
+
68
96
  def timestamp_certificate
69
97
  @timestamp_certificate ||=
70
98
  begin
@@ -85,7 +113,8 @@ module HexaPDF
85
113
  signer_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
86
114
  signer_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE'))
87
115
  signer_cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature'))
88
- signer_cert.add_extension(extension_factory.create_extension('extendedKeyUsage', 'timeStamping', true))
116
+ signer_cert.add_extension(extension_factory.create_extension('extendedKeyUsage',
117
+ 'timeStamping', true))
89
118
  signer_cert.sign(ca_key, OpenSSL::Digest.new('SHA1'))
90
119
 
91
120
  signer_cert
@@ -117,6 +146,7 @@ module HexaPDF
117
146
  end
118
147
  Thread.new { @tsa_server.start }
119
148
  end
149
+
120
150
  end
121
151
 
122
152
  end