hexapdf 0.19.0 → 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 +12 -0
- data/lib/hexapdf/content/graphics_state.rb +24 -5
- data/lib/hexapdf/document/signatures.rb +221 -0
- data/lib/hexapdf/layout/style.rb +2 -1
- 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/test_graphics_state.rb +9 -1
- data/test/hexapdf/content/test_operator.rb +8 -3
- data/test/hexapdf/layout/test_style.rb +11 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/test_font.rb +4 -0
- data/test/hexapdf/type/test_font_type3.rb +16 -1
- metadata +4 -32
- data/test/data/cert/create.sh +0 -171
- data/test/data/cert/root-ca/certs/84E66B6F4C359E741C0AFA014790DF39.pem +0 -119
- data/test/data/cert/root-ca/certs/84E66B6F4C359E741C0AFA014790DF3A.pem +0 -125
- data/test/data/cert/root-ca/db/crlnumber +0 -1
- data/test/data/cert/root-ca/db/index +0 -2
- data/test/data/cert/root-ca/db/index.attr +0 -1
- data/test/data/cert/root-ca/db/index.attr.old +0 -1
- data/test/data/cert/root-ca/db/index.old +0 -1
- data/test/data/cert/root-ca/db/serial +0 -1
- data/test/data/cert/root-ca/db/serial.old +0 -1
- data/test/data/cert/root-ca/private/root-ca.key +0 -52
- data/test/data/cert/root-ca/root-ca.conf +0 -65
- data/test/data/cert/root-ca/root-ca.crt +0 -119
- data/test/data/cert/root-ca/root-ca.csr +0 -28
- data/test/data/cert/signature-1-pkcs7-detached.pdf +0 -182
- data/test/data/cert/sub-ca/certs/453FF080E3EDCD6A388D5368DFC320D9.pem +0 -125
- data/test/data/cert/sub-ca/db/crlnumber +0 -1
- data/test/data/cert/sub-ca/db/index +0 -1
- data/test/data/cert/sub-ca/db/index.attr +0 -1
- data/test/data/cert/sub-ca/db/index.old +0 -0
- data/test/data/cert/sub-ca/db/serial +0 -1
- data/test/data/cert/sub-ca/db/serial.old +0 -1
- data/test/data/cert/sub-ca/private/signing.key +0 -52
- data/test/data/cert/sub-ca/private/sub-ca.key +0 -52
- data/test/data/cert/sub-ca/signing.crt +0 -125
- data/test/data/cert/sub-ca/signing.csr +0 -28
- data/test/data/cert/sub-ca/signing.p12 +0 -0
- data/test/data/cert/sub-ca/sub-ca.conf +0 -65
- data/test/data/cert/sub-ca/sub-ca.crt +0 -125
- data/test/data/cert/sub-ca/sub-ca.csr +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 496c87d6cffcfde7b155fbfcdd78673fb34931d4d0fd997bcee934decfba175d
|
4
|
+
data.tar.gz: ce72598c9d33735c6ea4738c072b1f00202dc7bac24577c9c27a062add38ea8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cee5a6f86084490ba5602c40ec48945b9a3c0bdecb22fe03e2d7b8b6ad02c791900f9fd7354557a9ac3dfdebb4a8d95c7335fc329c0054156082853f33717144
|
7
|
+
data.tar.gz: eaed67cce68e703eddbbb357e790a9f0bc62083ff8ba0b0856aa360d499f75e0e022b1ccd41ebcdd53e2fe269a93b5f587c47db41311c3f6a0beb9ed293e02cd
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
## 0.19.1 - 2021-12-12
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* [HexaPDF::Type::FontType3#bounding_box] to fix content stream processing error
|
6
|
+
|
7
|
+
### Fixed
|
8
|
+
|
9
|
+
* Calculation of scaled font size for [HexaPDF::Content::GraphicsState] and
|
10
|
+
[HexaPDF::Layout::Style] when Type3 fonts are used
|
11
|
+
|
12
|
+
|
1
13
|
## 0.19.0 - 2021-11-24
|
2
14
|
|
3
15
|
### Added
|
@@ -512,7 +512,7 @@ module HexaPDF
|
|
512
512
|
attr_accessor :leading
|
513
513
|
|
514
514
|
# The font for the text.
|
515
|
-
|
515
|
+
attr_reader :font
|
516
516
|
|
517
517
|
# The font size.
|
518
518
|
attr_reader :font_size
|
@@ -552,9 +552,11 @@ module HexaPDF
|
|
552
552
|
|
553
553
|
# The scaled font size used in glyph displacement calculations.
|
554
554
|
#
|
555
|
-
# This returns the font size
|
555
|
+
# This returns the font size multiplied by the scaling factor from glyph space to text space
|
556
|
+
# (0.001 for all fonts except Type3 fonts or the scaling specified in /FontMatrix for Type3
|
557
|
+
# fonts) and multiplied by #scaled_horizontal_scaling.
|
556
558
|
#
|
557
|
-
# See PDF1.7 s9.4.4
|
559
|
+
# See PDF1.7 s9.4.4, HexaPDF::Type::FontType3
|
558
560
|
attr_reader :scaled_font_size
|
559
561
|
|
560
562
|
# The scaled horizontal scaling used in glyph displacement calculations.
|
@@ -665,6 +667,15 @@ module HexaPDF
|
|
665
667
|
self.fill_color = color_space.default_color
|
666
668
|
end
|
667
669
|
|
670
|
+
##
|
671
|
+
# :attr_writer: font
|
672
|
+
#
|
673
|
+
# Sets the font and updates the glyph space to text space scaling.
|
674
|
+
def font=(font)
|
675
|
+
@font = font
|
676
|
+
update_scaled_font_size
|
677
|
+
end
|
678
|
+
|
668
679
|
##
|
669
680
|
# :attr_writer: character_spacing
|
670
681
|
#
|
@@ -689,7 +700,7 @@ module HexaPDF
|
|
689
700
|
# Sets the font size and updates the scaled font size.
|
690
701
|
def font_size=(size)
|
691
702
|
@font_size = size
|
692
|
-
|
703
|
+
update_scaled_font_size
|
693
704
|
end
|
694
705
|
|
695
706
|
##
|
@@ -702,7 +713,15 @@ module HexaPDF
|
|
702
713
|
@scaled_horizontal_scaling = scaling / 100.0
|
703
714
|
@scaled_character_spacing = @character_spacing * @scaled_horizontal_scaling
|
704
715
|
@scaled_word_spacing = @word_spacing * @scaled_horizontal_scaling
|
705
|
-
|
716
|
+
update_scaled_font_size
|
717
|
+
end
|
718
|
+
|
719
|
+
private
|
720
|
+
|
721
|
+
# Updates the cached value for the scaled font size.
|
722
|
+
def update_scaled_font_size
|
723
|
+
@scaled_font_size = @font_size * (@font&.glyph_scaling_factor || 0.001) *
|
724
|
+
@scaled_horizontal_scaling
|
706
725
|
end
|
707
726
|
|
708
727
|
end
|
@@ -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
|
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/type/font.rb
CHANGED
@@ -41,6 +41,10 @@ module HexaPDF
|
|
41
41
|
|
42
42
|
# Represents a Type 3 font.
|
43
43
|
#
|
44
|
+
# Note: We assume the /FontMatrix is only used for scaling, i.e. of the form [x 0 0 +/-x 0 0].
|
45
|
+
# If it is of a different form, things won't work correctly. This will be handled once such a
|
46
|
+
# case is found.
|
47
|
+
#
|
44
48
|
# See: PDF1.7 s9.6.5
|
45
49
|
class FontType3 < FontSimple
|
46
50
|
|
@@ -51,6 +55,22 @@ module HexaPDF
|
|
51
55
|
define_field :CharProcs, type: Dictionary, required: true
|
52
56
|
define_field :Resources, type: Dictionary, version: '1.2'
|
53
57
|
|
58
|
+
# Returns the bounding box of the font.
|
59
|
+
def bounding_box
|
60
|
+
matrix = self[:FontMatrix]
|
61
|
+
bbox = self[:FontBBox].value
|
62
|
+
if matrix[3] < 0 # Some writers invert the y-axis
|
63
|
+
bbox = bbox.dup
|
64
|
+
bbox[1], bbox[3] = -bbox[3], -bbox[1]
|
65
|
+
end
|
66
|
+
bbox
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the glyph scaling factor for transforming from glyph space to text space.
|
70
|
+
def glyph_scaling_factor
|
71
|
+
self[:FontMatrix][0]
|
72
|
+
end
|
73
|
+
|
54
74
|
private
|
55
75
|
|
56
76
|
def perform_validation
|
@@ -0,0 +1,125 @@
|
|
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
|
+
require 'hexapdf/type/signature'
|
39
|
+
|
40
|
+
module HexaPDF
|
41
|
+
module Type
|
42
|
+
class Signature
|
43
|
+
|
44
|
+
# The signature handler for the adbe.pkcs7.detached sub-filter.
|
45
|
+
class AdbePkcs7Detached < Handler
|
46
|
+
|
47
|
+
# Creates a new signature handler for the given signature dictionary.
|
48
|
+
def initialize(signature_dict)
|
49
|
+
super
|
50
|
+
@pkcs7 = OpenSSL::PKCS7.new(signature_dict.contents)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the common name of the signer.
|
54
|
+
def signer_name
|
55
|
+
signer_certificate.subject.to_a.assoc("CN")&.[](1) || super
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the time of signing.
|
59
|
+
def signing_time
|
60
|
+
signer_info.signed_time rescue super
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the certificate chain.
|
64
|
+
def certificate_chain
|
65
|
+
@pkcs7.certificates
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the signer certificate (an instance of OpenSSL::X509::Certificate).
|
69
|
+
def signer_certificate
|
70
|
+
info = signer_info
|
71
|
+
certificate_chain.find {|cert| cert.issuer == info.issuer && cert.serial == info.serial }
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the signer information object (an instance of OpenSSL::PKCS7::SignerInfo).
|
75
|
+
def signer_info
|
76
|
+
@pkcs7.signers.first
|
77
|
+
end
|
78
|
+
|
79
|
+
# Verifies the signature using the provided OpenSSL::X509::Store object.
|
80
|
+
def verify(store, allow_self_signed: false)
|
81
|
+
result = VerificationResult.new
|
82
|
+
|
83
|
+
signer_info = self.signer_info
|
84
|
+
signer_certificate = self.signer_certificate
|
85
|
+
certificate_chain = self.certificate_chain
|
86
|
+
|
87
|
+
if certificate_chain.empty?
|
88
|
+
result.log(:error, "No certificates found in signature")
|
89
|
+
return result
|
90
|
+
end
|
91
|
+
|
92
|
+
if @pkcs7.signers.size != 1
|
93
|
+
result.log(:error, "Exactly one signer needed, found #{@pkcs7.signers.size}")
|
94
|
+
end
|
95
|
+
|
96
|
+
unless signer_certificate
|
97
|
+
result.log(:error, "Signer serial=#{signer_info.serial} issuer=#{signer_info.issuer} " \
|
98
|
+
"not found in certificates stored in PKCS7 object")
|
99
|
+
return result
|
100
|
+
end
|
101
|
+
|
102
|
+
key_usage = signer_certificate.extensions.find {|ext| ext.oid == 'keyUsage' }
|
103
|
+
unless key_usage && key_usage.value.split(', ').include?("Digital Signature")
|
104
|
+
result.log(:error, "Certificate key usage is missing 'Digital Signature'")
|
105
|
+
end
|
106
|
+
|
107
|
+
verify_signing_time(result)
|
108
|
+
|
109
|
+
store.verify_callback = store_verification_callback(result,
|
110
|
+
allow_self_signed: allow_self_signed)
|
111
|
+
if @pkcs7.verify(certificate_chain, store, signature_dict.signed_data,
|
112
|
+
OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY)
|
113
|
+
result.log(:info, "Signature valid")
|
114
|
+
else
|
115
|
+
result.log(:error, "Signature verification failed")
|
116
|
+
end
|
117
|
+
|
118
|
+
result
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
data/lib/hexapdf/version.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'test_helper'
|
4
4
|
require 'hexapdf/content/graphics_state'
|
5
|
+
require 'ostruct'
|
5
6
|
|
6
7
|
# Dummy class used as wrapper so that constant lookup works correctly
|
7
8
|
class GraphicsStateWrapper < Minitest::Spec
|
@@ -146,6 +147,13 @@ class GraphicsStateWrapper < Minitest::Spec
|
|
146
147
|
it "fails when restoring the graphics state if the stack is empty" do
|
147
148
|
assert_raises(HexaPDF::Error) { @gs.restore }
|
148
149
|
end
|
149
|
-
end
|
150
150
|
|
151
|
+
it "uses the correct glyph to text space scaling" do
|
152
|
+
font = OpenStruct.new
|
153
|
+
font.glyph_scaling_factor = 0.002
|
154
|
+
@gs.font = font
|
155
|
+
@gs.font_size = 10
|
156
|
+
assert_equal(0.02, @gs.scaled_font_size)
|
157
|
+
end
|
158
|
+
end
|
151
159
|
end
|
@@ -4,6 +4,7 @@ require 'test_helper'
|
|
4
4
|
require 'hexapdf/content/operator'
|
5
5
|
require 'hexapdf/content/processor'
|
6
6
|
require 'hexapdf/serializer'
|
7
|
+
require 'ostruct'
|
7
8
|
|
8
9
|
describe HexaPDF::Content::Operator::BaseOperator do
|
9
10
|
before do
|
@@ -190,9 +191,11 @@ end
|
|
190
191
|
|
191
192
|
describe_operator :SetGraphicsStateParameters, :gs do
|
192
193
|
it "applies parameters from an ExtGState dictionary" do
|
194
|
+
font = OpenStruct.new
|
195
|
+
font.glyph_scaling_factor = 0.01
|
193
196
|
@processor.resources[:ExtGState] = {Name: {LW: 10, LC: 2, LJ: 2, ML: 2, D: [[3, 5], 2],
|
194
197
|
RI: 2, SA: true, BM: :Multiply, CA: 0.5, ca: 0.5,
|
195
|
-
AIS: true, TK: false, Font: [
|
198
|
+
AIS: true, TK: false, Font: [font, 10]}}
|
196
199
|
@processor.resources.define_singleton_method(:document) do
|
197
200
|
Object.new.tap {|obj| obj.define_singleton_method(:deref) {|o| o } }
|
198
201
|
end
|
@@ -210,7 +213,7 @@ describe_operator :SetGraphicsStateParameters, :gs do
|
|
210
213
|
assert_equal(0.5, gs.stroke_alpha)
|
211
214
|
assert_equal(0.5, gs.fill_alpha)
|
212
215
|
assert(gs.alpha_source)
|
213
|
-
assert_equal(
|
216
|
+
assert_equal(font, gs.font)
|
214
217
|
assert_equal(10, gs.font_size)
|
215
218
|
refute(gs.text_knockout)
|
216
219
|
end
|
@@ -448,7 +451,9 @@ describe_operator :SetFontAndSize, :Tf do
|
|
448
451
|
self[:Font] && self[:Font][name]
|
449
452
|
end
|
450
453
|
|
451
|
-
|
454
|
+
font = OpenStruct.new
|
455
|
+
font.glyph_scaling_factor = 0.01
|
456
|
+
@processor.resources[:Font] = {F1: font}
|
452
457
|
invoke(:F1, 10)
|
453
458
|
assert_equal(@processor.resources.font(:F1), @processor.graphics_state.font)
|
454
459
|
assert_equal(10, @processor.graphics_state.font_size)
|
@@ -597,6 +597,11 @@ end
|
|
597
597
|
describe HexaPDF::Layout::Style do
|
598
598
|
before do
|
599
599
|
@style = HexaPDF::Layout::Style.new
|
600
|
+
@style.font = Object.new.tap do |obj|
|
601
|
+
obj.define_singleton_method(:pdf_object) do
|
602
|
+
Object.new.tap {|pdf| pdf.define_singleton_method(:glyph_scaling_factor) { 0.001 } }
|
603
|
+
end
|
604
|
+
end
|
600
605
|
end
|
601
606
|
|
602
607
|
it "can assign values on initialization" do
|
@@ -644,6 +649,7 @@ describe HexaPDF::Layout::Style do
|
|
644
649
|
end
|
645
650
|
|
646
651
|
it "has several simple and dynamically generated properties with default values" do
|
652
|
+
@style = HexaPDF::Layout::Style.new
|
647
653
|
assert_raises(HexaPDF::Error) { @style.font }
|
648
654
|
assert_equal(10, @style.font_size)
|
649
655
|
assert_equal(0, @style.character_spacing)
|
@@ -725,6 +731,11 @@ describe HexaPDF::Layout::Style do
|
|
725
731
|
font = Object.new
|
726
732
|
font.define_singleton_method(:scaling_factor) { 1 }
|
727
733
|
font.define_singleton_method(:wrapped_font) { wrapped_font }
|
734
|
+
font.define_singleton_method(:pdf_object) do
|
735
|
+
obj = Object.new
|
736
|
+
obj.define_singleton_method(:glyph_scaling_factor) { 0.001 }
|
737
|
+
obj
|
738
|
+
end
|
728
739
|
@style.font = font
|
729
740
|
end
|
730
741
|
|
data/test/hexapdf/test_writer.rb
CHANGED
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
|
|
40
40
|
219
|
41
41
|
%%EOF
|
42
42
|
3 0 obj
|
43
|
-
<</Producer(HexaPDF version 0.19.
|
43
|
+
<</Producer(HexaPDF version 0.19.1)>>
|
44
44
|
endobj
|
45
45
|
xref
|
46
46
|
3 1
|
@@ -72,7 +72,7 @@ describe HexaPDF::Writer do
|
|
72
72
|
141
|
73
73
|
%%EOF
|
74
74
|
6 0 obj
|
75
|
-
<</Producer(HexaPDF version 0.19.
|
75
|
+
<</Producer(HexaPDF version 0.19.1)>>
|
76
76
|
endobj
|
77
77
|
2 0 obj
|
78
78
|
<</Length 10>>stream
|
@@ -9,10 +9,25 @@ describe HexaPDF::Type::FontType3 do
|
|
9
9
|
@doc = HexaPDF::Document.new
|
10
10
|
@font = @doc.add({Type: :Font, Subtype: :Type3, Encoding: :WinAnsiEncoding,
|
11
11
|
FirstChar: 32, LastChar: 34, Widths: [600, 0, 700],
|
12
|
-
FontBBox: [0,
|
12
|
+
FontBBox: [0, 100, 100, 0], FontMatrix: [0.002, 0, 0, 0.002, 0, 0],
|
13
13
|
CharProcs: {}})
|
14
14
|
end
|
15
15
|
|
16
|
+
describe "bounding_box" do
|
17
|
+
it "returns the font's bounding box" do
|
18
|
+
assert_equal([0, 0, 100, 100], @font.bounding_box)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "inverts the y-values if necessary based on /FontMatrix" do
|
22
|
+
@font[:FontMatrix][3] *= -1
|
23
|
+
assert_equal([0, -100, 100, 0], @font.bounding_box)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns the glyph scaling factor" do
|
28
|
+
assert_equal(0.002, @font.glyph_scaling_factor)
|
29
|
+
end
|
30
|
+
|
16
31
|
describe "validation" do
|
17
32
|
it "works for valid objects" do
|
18
33
|
assert(@font.validate)
|