hexapdf 0.17.2 → 0.19.1
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 +67 -0
- data/lib/hexapdf/cli/command.rb +7 -1
- data/lib/hexapdf/content/canvas.rb +2 -2
- data/lib/hexapdf/content/color_space.rb +19 -0
- data/lib/hexapdf/content/graphic_object/arc.rb +2 -2
- data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +4 -4
- data/lib/hexapdf/content/graphic_object/solid_arc.rb +111 -10
- data/lib/hexapdf/content/graphics_state.rb +167 -25
- data/lib/hexapdf/dictionary.rb +1 -1
- data/lib/hexapdf/dictionary_fields.rb +1 -1
- data/lib/hexapdf/document/signatures.rb +221 -0
- data/lib/hexapdf/encryption/security_handler.rb +3 -1
- data/lib/hexapdf/layout/style.rb +2 -1
- data/lib/hexapdf/object.rb +18 -0
- data/lib/hexapdf/parser.rb +3 -0
- data/lib/hexapdf/serializer.rb +2 -0
- data/lib/hexapdf/task/optimize.rb +46 -3
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +4 -4
- data/lib/hexapdf/type/acro_form/form.rb +39 -28
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +56 -18
- data/lib/hexapdf/type/annotations/widget.rb +3 -15
- data/lib/hexapdf/type/font.rb +5 -0
- data/lib/hexapdf/type/font_type3.rb +20 -0
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +125 -0
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +16 -8
- data/test/hexapdf/content/test_color_space.rb +26 -0
- data/test/hexapdf/content/test_graphics_state.rb +9 -1
- data/test/hexapdf/content/test_operator.rb +8 -3
- data/test/hexapdf/encryption/test_security_handler.rb +3 -1
- data/test/hexapdf/layout/test_style.rb +11 -0
- data/test/hexapdf/task/test_optimize.rb +26 -0
- data/test/hexapdf/test_dictionary.rb +1 -0
- data/test/hexapdf/test_dictionary_fields.rb +3 -2
- data/test/hexapdf/test_object.rb +28 -0
- data/test/hexapdf/test_parser.rb +11 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +8 -1
- data/test/hexapdf/type/acro_form/test_form.rb +15 -8
- data/test/hexapdf/type/acro_form/test_variable_text_field.rb +18 -8
- data/test/hexapdf/type/test_font.rb +4 -0
- data/test/hexapdf/type/test_font_type3.rb +16 -1
- metadata +4 -2
data/lib/hexapdf/dictionary.rb
CHANGED
@@ -318,8 +318,8 @@ module HexaPDF
|
|
318
318
|
value[name] = document.add(obj)
|
319
319
|
elsif !field.indirect && obj.kind_of?(HexaPDF::Object) && obj.indirect?
|
320
320
|
yield("Field #{name} needs to be a direct object", true)
|
321
|
-
document.delete(obj)
|
322
321
|
value[name] = obj.value
|
322
|
+
document.delete(obj)
|
323
323
|
end
|
324
324
|
end
|
325
325
|
end
|
@@ -293,7 +293,7 @@ module HexaPDF
|
|
293
293
|
end
|
294
294
|
|
295
295
|
# :nodoc:
|
296
|
-
DATE_RE = /\AD:(\d{4})(\d\d)?(\d\d)?(\d\d)?(\d\d)?(\d\d)?([Z+-])?(?:(\d\d)(?:'|'(\d
|
296
|
+
DATE_RE = /\AD:(\d{4})(\d\d)?(\d\d)?(\d\d)?(\d\d)?(\d\d)?([Z+-])?(?:(\d\d)(?:'|'([0-5]\d)'?|\z)?)?\z/n
|
297
297
|
|
298
298
|
# Checks if the given object is a string and converts into a Time object if possible.
|
299
299
|
# Otherwise returns +nil+.
|
@@ -0,0 +1,221 @@
|
|
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 'openssl'
|
38
|
+
|
39
|
+
module HexaPDF
|
40
|
+
class Document
|
41
|
+
|
42
|
+
# This class provides methods for interacting with digital signatures of a PDF file.
|
43
|
+
class Signatures
|
44
|
+
|
45
|
+
# This is the default signing handler which provides the ability to sign a document with a
|
46
|
+
# provided certificate using the adb.pkcs7.detached algorithm.
|
47
|
+
class DefaultHandler
|
48
|
+
|
49
|
+
# Creates a new DefaultHandler with the given signing certificate, the associated signing
|
50
|
+
# key and an optional array of certificates that should also be present in the signature.
|
51
|
+
def initialize(certificate, key, certificate_chain = [])
|
52
|
+
@certificate = certificate
|
53
|
+
@key = key
|
54
|
+
@certificate_chain = certificate_chain
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns the name to be set on the /Filter key when using this signing handler.
|
58
|
+
def filter_name
|
59
|
+
:"Adobe.PPKLite"
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the name to be set on the /SubFilter key when using this signing handler.
|
63
|
+
def sub_filter_name
|
64
|
+
:"adbe.pkcs7.detached"
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns the size of the signature that would be created.
|
68
|
+
def signature_size
|
69
|
+
sign("").size
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns the DER serialized OpenSSL::PKCS7 structure containing the signature for the given
|
73
|
+
# data.
|
74
|
+
def sign(data)
|
75
|
+
OpenSSL::PKCS7.sign(@certificate, @key, data, @certificate_chain,
|
76
|
+
OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
include Enumerable
|
82
|
+
|
83
|
+
# Creates a new Signatures object for the given PDF document.
|
84
|
+
def initialize(document)
|
85
|
+
@document = document
|
86
|
+
end
|
87
|
+
|
88
|
+
# Adds a signature to the document and returns the corresponding signature object.
|
89
|
+
#
|
90
|
+
# This method will add a new signature to the document and write the updated document to the
|
91
|
+
# given file or IO stream. Afterwards the document can't be modified anymore and still retain
|
92
|
+
# a correct digital signature; create a new document based on the file or IO stream instead.
|
93
|
+
#
|
94
|
+
# +signature+::
|
95
|
+
# Can either be a signature object or +nil+. Providing a signature object provides for
|
96
|
+
# more control, e.g.:
|
97
|
+
#
|
98
|
+
# * Setting values for optional fields like /Reason and /Location.
|
99
|
+
# * Indirectly specifying which signature field should be used.
|
100
|
+
#
|
101
|
+
# If the +signature+ is not associated with an AcroForm signature field, a new signature
|
102
|
+
# field is created and added to the main AcroForm object, creating that if necessary.
|
103
|
+
#
|
104
|
+
# If the associated signature field doesn't have a widget, a non-visible one is created on
|
105
|
+
# the first page.
|
106
|
+
#
|
107
|
+
# +handler+::
|
108
|
+
# The signature handler that provides the necessary methods for signing, see
|
109
|
+
# DefaultHandler.
|
110
|
+
#
|
111
|
+
# +write_options+::
|
112
|
+
# These options will be passed on to the HexaPDF::Document#write command. Note that
|
113
|
+
# +incremental+ will be automatically set if signing an already existing file.
|
114
|
+
def add(file_or_io, handler, signature: nil, **write_options)
|
115
|
+
signature ||= @document.add({Type: :Sig})
|
116
|
+
signature[:Filter] = handler.filter_name
|
117
|
+
signature[:SubFilter] = handler.sub_filter_name
|
118
|
+
signature[:ByteRange] = [0, 1_000_000_000_000, 1_000_000_000_000, 1_000_000_000_000]
|
119
|
+
signature[:Contents] = '00' * handler.signature_size # twice the size due to hex encoding
|
120
|
+
|
121
|
+
# Prepare signature field
|
122
|
+
form = @document.acro_form(create: true)
|
123
|
+
form.signature_flag(:signatures_exist)
|
124
|
+
|
125
|
+
signature_field = each.find {|value| value == signature }
|
126
|
+
unless signature_field
|
127
|
+
signature_field = form.create_signature_field(generate_field_name)
|
128
|
+
signature_field.field_value = signature
|
129
|
+
end
|
130
|
+
|
131
|
+
if signature_field.each_widget.to_a.empty?
|
132
|
+
signature_field.create_widget(@document.pages[0], Rect: [0, 0, 0, 0])
|
133
|
+
end
|
134
|
+
|
135
|
+
io = if file_or_io.kind_of?(String)
|
136
|
+
File.open(file_or_io, 'w+')
|
137
|
+
else
|
138
|
+
file_or_io
|
139
|
+
end
|
140
|
+
|
141
|
+
# Save the current state so that we can determine the correct /ByteRange value and set the
|
142
|
+
# values
|
143
|
+
section = @document.write(io, incremental: true, **write_options)
|
144
|
+
data = section.map {|oid, _gen, entry| [entry.pos.to_i, oid] }.sort
|
145
|
+
index = data.index {|_pos, oid| oid == signature.oid }
|
146
|
+
signature_offset = data[index][0]
|
147
|
+
signature_length = data[index + 1][0] - data[index][0]
|
148
|
+
io.pos = signature_offset
|
149
|
+
signature_data = io.read(signature_length)
|
150
|
+
|
151
|
+
io.rewind
|
152
|
+
file_data = io.read
|
153
|
+
|
154
|
+
# Calculate the offsets for the /ByteRange
|
155
|
+
contents_offset = signature_offset + signature_data.index('Contents(') + 8
|
156
|
+
offset2 = contents_offset + signature[:Contents].size + 2 # +2 because of the needed < and >
|
157
|
+
length2 = file_data.size - offset2
|
158
|
+
signature[:ByteRange] = [0, contents_offset, offset2, length2]
|
159
|
+
|
160
|
+
# Set the correct /ByteRange value
|
161
|
+
signature_data.sub!(/ByteRange\[0 1000000000000 1000000000000 1000000000000\]/) do |match|
|
162
|
+
length = match.size
|
163
|
+
result = "ByteRange[0 #{contents_offset} #{offset2} #{length2}]"
|
164
|
+
result.ljust(length)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Now everything besides the /Contents value is correct, so we can read the contents for
|
168
|
+
# signing
|
169
|
+
file_data[signature_offset, signature_length] = signature_data
|
170
|
+
signed_contents = file_data[0, contents_offset] << file_data[offset2, length2]
|
171
|
+
signature[:Contents] = handler.sign(signed_contents)
|
172
|
+
|
173
|
+
# Set the correct /Contents value as hexstring
|
174
|
+
signature_data.sub!(/Contents\(0+\)/) do |match|
|
175
|
+
length = match.size
|
176
|
+
result = "Contents<#{signature[:Contents].unpack1('H*')}"
|
177
|
+
"#{result.ljust(length - 1, '0')}>"
|
178
|
+
end
|
179
|
+
|
180
|
+
io.pos = signature_offset
|
181
|
+
io.write(signature_data)
|
182
|
+
|
183
|
+
signature
|
184
|
+
ensure
|
185
|
+
io.close if io && io != file_or_io
|
186
|
+
end
|
187
|
+
|
188
|
+
# :call-seq:
|
189
|
+
# signatures.each {|signature| block } -> signatures
|
190
|
+
# signatures.each -> Enumerator
|
191
|
+
#
|
192
|
+
# Iterates over all signatures in the order they are found.
|
193
|
+
def each
|
194
|
+
return to_enum(__method__) unless block_given?
|
195
|
+
|
196
|
+
return [] unless (form = @document.acro_form)
|
197
|
+
form.each_field do |field|
|
198
|
+
yield(field.field_value) if field.field_type == :Sig && field.field_value
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Returns the number of signatures in the PDF document. May be zero if the document has no
|
203
|
+
# signatures.
|
204
|
+
def count
|
205
|
+
each.to_a.size
|
206
|
+
end
|
207
|
+
|
208
|
+
private
|
209
|
+
|
210
|
+
# Generates a field name for a signature field.
|
211
|
+
def generate_field_name
|
212
|
+
index = (@document.acro_form.each_field.
|
213
|
+
map {|field| field.full_field_name.scan(/\ASignature(\d+)/).first&.first.to_i }.
|
214
|
+
max || 0) + 1
|
215
|
+
"Signature#{index}"
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
end
|
@@ -213,7 +213,9 @@ module HexaPDF
|
|
213
213
|
end
|
214
214
|
|
215
215
|
handler = handler.new(document)
|
216
|
-
document.trailer[:Encrypt] = handler.set_up_decryption(dict, **options)
|
216
|
+
dict = document.trailer[:Encrypt] = handler.set_up_decryption(dict, **options)
|
217
|
+
HexaPDF::Object.make_direct(dict.value)
|
218
|
+
document.revisions.current.update(dict)
|
217
219
|
document.revisions.each do |r|
|
218
220
|
loader = r.loader
|
219
221
|
r.loader = lambda do |xref_entry|
|
data/lib/hexapdf/layout/style.rb
CHANGED
@@ -1069,7 +1069,8 @@ module HexaPDF
|
|
1069
1069
|
|
1070
1070
|
# The font size scaled appropriately.
|
1071
1071
|
def scaled_font_size
|
1072
|
-
@scaled_font_size ||= calculated_font_size
|
1072
|
+
@scaled_font_size ||= calculated_font_size * font.pdf_object.glyph_scaling_factor *
|
1073
|
+
scaled_horizontal_scaling
|
1073
1074
|
end
|
1074
1075
|
|
1075
1076
|
# The character spacing scaled appropriately.
|
data/lib/hexapdf/object.rb
CHANGED
@@ -141,6 +141,24 @@ module HexaPDF
|
|
141
141
|
end
|
142
142
|
end
|
143
143
|
|
144
|
+
# Makes sure that the object itself as well as all nested values are direct objects.
|
145
|
+
#
|
146
|
+
# If an indirect object is found, it is turned into a direct object and the indirect object is
|
147
|
+
# deleted from the document.
|
148
|
+
def self.make_direct(object)
|
149
|
+
if object.kind_of?(HexaPDF::Object) && object.indirect?
|
150
|
+
object_to_delete = object
|
151
|
+
object = object.value
|
152
|
+
object_to_delete.document.delete(object_to_delete)
|
153
|
+
end
|
154
|
+
if object.kind_of?(Hash)
|
155
|
+
object.transform_values! {|val| make_direct(val) }
|
156
|
+
elsif object.kind_of?(Array)
|
157
|
+
object.map! {|val| make_direct(val) }
|
158
|
+
end
|
159
|
+
object
|
160
|
+
end
|
161
|
+
|
144
162
|
# The wrapped HexaPDF::PDFData value.
|
145
163
|
#
|
146
164
|
# This attribute is not part of the public API!
|
data/lib/hexapdf/parser.rb
CHANGED
@@ -404,6 +404,7 @@ module HexaPDF
|
|
404
404
|
def reconstruct_revision
|
405
405
|
return if @in_reconstruct_revision
|
406
406
|
@in_reconstruct_revision = true
|
407
|
+
@header_offset = 0
|
407
408
|
|
408
409
|
raise unless @document.config['parser.try_xref_reconstruction']
|
409
410
|
msg = "#{$!} - trying cross-reference table reconstruction"
|
@@ -428,8 +429,10 @@ module HexaPDF
|
|
428
429
|
elsif gen.kind_of?(Integer) && tok.kind_of?(Tokenizer::Token) && tok == 'obj'
|
429
430
|
xref.add_in_use_entry(token, gen, pos)
|
430
431
|
if linearized.nil?
|
432
|
+
pos = @tokenizer.pos
|
431
433
|
obj = @tokenizer.next_object rescue nil
|
432
434
|
linearized = obj.kind_of?(Hash) && obj.key?(:Linearized)
|
435
|
+
@tokenizer.pos = pos
|
433
436
|
end
|
434
437
|
@tokenizer.scan_until(/(?:\n|\r\n?)endobj\b/)
|
435
438
|
end
|
data/lib/hexapdf/serializer.rb
CHANGED
@@ -72,8 +72,19 @@ module HexaPDF
|
|
72
72
|
# Compresses the content streams of all pages if set to +true+. Note that this can take a
|
73
73
|
# *very* long time because each content stream has to be unfiltered, parsed, serialized
|
74
74
|
# and then filtered again.
|
75
|
+
#
|
76
|
+
# prune_page_resources::
|
77
|
+
# Removes all unused XObjects from the resources dictionaries of all pages. It is
|
78
|
+
# recommended to also set the +compact+ argument because otherwise the unused XObjects won't
|
79
|
+
# be deleted from the document.
|
80
|
+
#
|
81
|
+
# This is sometimes necessary after importing pages from other PDF files that use a single
|
82
|
+
# resources dictionary for all pages.
|
75
83
|
def self.call(doc, compact: false, object_streams: :preserve, xref_streams: :preserve,
|
76
|
-
compress_pages: false)
|
84
|
+
compress_pages: false, prune_page_resources: false)
|
85
|
+
used_refs = compress_pages(doc) if compress_pages
|
86
|
+
prune_page_resources(doc, used_refs) if prune_page_resources
|
87
|
+
|
77
88
|
if compact
|
78
89
|
compact(doc, object_streams, xref_streams)
|
79
90
|
elsif object_streams != :preserve
|
@@ -83,8 +94,6 @@ module HexaPDF
|
|
83
94
|
else
|
84
95
|
doc.each(only_current: false, &method(:delete_fields_with_defaults))
|
85
96
|
end
|
86
|
-
|
87
|
-
compress_pages(doc) if compress_pages
|
88
97
|
end
|
89
98
|
|
90
99
|
# Compacts the document by merging all revisions into one, deleting null and unused entries
|
@@ -214,12 +223,41 @@ module HexaPDF
|
|
214
223
|
|
215
224
|
# Compresses the contents of all pages by parsing and then serializing again. The HexaPDF
|
216
225
|
# serializer is already optimized for small output size so nothing else needs to be done.
|
226
|
+
#
|
227
|
+
# Returns a hash of the form key=>true where the keys are the used XObjects (for use with
|
228
|
+
# #prune_page_resources).
|
217
229
|
def self.compress_pages(doc)
|
230
|
+
used_refs = {}
|
218
231
|
doc.pages.each do |page|
|
219
232
|
processor = SerializationProcessor.new
|
220
233
|
HexaPDF::Content::Parser.parse(page.contents, processor)
|
221
234
|
page.contents = processor.result
|
222
235
|
page[:Contents].set_filter(:FlateDecode)
|
236
|
+
xobjects = page.resources[:XObject]
|
237
|
+
processor.used_references.each {|ref| used_refs[xobjects[ref]] = true }
|
238
|
+
end
|
239
|
+
used_refs
|
240
|
+
end
|
241
|
+
|
242
|
+
# Deletes all XObject entries from the resources dictionaries of all pages whose names do not
|
243
|
+
# match the keys in +used_refs+.
|
244
|
+
def self.prune_page_resources(doc, used_refs)
|
245
|
+
unless used_refs
|
246
|
+
used_refs = {}
|
247
|
+
doc.pages.each do |page|
|
248
|
+
xobjects = page.resources[:XObject]
|
249
|
+
HexaPDF::Content::Parser.parse(page.contents) do |op, operands|
|
250
|
+
used_refs[xobjects[operands[0]]] = true if op == :Do
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
doc.pages.each do |page|
|
256
|
+
xobjects = page.resources[:XObject]
|
257
|
+
xobjects.each do |key, obj|
|
258
|
+
next if used_refs[obj]
|
259
|
+
xobjects.delete(key)
|
260
|
+
end
|
223
261
|
end
|
224
262
|
end
|
225
263
|
|
@@ -228,14 +266,19 @@ module HexaPDF
|
|
228
266
|
|
229
267
|
attr_reader :result #:nodoc:
|
230
268
|
|
269
|
+
# Contains all found references
|
270
|
+
attr_reader :used_references
|
271
|
+
|
231
272
|
def initialize #:nodoc:
|
232
273
|
@result = ''.b
|
233
274
|
@serializer = HexaPDF::Serializer.new
|
275
|
+
@used_references = []
|
234
276
|
end
|
235
277
|
|
236
278
|
def process(op, operands) #:nodoc:
|
237
279
|
@result << HexaPDF::Content::Operator::DEFAULT_OPERATORS[op].
|
238
280
|
serialize(@serializer, *operands)
|
281
|
+
@used_references << operands[0] if op == :Do
|
239
282
|
end
|
240
283
|
|
241
284
|
end
|
@@ -227,8 +227,8 @@ module HexaPDF
|
|
227
227
|
# Note: Rich text fields are currently not supported!
|
228
228
|
def create_text_appearances
|
229
229
|
default_resources = @document.acro_form.default_resources
|
230
|
-
font, font_size = retrieve_font_information(default_resources)
|
231
|
-
style = HexaPDF::Layout::Style.new(font: font)
|
230
|
+
font, font_size, font_color = retrieve_font_information(default_resources)
|
231
|
+
style = HexaPDF::Layout::Style.new(font: font, fill_color: font_color)
|
232
232
|
border_style = @widget.border_style
|
233
233
|
padding = [1, border_style.width].max
|
234
234
|
|
@@ -482,7 +482,7 @@ module HexaPDF
|
|
482
482
|
|
483
483
|
# Returns the font wrapper and font size to be used for a variable text field.
|
484
484
|
def retrieve_font_information(resources)
|
485
|
-
font_name, font_size = @field.parse_default_appearance_string
|
485
|
+
font_name, font_size, font_color = @field.parse_default_appearance_string
|
486
486
|
font_object = resources.font(font_name) rescue nil
|
487
487
|
font = font_object&.font_wrapper
|
488
488
|
unless font
|
@@ -498,7 +498,7 @@ module HexaPDF
|
|
498
498
|
raise(HexaPDF::Error, "Font #{font_name} of the AcroForm's default resources not usable")
|
499
499
|
end
|
500
500
|
end
|
501
|
-
[font, font_size]
|
501
|
+
[font, font_size, font_color]
|
502
502
|
end
|
503
503
|
|
504
504
|
# Calculates the font size for text fields based on the font and font size of the default
|
@@ -157,8 +157,8 @@ module HexaPDF
|
|
157
157
|
# The optional keyword arguments allow setting often used properties of the field:
|
158
158
|
#
|
159
159
|
# +font+::
|
160
|
-
# The font that should be used for the text of the field. If +font_size+
|
161
|
-
# +
|
160
|
+
# The font that should be used for the text of the field. If +font_size+, +font_options+
|
161
|
+
# or +font_color+ is specified but +font+ isn't, the font Helvetica is used.
|
162
162
|
#
|
163
163
|
# If no font is set on the text field, the default font properties of the AcroForm form
|
164
164
|
# are used. Note that field specific or form specific font properties have to be set.
|
@@ -169,15 +169,20 @@ module HexaPDF
|
|
169
169
|
# A hash with font options like :variant that should be used.
|
170
170
|
#
|
171
171
|
# +font_size+::
|
172
|
-
# The font size that should be used. If +font+ or +
|
173
|
-
# +font_size+ isn't, font size defaults to 0 (= auto-sizing).
|
172
|
+
# The font size that should be used. If +font+, +font_options+ or +font_color+ is
|
173
|
+
# specified but +font_size+ isn't, font size defaults to 0 (= auto-sizing).
|
174
|
+
#
|
175
|
+
# +font_color+::
|
176
|
+
# The font color that should be used. If +font+, +font_options+ or +font_size+ is
|
177
|
+
# specified but +font_color+ isn't, font color defaults to 0 (i.e. black).
|
174
178
|
#
|
175
179
|
# +align+::
|
176
180
|
# The alignment of the text, either :left, :center or :right.
|
177
|
-
def create_text_field(name, font: nil, font_options: nil, font_size: nil,
|
181
|
+
def create_text_field(name, font: nil, font_options: nil, font_size: nil, font_color: nil,
|
182
|
+
align: nil)
|
178
183
|
create_field(name, :Tx) do |field|
|
179
184
|
apply_variable_text_properties(field, font: font, font_options: font_options,
|
180
|
-
font_size: font_size, align: align)
|
185
|
+
font_size: font_size, font_color: font_color, align: align)
|
181
186
|
end
|
182
187
|
end
|
183
188
|
|
@@ -189,11 +194,11 @@ module HexaPDF
|
|
189
194
|
# The optional keyword arguments allow setting often used properties of the field, see
|
190
195
|
# #create_text_field for details.
|
191
196
|
def create_multiline_text_field(name, font: nil, font_options: nil, font_size: nil,
|
192
|
-
align: nil)
|
197
|
+
font_color: nil, align: nil)
|
193
198
|
create_field(name, :Tx) do |field|
|
194
199
|
field.initialize_as_multiline_text_field
|
195
200
|
apply_variable_text_properties(field, font: font, font_options: font_options,
|
196
|
-
font_size: font_size, align: align)
|
201
|
+
font_size: font_size, font_color: font_color, align: align)
|
197
202
|
end
|
198
203
|
end
|
199
204
|
|
@@ -208,11 +213,11 @@ module HexaPDF
|
|
208
213
|
# The optional keyword arguments allow setting often used properties of the field, see
|
209
214
|
# #create_text_field for details.
|
210
215
|
def create_comb_text_field(name, max_chars:, font: nil, font_options: nil, font_size: nil,
|
211
|
-
align: nil)
|
216
|
+
font_color: nil, align: nil)
|
212
217
|
create_field(name, :Tx) do |field|
|
213
218
|
field.initialize_as_comb_text_field
|
214
219
|
apply_variable_text_properties(field, font: font, font_options: font_options,
|
215
|
-
font_size: font_size, align: align)
|
220
|
+
font_size: font_size, font_color: font_color, align: align)
|
216
221
|
field[:MaxLen] = max_chars
|
217
222
|
end
|
218
223
|
end
|
@@ -224,11 +229,12 @@ module HexaPDF
|
|
224
229
|
#
|
225
230
|
# The optional keyword arguments allow setting often used properties of the field, see
|
226
231
|
# #create_text_field for details.
|
227
|
-
def create_file_select_field(name, font: nil, font_options: nil, font_size: nil,
|
232
|
+
def create_file_select_field(name, font: nil, font_options: nil, font_size: nil,
|
233
|
+
font_color: nil, align: nil)
|
228
234
|
create_field(name, :Tx) do |field|
|
229
235
|
field.initialize_as_file_select_field
|
230
236
|
apply_variable_text_properties(field, font: font, font_options: font_options,
|
231
|
-
font_size: font_size, align: align)
|
237
|
+
font_size: font_size, font_color: font_color, align: align)
|
232
238
|
end
|
233
239
|
end
|
234
240
|
|
@@ -239,11 +245,12 @@ module HexaPDF
|
|
239
245
|
#
|
240
246
|
# The optional keyword arguments allow setting often used properties of the field, see
|
241
247
|
# #create_text_field for details.
|
242
|
-
def create_password_field(name, font: nil, font_options: nil, font_size: nil,
|
248
|
+
def create_password_field(name, font: nil, font_options: nil, font_size: nil,
|
249
|
+
font_color: nil, align: nil)
|
243
250
|
create_field(name, :Tx) do |field|
|
244
251
|
field.initialize_as_password_field
|
245
252
|
apply_variable_text_properties(field, font: font, font_options: font_options,
|
246
|
-
font_size: font_size, align: align)
|
253
|
+
font_size: font_size, font_color: font_color, align: align)
|
247
254
|
end
|
248
255
|
end
|
249
256
|
|
@@ -280,13 +287,13 @@ module HexaPDF
|
|
280
287
|
# +font+, +font_options+, +font_size+ and +align+::
|
281
288
|
# See #create_text_field
|
282
289
|
def create_combo_box(name, option_items: nil, editable: nil, font: nil,
|
283
|
-
font_options: nil, font_size: nil, align: nil)
|
290
|
+
font_options: nil, font_size: nil, font_color: nil, align: nil)
|
284
291
|
create_field(name, :Ch) do |field|
|
285
292
|
field.initialize_as_combo_box
|
286
293
|
field.option_items = option_items if option_items
|
287
294
|
field.flag(:edit) if editable
|
288
295
|
apply_variable_text_properties(field, font: font, font_options: font_options,
|
289
|
-
font_size: font_size, align: align)
|
296
|
+
font_size: font_size, font_color: font_color, align: align)
|
290
297
|
end
|
291
298
|
end
|
292
299
|
|
@@ -306,13 +313,13 @@ module HexaPDF
|
|
306
313
|
# +font+, +font_options+, +font_size+ and +align+::
|
307
314
|
# See #create_text_field.
|
308
315
|
def create_list_box(name, option_items: nil, multi_select: nil, font: nil,
|
309
|
-
font_options: nil, font_size: nil, align: nil)
|
316
|
+
font_options: nil, font_size: nil, font_color: nil, align: nil)
|
310
317
|
create_field(name, :Ch) do |field|
|
311
318
|
field.initialize_as_list_box
|
312
319
|
field.option_items = option_items if option_items
|
313
320
|
field.flag(:multi_select) if multi_select
|
314
321
|
apply_variable_text_properties(field, font: font, font_options: font_options,
|
315
|
-
font_size: font_size, align: align)
|
322
|
+
font_size: font_size, font_color: font_color, align: align)
|
316
323
|
end
|
317
324
|
end
|
318
325
|
|
@@ -322,13 +329,16 @@ module HexaPDF
|
|
322
329
|
type: :XXResources)
|
323
330
|
end
|
324
331
|
|
325
|
-
# Sets the global default appearance string using the provided values
|
332
|
+
# Sets the global default appearance string using the provided values or the default values
|
333
|
+
# which provide a sane default.
|
326
334
|
#
|
327
|
-
#
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
335
|
+
# See VariableTextField::create_appearance_string for information on the arguments.
|
336
|
+
def set_default_appearance_string(font: 'Helvetica', font_options: {}, font_size: 0,
|
337
|
+
font_color: 0)
|
338
|
+
self[:DA] = VariableTextField.create_appearance_string(document, font: font,
|
339
|
+
font_options: font_options,
|
340
|
+
font_size: font_size,
|
341
|
+
font_color: font_color)
|
332
342
|
end
|
333
343
|
|
334
344
|
# Sets the /NeedAppearances field to +true+.
|
@@ -420,11 +430,12 @@ module HexaPDF
|
|
420
430
|
|
421
431
|
# Applies the given variable field properties to the field.
|
422
432
|
def apply_variable_text_properties(field, font: nil, font_options: nil, font_size: nil,
|
423
|
-
align: nil)
|
424
|
-
if font || font_options || font_size
|
433
|
+
font_color: nil, align: nil)
|
434
|
+
if font || font_options || font_size || font_color
|
425
435
|
field.set_default_appearance_string(font: font || 'Helvetica',
|
426
436
|
font_options: font_options || {},
|
427
|
-
font_size: font_size || 0
|
437
|
+
font_size: font_size || 0,
|
438
|
+
font_color: font_color || 0)
|
428
439
|
end
|
429
440
|
field.text_alignment(align) if align
|
430
441
|
end
|
@@ -437,7 +448,7 @@ module HexaPDF
|
|
437
448
|
yield("When the field /DA is present, the field /DR must also be present")
|
438
449
|
return
|
439
450
|
end
|
440
|
-
font_name,
|
451
|
+
font_name, = VariableTextField.parse_appearance_string(da)
|
441
452
|
if font_name && !(self[:DR][:Font] && self[:DR][:Font][font_name])
|
442
453
|
yield("The font specified in /DA is not in the /DR resource dictionary")
|
443
454
|
end
|