hexapdf 0.27.0 → 0.29.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +100 -11
- data/examples/019-acro_form.rb +14 -3
- data/examples/023-images.rb +30 -0
- data/examples/024-digital-signatures.rb +23 -0
- data/lib/hexapdf/cli/info.rb +5 -1
- data/lib/hexapdf/cli/inspect.rb +2 -2
- data/lib/hexapdf/cli/split.rb +2 -2
- data/lib/hexapdf/configuration.rb +13 -14
- data/lib/hexapdf/content/canvas.rb +8 -3
- data/lib/hexapdf/dictionary.rb +1 -5
- data/lib/hexapdf/dictionary_fields.rb +6 -2
- data/lib/hexapdf/digital_signature/cms_handler.rb +137 -0
- data/lib/hexapdf/digital_signature/handler.rb +138 -0
- data/lib/hexapdf/digital_signature/pkcs1_handler.rb +96 -0
- data/lib/hexapdf/{type → digital_signature}/signature.rb +3 -8
- data/lib/hexapdf/digital_signature/signatures.rb +210 -0
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +317 -0
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +308 -0
- data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +148 -0
- data/lib/hexapdf/digital_signature/signing.rb +101 -0
- data/lib/hexapdf/{type/signature → digital_signature}/verification_result.rb +37 -41
- data/lib/hexapdf/digital_signature.rb +56 -0
- data/lib/hexapdf/document.rb +27 -24
- data/lib/hexapdf/encryption/standard_security_handler.rb +2 -1
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/importer.rb +32 -27
- data/lib/hexapdf/layout/list_box.rb +1 -5
- data/lib/hexapdf/object.rb +5 -0
- data/lib/hexapdf/parser.rb +13 -0
- data/lib/hexapdf/revision.rb +15 -12
- data/lib/hexapdf/revisions.rb +4 -0
- data/lib/hexapdf/tokenizer.rb +14 -8
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +174 -128
- data/lib/hexapdf/type/acro_form/button_field.rb +5 -3
- data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/field.rb +11 -5
- data/lib/hexapdf/type/acro_form/form.rb +33 -7
- data/lib/hexapdf/type/acro_form/signature_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/text_field.rb +12 -2
- data/lib/hexapdf/type/annotations/widget.rb +3 -0
- data/lib/hexapdf/type/font_true_type.rb +14 -0
- data/lib/hexapdf/type/object_stream.rb +2 -2
- data/lib/hexapdf/type/outline.rb +1 -1
- data/lib/hexapdf/type/page.rb +56 -46
- data/lib/hexapdf/type.rb +0 -1
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +2 -3
- data/test/hexapdf/content/test_canvas.rb +5 -0
- data/test/hexapdf/{type/signature → digital_signature}/common.rb +34 -4
- data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
- data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
- data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
- data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
- data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
- data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
- data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
- data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
- data/test/hexapdf/digital_signature/test_signing.rb +53 -0
- data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
- data/test/hexapdf/document/test_pages.rb +2 -2
- data/test/hexapdf/encryption/test_aes.rb +1 -1
- data/test/hexapdf/filter/test_predictor.rb +0 -1
- data/test/hexapdf/layout/test_box.rb +2 -1
- data/test/hexapdf/layout/test_column_box.rb +1 -1
- data/test/hexapdf/layout/test_list_box.rb +1 -1
- data/test/hexapdf/test_dictionary_fields.rb +2 -1
- data/test/hexapdf/test_document.rb +3 -9
- data/test/hexapdf/test_importer.rb +13 -6
- data/test/hexapdf/test_parser.rb +17 -0
- data/test/hexapdf/test_revision.rb +15 -14
- data/test/hexapdf/test_revisions.rb +43 -0
- data/test/hexapdf/test_stream.rb +1 -1
- data/test/hexapdf/test_tokenizer.rb +3 -4
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +135 -56
- data/test/hexapdf/type/acro_form/test_button_field.rb +6 -1
- data/test/hexapdf/type/acro_form/test_choice_field.rb +4 -0
- data/test/hexapdf/type/acro_form/test_field.rb +4 -4
- data/test/hexapdf/type/acro_form/test_form.rb +18 -0
- data/test/hexapdf/type/acro_form/test_signature_field.rb +4 -0
- data/test/hexapdf/type/acro_form/test_text_field.rb +13 -0
- data/test/hexapdf/type/test_font_true_type.rb +20 -0
- data/test/hexapdf/type/test_object_stream.rb +2 -1
- data/test/hexapdf/type/test_outline.rb +3 -0
- data/test/hexapdf/type/test_page.rb +67 -30
- data/test/hexapdf/type/test_page_tree_node.rb +4 -2
- metadata +69 -16
- data/lib/hexapdf/document/signatures.rb +0 -546
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
- data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
- data/lib/hexapdf/type/signature/handler.rb +0 -140
- 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.
|
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.
|
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
|
-
|
266
|
+
appearance_generator_class.new(widget).create_push_button_appearances
|
265
267
|
end
|
266
268
|
end
|
267
269
|
end
|
@@ -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
|
-
#
|
257
|
-
#
|
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:
|
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
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
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
|
-
|
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
|
@@ -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
|
-
|
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
|
-
|
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
|
data/lib/hexapdf/type/outline.rb
CHANGED
@@ -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
|
data/lib/hexapdf/type/page.rb
CHANGED
@@ -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
|
-
|
279
|
-
when
|
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
|
-
|
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
|
-
|
292
|
-
when
|
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
|
-
|
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
|
-
|
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
|
-
|
575
|
-
|
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')
|
data/lib/hexapdf/version.rb
CHANGED
data/lib/hexapdf/writer.rb
CHANGED
@@ -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(
|
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(
|
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',
|
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
|