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.
- 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
|