hexapdf 0.14.2 → 0.15.2
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 +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
|