hexapdf 0.14.2 → 0.15.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +96 -0
- data/lib/hexapdf/cli/form.rb +30 -8
- data/lib/hexapdf/configuration.rb +19 -4
- data/lib/hexapdf/content/canvas.rb +1 -0
- data/lib/hexapdf/dictionary.rb +3 -0
- data/lib/hexapdf/dictionary_fields.rb +1 -1
- data/lib/hexapdf/encryption/security_handler.rb +7 -2
- data/lib/hexapdf/encryption/standard_security_handler.rb +12 -0
- data/lib/hexapdf/error.rb +4 -3
- data/lib/hexapdf/filter.rb +1 -0
- data/lib/hexapdf/filter/crypt.rb +60 -0
- data/lib/hexapdf/font/true_type/subsetter.rb +5 -1
- data/lib/hexapdf/font/type1/afm_parser.rb +2 -1
- data/lib/hexapdf/parser.rb +46 -14
- data/lib/hexapdf/pdf_array.rb +3 -0
- data/lib/hexapdf/revision.rb +16 -0
- data/lib/hexapdf/serializer.rb +10 -3
- data/lib/hexapdf/tokenizer.rb +44 -3
- data/lib/hexapdf/type/acro_form.rb +1 -0
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +32 -17
- data/lib/hexapdf/type/acro_form/button_field.rb +8 -4
- data/lib/hexapdf/type/acro_form/field.rb +1 -0
- data/lib/hexapdf/type/acro_form/form.rb +37 -0
- data/lib/hexapdf/type/acro_form/signature_field.rb +223 -0
- data/lib/hexapdf/type/annotation.rb +13 -9
- data/lib/hexapdf/type/annotations/widget.rb +3 -1
- data/lib/hexapdf/type/font_descriptor.rb +9 -2
- data/lib/hexapdf/type/page.rb +81 -0
- data/lib/hexapdf/type/resources.rb +4 -0
- data/lib/hexapdf/type/xref_stream.rb +7 -0
- data/lib/hexapdf/utils/graphics_helpers.rb +4 -4
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/test_canvas.rb +21 -0
- data/test/hexapdf/encryption/test_security_handler.rb +15 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +26 -0
- data/test/hexapdf/filter/test_crypt.rb +21 -0
- data/test/hexapdf/font/true_type/test_subsetter.rb +2 -0
- data/test/hexapdf/font/type1/test_afm_parser.rb +5 -0
- data/test/hexapdf/test_dictionary_fields.rb +7 -0
- data/test/hexapdf/test_parser.rb +82 -2
- data/test/hexapdf/test_revision.rb +21 -0
- data/test/hexapdf/test_serializer.rb +10 -0
- data/test/hexapdf/test_tokenizer.rb +50 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +24 -3
- data/test/hexapdf/type/acro_form/test_button_field.rb +13 -7
- data/test/hexapdf/type/acro_form/test_field.rb +5 -0
- data/test/hexapdf/type/acro_form/test_form.rb +46 -2
- data/test/hexapdf/type/acro_form/test_signature_field.rb +38 -0
- data/test/hexapdf/type/annotations/test_widget.rb +2 -0
- data/test/hexapdf/type/test_annotation.rb +20 -10
- data/test/hexapdf/type/test_font_descriptor.rb +7 -0
- data/test/hexapdf/type/test_page.rb +187 -49
- data/test/hexapdf/type/test_resources.rb +6 -0
- data/test/hexapdf/type/test_xref_stream.rb +7 -0
- data/test/hexapdf/utils/test_graphics_helpers.rb +8 -0
- metadata +6 -2
data/lib/hexapdf/pdf_array.rb
CHANGED
@@ -65,6 +65,9 @@ module HexaPDF
|
|
65
65
|
# * Returns the native Ruby object for values with class HexaPDF::Object. However, all
|
66
66
|
# subclasses of HexaPDF::Object are returned as is (it makes no sense, for example, to return
|
67
67
|
# the hash that describes the Catalog instead of the Catalog object).
|
68
|
+
#
|
69
|
+
# Note: Hash or Array values will always be returned as-is, i.e. not wrapped with Dictionary or
|
70
|
+
# PDFArray.
|
68
71
|
def [](arg1, arg2 = nil)
|
69
72
|
data = arg2 ? value[arg1, arg2] : value[arg1]
|
70
73
|
return if data.nil?
|
data/lib/hexapdf/revision.rb
CHANGED
@@ -158,6 +158,22 @@ module HexaPDF
|
|
158
158
|
add_without_check(obj)
|
159
159
|
end
|
160
160
|
|
161
|
+
# :call-seq:
|
162
|
+
# revision.update(obj) -> obj or nil
|
163
|
+
#
|
164
|
+
# Updates the stored object to point to the given HexaPDF::Object wrapper, returning the object
|
165
|
+
# if successful or +nil+ otherwise.
|
166
|
+
#
|
167
|
+
# If +obj+ isn't stored in this revision or the stored object doesn't contain the same
|
168
|
+
# HexaPDF::PDFData object as the given object, nothing is done.
|
169
|
+
#
|
170
|
+
# This method should only be used if the wrong wrapper class is stored (e.g. because
|
171
|
+
# auto-detection didn't or couldn't work correctly) and thus needs correction.
|
172
|
+
def update(obj)
|
173
|
+
return nil if object(obj)&.data != obj.data
|
174
|
+
add_without_check(obj)
|
175
|
+
end
|
176
|
+
|
161
177
|
# :call-seq:
|
162
178
|
# revision.delete(ref, mark_as_free: true)
|
163
179
|
# revision.delete(oid, mark_as_free: true)
|
data/lib/hexapdf/serializer.rb
CHANGED
@@ -191,7 +191,13 @@ module HexaPDF
|
|
191
191
|
#
|
192
192
|
# See: PDF1.7 s7.3.3
|
193
193
|
def serialize_float(obj)
|
194
|
-
-0.0001 < obj && obj < 0.0001 && obj != 0
|
194
|
+
if -0.0001 < obj && obj < 0.0001 && obj != 0
|
195
|
+
sprintf("%.6f", obj)
|
196
|
+
elsif obj.finite?
|
197
|
+
obj.round(6).to_s
|
198
|
+
else
|
199
|
+
raise HexaPDF::Error, "Can't serialize special floating point number #{obj}"
|
200
|
+
end
|
195
201
|
end
|
196
202
|
|
197
203
|
# The regexp matches all characters that need to be escaped and the substs hash contains the
|
@@ -343,6 +349,7 @@ module HexaPDF
|
|
343
349
|
@io << data.freeze
|
344
350
|
end
|
345
351
|
@io << "\nendstream"
|
352
|
+
@in_object = false
|
346
353
|
|
347
354
|
nil
|
348
355
|
else
|
@@ -350,12 +357,12 @@ module HexaPDF
|
|
350
357
|
obj.value[:Length] = data.size
|
351
358
|
|
352
359
|
str = serialize_hash(obj.value)
|
360
|
+
@in_object = false
|
361
|
+
|
353
362
|
str << "stream\n"
|
354
363
|
str << data
|
355
364
|
str << "\nendstream"
|
356
365
|
end
|
357
|
-
ensure
|
358
|
-
@in_object = false
|
359
366
|
end
|
360
367
|
|
361
368
|
# Invokes the correct serialization method for the object.
|
data/lib/hexapdf/tokenizer.rb
CHANGED
@@ -73,11 +73,15 @@ module HexaPDF
|
|
73
73
|
# The IO object from the tokens are read.
|
74
74
|
attr_reader :io
|
75
75
|
|
76
|
-
# Creates a new tokenizer.
|
77
|
-
|
76
|
+
# Creates a new tokenizer for the given IO stream.
|
77
|
+
#
|
78
|
+
# If +on_correctable_error+ is set to an object responding to +call(msg, pos)+, errors for
|
79
|
+
# correctable situations are only raised if the return value of calling the object is +true+.
|
80
|
+
def initialize(io, on_correctable_error: nil)
|
78
81
|
@io = io
|
79
82
|
@ss = StringScanner.new(''.b)
|
80
83
|
@original_pos = -1
|
84
|
+
@on_correctable_error = on_correctable_error || proc { false }
|
81
85
|
self.pos = 0
|
82
86
|
end
|
83
87
|
|
@@ -180,7 +184,8 @@ module HexaPDF
|
|
180
184
|
end
|
181
185
|
else
|
182
186
|
unless allow_keyword
|
183
|
-
|
187
|
+
maybe_raise("Invalid object, got token #{token}", force: token !~ /^-?(nan|inf)$/i)
|
188
|
+
token = 0
|
184
189
|
end
|
185
190
|
end
|
186
191
|
end
|
@@ -188,6 +193,28 @@ module HexaPDF
|
|
188
193
|
token
|
189
194
|
end
|
190
195
|
|
196
|
+
# Returns a single integer or keyword token read from the current position and advances the scan
|
197
|
+
# pointer. If the current position doesn't contain such a token, +nil+ is returned without
|
198
|
+
# advancing the scan pointer. The value +NO_MORE_TOKENS+ is returned if there are no more tokens
|
199
|
+
# available.
|
200
|
+
#
|
201
|
+
# Initial runs of whitespace characters are ignored.
|
202
|
+
#
|
203
|
+
# Note: This is a special method meant for use with reconstructing the cross-reference table!
|
204
|
+
def next_integer_or_keyword
|
205
|
+
skip_whitespace
|
206
|
+
byte = @ss.string.getbyte(@ss.pos) || -1
|
207
|
+
if 48 <= byte && byte <= 57
|
208
|
+
parse_number
|
209
|
+
elsif (97 <= byte && byte <= 122) || (65 <= byte && byte <= 90)
|
210
|
+
parse_keyword
|
211
|
+
elsif byte == -1 # we reached the end of the file
|
212
|
+
NO_MORE_TOKENS
|
213
|
+
else
|
214
|
+
nil
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
191
218
|
# Reads the byte (an integer) at the current position and advances the scan pointer.
|
192
219
|
def next_byte
|
193
220
|
prepare_string_scanner(1)
|
@@ -406,6 +433,20 @@ module HexaPDF
|
|
406
433
|
true
|
407
434
|
end
|
408
435
|
|
436
|
+
# Calls the @on_correctable_error callable object with the given message and the current
|
437
|
+
# position. If the returned value is +true+, raises a HexaPDF::MalformedPDFError. Otherwise the
|
438
|
+
# error is corrected (by the caller) and tokenization continues.
|
439
|
+
#
|
440
|
+
# If the option +force+ is used, the callable object is not called and the error is raised
|
441
|
+
# immediately.
|
442
|
+
def maybe_raise(msg, force: false)
|
443
|
+
if force || @on_correctable_error.call(msg, pos)
|
444
|
+
error = HexaPDF::MalformedPDFError.new(msg, pos: pos)
|
445
|
+
error.set_backtrace(caller(1))
|
446
|
+
raise error
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
409
450
|
end
|
410
451
|
|
411
452
|
end
|
@@ -48,6 +48,7 @@ module HexaPDF
|
|
48
48
|
autoload(:TextField, 'hexapdf/type/acro_form/text_field')
|
49
49
|
autoload(:ButtonField, 'hexapdf/type/acro_form/button_field')
|
50
50
|
autoload(:ChoiceField, 'hexapdf/type/acro_form/choice_field')
|
51
|
+
autoload(:SignatureField, 'hexapdf/type/acro_form/signature_field')
|
51
52
|
|
52
53
|
autoload(:AppearanceGenerator, 'hexapdf/type/acro_form/appearance_generator')
|
53
54
|
|
@@ -120,7 +120,7 @@ module HexaPDF
|
|
120
120
|
# widget.marker_style(style: :cross)
|
121
121
|
# # => no visible rectangle, gray background, cross mark when checked
|
122
122
|
def create_check_box_appearances
|
123
|
-
unless @widget.
|
123
|
+
unless @widget.appearance_dict&.normal_appearance&.value&.size == 2
|
124
124
|
raise HexaPDF::Error, "Widget of check box doesn't define name for on state"
|
125
125
|
end
|
126
126
|
border_style = @widget.border_style
|
@@ -128,11 +128,11 @@ module HexaPDF
|
|
128
128
|
|
129
129
|
rect = update_widget(@field[:V], border_width)
|
130
130
|
|
131
|
-
off_form = @widget.
|
131
|
+
off_form = @widget.appearance_dict.normal_appearance[:Off] =
|
132
132
|
@document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, rect.width, rect.height]})
|
133
133
|
apply_background_and_border(border_style, off_form.canvas)
|
134
134
|
|
135
|
-
on_form = @widget.
|
135
|
+
on_form = @widget.appearance_dict.normal_appearance[@field.check_box_on_name] =
|
136
136
|
@document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, rect.width, rect.height]})
|
137
137
|
canvas = on_form.canvas
|
138
138
|
apply_background_and_border(border_style, canvas)
|
@@ -169,22 +169,22 @@ module HexaPDF
|
|
169
169
|
# widget.marker_style(style: :circle, size: 0, color: 0)
|
170
170
|
# # => default appearance
|
171
171
|
def create_radio_button_appearances
|
172
|
-
unless @widget.
|
172
|
+
unless @widget.appearance_dict&.normal_appearance&.value&.size == 2
|
173
173
|
raise HexaPDF::Error, "Widget of radio button doesn't define unique name for on state"
|
174
174
|
end
|
175
175
|
|
176
|
-
on_name = (@widget.
|
176
|
+
on_name = (@widget.appearance_dict.normal_appearance.value.keys - [:Off]).first
|
177
177
|
border_style = @widget.border_style
|
178
178
|
marker_style = @widget.marker_style
|
179
179
|
|
180
180
|
rect = update_widget(@field[:V] == on_name ? on_name : :Off, border_style.width)
|
181
181
|
|
182
|
-
off_form = @widget.
|
182
|
+
off_form = @widget.appearance_dict.normal_appearance[:Off] =
|
183
183
|
@document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, rect.width, rect.height]})
|
184
184
|
apply_background_and_border(border_style, off_form.canvas,
|
185
185
|
circular: marker_style.style == :circle)
|
186
186
|
|
187
|
-
on_form = @widget.
|
187
|
+
on_form = @widget.appearance_dict.normal_appearance[on_name] =
|
188
188
|
@document.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, rect.width, rect.height]})
|
189
189
|
canvas = on_form.canvas
|
190
190
|
apply_background_and_border(border_style, canvas,
|
@@ -219,17 +219,8 @@ module HexaPDF
|
|
219
219
|
#
|
220
220
|
# Note: Multiline, comb and rich text fields are currently not supported!
|
221
221
|
def create_text_appearances
|
222
|
-
font_name, font_size = @field.parse_default_appearance_string
|
223
222
|
default_resources = @document.acro_form.default_resources
|
224
|
-
font = default_resources
|
225
|
-
unless font
|
226
|
-
fallback_font_name, fallback_font_options = @document.config['acro_form.fallback_font']
|
227
|
-
if fallback_font_name
|
228
|
-
font = @document.fonts.add(fallback_font_name, **(fallback_font_options || {}))
|
229
|
-
else
|
230
|
-
raise(HexaPDF::Error, "Font #{font_name} of the AcroForm's default resources not usable")
|
231
|
-
end
|
232
|
-
end
|
223
|
+
font, font_size = retrieve_font_information(default_resources)
|
233
224
|
style = HexaPDF::Layout::Style.new(font: font)
|
234
225
|
border_style = @widget.border_style
|
235
226
|
padding = [1, border_style.width].max
|
@@ -245,6 +236,9 @@ module HexaPDF
|
|
245
236
|
end
|
246
237
|
|
247
238
|
form = (@widget[:AP] ||= {})[:N] ||= @document.add({Type: :XObject, Subtype: :Form})
|
239
|
+
# Wrap existing object in Form class in case the PDF writer didn't include the /Subtype
|
240
|
+
# key; we can do this since we know this has to be a Form object
|
241
|
+
form = @document.wrap(form, type: :XObject, subtype: :Form) unless form[:Subtype] == :Form
|
248
242
|
form.value.replace({Type: :XObject, Subtype: :Form, BBox: [0, 0, rect.width, rect.height]})
|
249
243
|
form.contents = ''
|
250
244
|
form[:Resources] = HexaPDF::Object.deep_copy(default_resources)
|
@@ -479,6 +473,27 @@ module HexaPDF
|
|
479
473
|
end
|
480
474
|
end
|
481
475
|
|
476
|
+
# Returns the font wrapper and font size to be used for a variable text field.
|
477
|
+
def retrieve_font_information(resources)
|
478
|
+
font_name, font_size = @field.parse_default_appearance_string
|
479
|
+
font_object = resources.font(font_name) rescue nil
|
480
|
+
font = font_object&.font_wrapper
|
481
|
+
unless font
|
482
|
+
fallback_font = @document.config['acro_form.fallback_font']
|
483
|
+
fallback_font_name, fallback_font_options = if fallback_font.respond_to?(:call)
|
484
|
+
fallback_font.call(@field, font_object)
|
485
|
+
else
|
486
|
+
fallback_font
|
487
|
+
end
|
488
|
+
if fallback_font_name
|
489
|
+
font = @document.fonts.add(fallback_font_name, **(fallback_font_options || {}))
|
490
|
+
else
|
491
|
+
raise(HexaPDF::Error, "Font #{font_name} of the AcroForm's default resources not usable")
|
492
|
+
end
|
493
|
+
end
|
494
|
+
[font, font_size]
|
495
|
+
end
|
496
|
+
|
482
497
|
# Calculates the font size for text fields based on the font and font size of the default
|
483
498
|
# appearance string, the annotation rectangle and the border style.
|
484
499
|
def calculate_font_size(font, font_size, rect, border_style)
|
@@ -184,7 +184,7 @@ module HexaPDF
|
|
184
184
|
#
|
185
185
|
# Defaults to :Yes if no other name could be determined.
|
186
186
|
def check_box_on_name
|
187
|
-
each_widget.to_a.first&.
|
187
|
+
each_widget.to_a.first&.appearance_dict&.normal_appearance&.value&.each_key&.
|
188
188
|
find {|key| key != :Off } || :Yes
|
189
189
|
end
|
190
190
|
|
@@ -192,7 +192,7 @@ module HexaPDF
|
|
192
192
|
# button.
|
193
193
|
def radio_button_values
|
194
194
|
each_widget.map do |widget|
|
195
|
-
widget.
|
195
|
+
widget.appearance_dict&.normal_appearance&.value&.each_key&.find {|key| key != :Off }
|
196
196
|
end.compact
|
197
197
|
end
|
198
198
|
|
@@ -233,7 +233,11 @@ module HexaPDF
|
|
233
233
|
def create_appearances(force: false)
|
234
234
|
appearance_generator_class = document.config.constantize('acro_form.appearance_generator')
|
235
235
|
each_widget do |widget|
|
236
|
-
|
236
|
+
normal_appearance = widget.appearance_dict&.normal_appearance
|
237
|
+
next if !force && normal_appearance &&
|
238
|
+
((!push_button? && normal_appearance.value.length == 2 &&
|
239
|
+
normal_appearance.value.each_value.all?(HexaPDF::Stream)) ||
|
240
|
+
(push_button? && normal_appearance.kind_of?(HexaPDF::Stream)))
|
237
241
|
if check_box?
|
238
242
|
appearance_generator_class.new(widget).create_check_box_appearances
|
239
243
|
elsif radio_button?
|
@@ -250,7 +254,7 @@ module HexaPDF
|
|
250
254
|
create_appearances
|
251
255
|
value = self[:V]
|
252
256
|
each_widget do |widget|
|
253
|
-
widget[:AS] = (widget.
|
257
|
+
widget[:AS] = (widget.appearance_dict&.normal_appearance&.key?(value) ? value : :Off)
|
254
258
|
end
|
255
259
|
end
|
256
260
|
|
@@ -331,6 +331,43 @@ module HexaPDF
|
|
331
331
|
end
|
332
332
|
end
|
333
333
|
|
334
|
+
# Flattens the whole interactive form or only the given fields, and returns the fields that
|
335
|
+
# couldn't be flattened.
|
336
|
+
#
|
337
|
+
# Flattening means making the appearance streams of the field widgets part of the respective
|
338
|
+
# page's content stream and removing the fields themselves.
|
339
|
+
#
|
340
|
+
# If the whole interactive form is flattened, the form object itself is also removed if all
|
341
|
+
# fields were flattened.
|
342
|
+
#
|
343
|
+
# The +create_appearances+ argument controls whether missing appearances should
|
344
|
+
# automatically be created.
|
345
|
+
#
|
346
|
+
# See: HexaPDF::Type::Page#flatten_annotations
|
347
|
+
def flatten(fields: nil, create_appearances: true)
|
348
|
+
remove_form = fields.nil?
|
349
|
+
fields ||= each_field.to_a
|
350
|
+
if create_appearances
|
351
|
+
fields.each {|field| field.create_appearances if field.respond_to?(:create_appearances) }
|
352
|
+
end
|
353
|
+
|
354
|
+
not_flattened = fields.map {|field| field.each_widget.to_a }.flatten
|
355
|
+
document.pages.each {|page| not_flattened = page.flatten_annotations(not_flattened) }
|
356
|
+
fields -= not_flattened.map(&:form_field)
|
357
|
+
|
358
|
+
fields.each do |field|
|
359
|
+
(field[:Parent]&.[](:Kids) || self[:Fields]).delete(field)
|
360
|
+
document.delete(field)
|
361
|
+
end
|
362
|
+
|
363
|
+
if remove_form && not_flattened.empty?
|
364
|
+
document.catalog.delete(:AcroForm)
|
365
|
+
document.delete(self)
|
366
|
+
end
|
367
|
+
|
368
|
+
not_flattened
|
369
|
+
end
|
370
|
+
|
334
371
|
private
|
335
372
|
|
336
373
|
# Helper method for bit field getter access.
|
@@ -0,0 +1,223 @@
|
|
1
|
+
# -*- encoding: utf-8; frozen_string_literal: true -*-
|
2
|
+
#
|
3
|
+
#--
|
4
|
+
# This file is part of HexaPDF.
|
5
|
+
#
|
6
|
+
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
7
|
+
# Copyright (C) 2014-2021 Thomas Leitner
|
8
|
+
#
|
9
|
+
# HexaPDF is free software: you can redistribute it and/or modify it
|
10
|
+
# under the terms of the GNU Affero General Public License version 3 as
|
11
|
+
# published by the Free Software Foundation with the addition of the
|
12
|
+
# following permission added to Section 15 as permitted in Section 7(a):
|
13
|
+
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
|
14
|
+
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
|
15
|
+
# INFRINGEMENT OF THIRD PARTY RIGHTS.
|
16
|
+
#
|
17
|
+
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
|
18
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
20
|
+
# License for more details.
|
21
|
+
#
|
22
|
+
# You should have received a copy of the GNU Affero General Public License
|
23
|
+
# along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
|
24
|
+
#
|
25
|
+
# The interactive user interfaces in modified source and object code
|
26
|
+
# versions of HexaPDF must display Appropriate Legal Notices, as required
|
27
|
+
# under Section 5 of the GNU Affero General Public License version 3.
|
28
|
+
#
|
29
|
+
# In accordance with Section 7(b) of the GNU Affero General Public
|
30
|
+
# License, a covered work must retain the producer line in every PDF that
|
31
|
+
# is created or manipulated using HexaPDF.
|
32
|
+
#
|
33
|
+
# If the GNU Affero General Public License doesn't fit your need,
|
34
|
+
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
35
|
+
#++
|
36
|
+
|
37
|
+
require 'hexapdf/type/acro_form/field'
|
38
|
+
require 'hexapdf/type/acro_form/appearance_generator'
|
39
|
+
|
40
|
+
module HexaPDF
|
41
|
+
module Type
|
42
|
+
module AcroForm
|
43
|
+
|
44
|
+
# AcroForm signature fields represent a digital signature.
|
45
|
+
#
|
46
|
+
# It serves two purposes: To visually display the signature and to hold the information of the
|
47
|
+
# digital signature itself.
|
48
|
+
#
|
49
|
+
# If the signature should not be visible, the associated widget annotation should have zero
|
50
|
+
# width and height; and/or the 'hidden' or 'no_view' flags of the annotation should be set.
|
51
|
+
#
|
52
|
+
# See: PDF1.7 s12.7.4.5
|
53
|
+
class SignatureField < Field
|
54
|
+
|
55
|
+
# A signature field lock dictionary specifies a set of form fields that should be locked
|
56
|
+
# once the associated signature field is signed.
|
57
|
+
#
|
58
|
+
# See: PDF1.7 s12.7.4.5
|
59
|
+
class LockDictionary < Dictionary
|
60
|
+
|
61
|
+
define_type :SigFieldLock
|
62
|
+
|
63
|
+
define_field :Type, type: Symbol, default: type
|
64
|
+
define_field :Action, type: Symbol, required: true,
|
65
|
+
allowed_values: [:All, :Include, :Exclude]
|
66
|
+
define_field :Fields, type: PDFArray
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def perform_validation #:nodoc:
|
71
|
+
if self[:Action] != :All && !key?(:Fields)
|
72
|
+
yield("The /Fields key of the signature lock dictionary is missing")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
# A seed value dictionary contains information that constrains the properties of a signature
|
79
|
+
# that is applied to the associated signature field.
|
80
|
+
#
|
81
|
+
# == Flags
|
82
|
+
#
|
83
|
+
# If a flag is set it means that the associated entry is a required constraint. Otherwise it
|
84
|
+
# is optional.
|
85
|
+
#
|
86
|
+
# The available flags are: filter, sub_filter, v, reasons, legal_attestation, add_rev_info
|
87
|
+
# and digest_method.
|
88
|
+
#
|
89
|
+
# See: PDF1.7 s12.7.4.5
|
90
|
+
class SeedValueDictionary < Dictionary
|
91
|
+
|
92
|
+
extend Utils::BitField
|
93
|
+
|
94
|
+
define_type :SV
|
95
|
+
|
96
|
+
define_field :Type, type: Symbol, default: type
|
97
|
+
define_field :Ff, type: Integer, default: 0
|
98
|
+
define_field :Filter, type: Symbol
|
99
|
+
define_field :SubFilter, type: PDFArray
|
100
|
+
define_field :DigestMethod, type: PDFArray, version: '1.7'
|
101
|
+
define_field :V, type: Float
|
102
|
+
define_field :Cert, type: :SVCert
|
103
|
+
define_field :Reasons, type: PDFArray
|
104
|
+
define_field :MDP, type: Dictionary, version: '1.6'
|
105
|
+
define_field :TimeStamp, type: Dictionary, version: '1.6'
|
106
|
+
define_field :LegalAttestation, type: PDFArray, version: '1.6'
|
107
|
+
define_field :AddRevInfo, type: Boolean, version: '1.7'
|
108
|
+
|
109
|
+
##
|
110
|
+
# :method: flags
|
111
|
+
#
|
112
|
+
# Returns an array of flag names representing the set bit flags.
|
113
|
+
#
|
114
|
+
|
115
|
+
##
|
116
|
+
# :method: flagged?
|
117
|
+
# :call-seq:
|
118
|
+
# flagged?(flag)
|
119
|
+
#
|
120
|
+
# Returns +true+ if the given flag is set. The argument can either be the flag name or the
|
121
|
+
# bit index.
|
122
|
+
#
|
123
|
+
|
124
|
+
##
|
125
|
+
# :method: flag
|
126
|
+
# :call-seq:
|
127
|
+
# flag(*flags, clear_existing: false)
|
128
|
+
#
|
129
|
+
# Sets the given flags, given as flag names or bit indices. If +clear_existing+ is +true+,
|
130
|
+
# all prior flags will be cleared.
|
131
|
+
#
|
132
|
+
bit_field(:flags, {filter: 0, sub_filter: 1, v: 2, reasons: 3, legal_attestation: 4,
|
133
|
+
add_rev_info: 5, digest_method: 6},
|
134
|
+
lister: "flags", getter: "flagged?", setter: "flag", unsetter: "unflag",
|
135
|
+
value_getter: "self[:Ff]", value_setter: "self[:Ff]")
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
# A certificate seed value dictionary contains information about the characteristics of the
|
140
|
+
# certificate that shall be used when signing.
|
141
|
+
#
|
142
|
+
# == Flags
|
143
|
+
#
|
144
|
+
# The flags describe the entries that a signer is required to use.
|
145
|
+
#
|
146
|
+
# The available flags are: subject, issuer, oid, subject_dn, reserved, key_usage and url.
|
147
|
+
#
|
148
|
+
# See: PDF1.7 s12.7.4.5
|
149
|
+
class CertificateSeedValueDictionary < Dictionary
|
150
|
+
|
151
|
+
extend Utils::BitField
|
152
|
+
|
153
|
+
define_type :SVCert
|
154
|
+
|
155
|
+
define_field :Type, type: Symbol, default: type
|
156
|
+
define_field :Ff, type: Integer, default: 0
|
157
|
+
define_field :Subject, type: PDFArray
|
158
|
+
define_field :SubjectDN, type: PDFArray, version: '1.7'
|
159
|
+
define_field :KeyUsage, type: PDFArray, version: '1.7'
|
160
|
+
define_field :Issuer, type: PDFArray
|
161
|
+
define_field :OID, type: PDFArray
|
162
|
+
define_field :URL, type: String
|
163
|
+
define_field :URLType, type: Symbol, default: :Browser
|
164
|
+
|
165
|
+
##
|
166
|
+
# :method: flags
|
167
|
+
#
|
168
|
+
# Returns an array of flag names representing the set bit flags.
|
169
|
+
#
|
170
|
+
|
171
|
+
##
|
172
|
+
# :method: flagged?
|
173
|
+
# :call-seq:
|
174
|
+
# flagged?(flag)
|
175
|
+
#
|
176
|
+
# Returns +true+ if the given flag is set. The argument can either be the flag name or the
|
177
|
+
# bit index.
|
178
|
+
#
|
179
|
+
|
180
|
+
##
|
181
|
+
# :method: flag
|
182
|
+
# :call-seq:
|
183
|
+
# flag(*flags, clear_existing: false)
|
184
|
+
#
|
185
|
+
# Sets the given flags, given as flag names or bit indices. If +clear_existing+ is +true+,
|
186
|
+
# all prior flags will be cleared.
|
187
|
+
#
|
188
|
+
bit_field(:flags, {subject: 0, issuer: 1, oid: 2, subject_dn: 3, reserved: 4,
|
189
|
+
key_usage: 5, url: 6},
|
190
|
+
lister: "flags", getter: "flagged?", setter: "flag", unsetter: "unflag",
|
191
|
+
value_getter: "self[:Ff]", value_setter: "self[:Ff]")
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
define_field :Lock, type: :SigFieldLock, indirect: true, version: '1.5'
|
196
|
+
define_field :SV, type: :SV, indirect: true, version: '1.5'
|
197
|
+
|
198
|
+
# Returns the associated signature dictionary or +nil+ if the signature is not filled in.
|
199
|
+
def field_value
|
200
|
+
self[:V]
|
201
|
+
end
|
202
|
+
|
203
|
+
# Sets the signature dictionary as value of this signature field.
|
204
|
+
def field_value=(sig_dict)
|
205
|
+
self[:V] = sig_dict
|
206
|
+
end
|
207
|
+
|
208
|
+
private
|
209
|
+
|
210
|
+
def perform_validation #:nodoc:
|
211
|
+
if field_type != :Sig
|
212
|
+
yield("Field /FT of AcroForm signature field has to be :Sig", true)
|
213
|
+
self[:FT] = :Sig
|
214
|
+
end
|
215
|
+
|
216
|
+
super
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|