hexapdf 0.47.0 → 1.0.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 +65 -16
- data/lib/hexapdf/cli.rb +14 -1
- data/lib/hexapdf/composer.rb +7 -0
- data/lib/hexapdf/configuration.rb +2 -0
- data/lib/hexapdf/content/parser.rb +3 -1
- data/lib/hexapdf/digital_signature/cms_handler.rb +13 -0
- data/lib/hexapdf/digital_signature/signature.rb +1 -1
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +1 -0
- data/lib/hexapdf/document.rb +14 -3
- data/lib/hexapdf/font/cmap/writer.rb +58 -4
- data/lib/hexapdf/font/cmap.rb +7 -0
- data/lib/hexapdf/font/true_type_wrapper.rb +41 -16
- data/lib/hexapdf/layout/text_fragment.rb +2 -1
- data/lib/hexapdf/object.rb +1 -1
- data/lib/hexapdf/parser.rb +6 -2
- data/lib/hexapdf/reference.rb +1 -1
- data/lib/hexapdf/task/merge_acro_form.rb +164 -0
- data/lib/hexapdf/task.rb +1 -0
- data/lib/hexapdf/tokenizer.rb +2 -0
- data/lib/hexapdf/type/acro_form/form.rb +14 -27
- data/lib/hexapdf/type/acro_form/signature_field.rb +16 -6
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
- data/lib/hexapdf/type/actions/go_to.rb +1 -0
- data/lib/hexapdf/type/actions/go_to_r.rb +1 -0
- data/lib/hexapdf/type/actions/launch.rb +5 -1
- data/lib/hexapdf/type/annotation.rb +6 -1
- data/lib/hexapdf/type/annotations/markup_annotation.rb +14 -1
- data/lib/hexapdf/type/catalog.rb +3 -0
- data/lib/hexapdf/type/cid_font.rb +4 -1
- data/lib/hexapdf/type/file_specification.rb +17 -14
- data/lib/hexapdf/type/font_descriptor.rb +4 -3
- data/lib/hexapdf/type/font_simple.rb +3 -1
- data/lib/hexapdf/type/font_true_type.rb +2 -0
- data/lib/hexapdf/type/font_type0.rb +1 -1
- data/lib/hexapdf/type/font_type1.rb +7 -0
- data/lib/hexapdf/type/font_type3.rb +0 -1
- data/lib/hexapdf/type/form.rb +5 -2
- data/lib/hexapdf/type/graphics_state_parameter.rb +7 -4
- data/lib/hexapdf/type/image.rb +8 -4
- data/lib/hexapdf/type/info.rb +2 -2
- data/lib/hexapdf/type/mark_information.rb +2 -2
- data/lib/hexapdf/type/optional_content_configuration.rb +1 -1
- data/lib/hexapdf/type/optional_content_membership.rb +1 -1
- data/lib/hexapdf/type/page.rb +5 -3
- data/lib/hexapdf/type/resources.rb +6 -6
- data/lib/hexapdf/type/viewer_preferences.rb +4 -3
- data/lib/hexapdf/utils/sorted_tree_node.rb +12 -2
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +1 -0
- data/lib/hexapdf/xref_section.rb +20 -4
- data/test/hexapdf/common_tokenizer_tests.rb +5 -0
- data/test/hexapdf/digital_signature/signing/test_default_handler.rb +6 -0
- data/test/hexapdf/digital_signature/test_cms_handler.rb +12 -7
- data/test/hexapdf/digital_signature/test_signature.rb +7 -0
- data/test/hexapdf/digital_signature/test_signatures.rb +8 -3
- data/test/hexapdf/font/cmap/test_writer.rb +73 -16
- data/test/hexapdf/font/test_true_type_wrapper.rb +17 -3
- data/test/hexapdf/layout/test_list_box.rb +7 -7
- data/test/hexapdf/layout/test_text_fragment.rb +3 -3
- data/test/hexapdf/layout/test_text_layouter.rb +4 -2
- data/test/hexapdf/task/test_merge_acro_form.rb +104 -0
- data/test/hexapdf/test_composer.rb +8 -0
- data/test/hexapdf/test_document.rb +9 -0
- data/test/hexapdf/test_parser.rb +23 -6
- data/test/hexapdf/test_writer.rb +10 -5
- data/test/hexapdf/test_xref_section.rb +15 -0
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +18 -18
- data/test/hexapdf/type/acro_form/test_form.rb +7 -3
- data/test/hexapdf/type/actions/test_launch.rb +6 -2
- data/test/hexapdf/type/test_font_type1.rb +5 -0
- data/test/hexapdf/type/test_form.rb +1 -1
- data/test/hexapdf/type/test_page.rb +7 -1
- data/test/hexapdf/utils/test_sorted_tree_node.rb +7 -6
- metadata +4 -2
data/lib/hexapdf/type/image.rb
CHANGED
|
@@ -61,10 +61,10 @@ module HexaPDF
|
|
|
61
61
|
define_field :ColorSpace, type: [Symbol, PDFArray]
|
|
62
62
|
define_field :BitsPerComponent, type: Integer
|
|
63
63
|
define_field :Intent, type: Symbol, version: '1.1',
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
allowed_values: [HexaPDF::Content::RenderingIntent::ABSOLUTE_COLORIMETRIC,
|
|
65
|
+
HexaPDF::Content::RenderingIntent::RELATIVE_COLORIMETRIC,
|
|
66
|
+
HexaPDF::Content::RenderingIntent::SATURATION,
|
|
67
|
+
HexaPDF::Content::RenderingIntent::PERCEPTUAL]
|
|
68
68
|
define_field :ImageMask, type: Boolean, default: false
|
|
69
69
|
define_field :Mask, type: [Stream, PDFArray], version: '1.3'
|
|
70
70
|
define_field :Decode, type: PDFArray
|
|
@@ -72,11 +72,15 @@ module HexaPDF
|
|
|
72
72
|
define_field :Alternates, type: PDFArray, version: '1.3'
|
|
73
73
|
define_field :SMask, type: Stream, version: '1.4'
|
|
74
74
|
define_field :SMaskInData, type: Integer, version: '1.5', allowed_values: [0, 1, 2]
|
|
75
|
+
define_field :Name, type: Symbol
|
|
75
76
|
define_field :StructParent, type: Integer, version: '1.3'
|
|
76
77
|
define_field :ID, type: PDFByteString, version: '1.3'
|
|
77
78
|
define_field :OPI, type: Dictionary, version: '1.2'
|
|
78
79
|
define_field :Metadata, type: Stream, version: '1.4'
|
|
79
80
|
define_field :OC, type: Dictionary, version: '1.5'
|
|
81
|
+
define_field :AF, type: PDFArray, version: '2.0'
|
|
82
|
+
define_field :Measure, type: Dictionary, version: '2.0'
|
|
83
|
+
define_field :PtData, type: Dictionary, version: '2.0'
|
|
80
84
|
|
|
81
85
|
# Returns the source path that was used when creating the image object.
|
|
82
86
|
#
|
data/lib/hexapdf/type/info.rb
CHANGED
|
@@ -57,8 +57,8 @@ module HexaPDF
|
|
|
57
57
|
define_field :Producer, type: String
|
|
58
58
|
define_field :CreationDate, type: PDFDate
|
|
59
59
|
define_field :ModDate, type: PDFDate
|
|
60
|
-
define_field :Trapped, type: Symbol, version: '1.3',
|
|
61
|
-
|
|
60
|
+
define_field :Trapped, type: Symbol, version: '1.3', default: :Unknown,
|
|
61
|
+
allowed_values: [:True, :False, :Unknown]
|
|
62
62
|
|
|
63
63
|
# Info dictionaries must always be indirect.
|
|
64
64
|
def must_be_indirect?
|
|
@@ -48,8 +48,8 @@ module HexaPDF
|
|
|
48
48
|
define_type :XXMarkInformation
|
|
49
49
|
|
|
50
50
|
define_field :Marked, type: Boolean, default: false
|
|
51
|
-
define_field :UserProperties, type: Boolean, default: false
|
|
52
|
-
define_field :Suspects, type: Boolean, default: false
|
|
51
|
+
define_field :UserProperties, type: Boolean, default: false, version: '1.6'
|
|
52
|
+
define_field :Suspects, type: Boolean, default: false, version: '1.6'
|
|
53
53
|
|
|
54
54
|
end
|
|
55
55
|
|
|
@@ -76,7 +76,7 @@ module HexaPDF
|
|
|
76
76
|
define_field :AS, type: PDFArray
|
|
77
77
|
define_field :Order, type: PDFArray
|
|
78
78
|
define_field :ListMode, type: Symbol, default: :AllPages,
|
|
79
|
-
|
|
79
|
+
allowed_values: [:AllPages, :VisiblePages]
|
|
80
80
|
define_field :RBGroups, type: PDFArray
|
|
81
81
|
define_field :Locked, type: PDFArray, default: []
|
|
82
82
|
|
|
@@ -54,7 +54,7 @@ module HexaPDF
|
|
|
54
54
|
define_field :Type, type: Symbol, required: true, default: type
|
|
55
55
|
define_field :OCGs, type: [:OCG, PDFArray]
|
|
56
56
|
define_field :P, type: Symbol, default: :AnyOn,
|
|
57
|
-
|
|
57
|
+
allowed_values: [:AllOn, :AnyOn, :AnyOff, :AllOff]
|
|
58
58
|
define_field :VE, type: PDFArray
|
|
59
59
|
|
|
60
60
|
end
|
data/lib/hexapdf/type/page.rb
CHANGED
|
@@ -159,6 +159,9 @@ module HexaPDF
|
|
|
159
159
|
define_field :PresSteps, type: Dictionary, version: '1.5'
|
|
160
160
|
define_field :UserUnit, type: Numeric, version: '1.6'
|
|
161
161
|
define_field :VP, type: PDFArray, version: '1.6'
|
|
162
|
+
define_field :AF, type: PDFArray, version: '2.0'
|
|
163
|
+
define_field :OutputIntents, type: PDFArray, version: '2.0'
|
|
164
|
+
define_field :DPart, type: Dictionary, version: '2.0'
|
|
162
165
|
|
|
163
166
|
# Returns +true+ since page objects must always be indirect.
|
|
164
167
|
def must_be_indirect?
|
|
@@ -358,7 +361,7 @@ module HexaPDF
|
|
|
358
361
|
def contents
|
|
359
362
|
Array(self[:Contents]).each_with_object("".b) do |content_stream, content|
|
|
360
363
|
content << " " unless content.empty?
|
|
361
|
-
content << content_stream.stream
|
|
364
|
+
content << content_stream.stream if content_stream.kind_of?(Stream)
|
|
362
365
|
end
|
|
363
366
|
end
|
|
364
367
|
|
|
@@ -380,8 +383,7 @@ module HexaPDF
|
|
|
380
383
|
# Returns the, possibly inherited, resource dictionary which is automatically created if it
|
|
381
384
|
# doesn't exist.
|
|
382
385
|
def resources
|
|
383
|
-
self[:Resources] ||= document.wrap({
|
|
384
|
-
type: :XXResources)
|
|
386
|
+
self[:Resources] ||= document.wrap({}, type: :XXResources)
|
|
385
387
|
end
|
|
386
388
|
|
|
387
389
|
# Processes the content streams associated with the page with the given processor object.
|
|
@@ -49,13 +49,13 @@ module HexaPDF
|
|
|
49
49
|
|
|
50
50
|
define_type :XXResources
|
|
51
51
|
|
|
52
|
-
define_field :ExtGState,
|
|
52
|
+
define_field :ExtGState, type: Dictionary
|
|
53
53
|
define_field :ColorSpace, type: Dictionary
|
|
54
|
-
define_field :Pattern,
|
|
55
|
-
define_field :Shading,
|
|
56
|
-
define_field :XObject,
|
|
57
|
-
define_field :Font,
|
|
58
|
-
define_field :ProcSet,
|
|
54
|
+
define_field :Pattern, type: Dictionary
|
|
55
|
+
define_field :Shading, type: Dictionary, version: '1.3'
|
|
56
|
+
define_field :XObject, type: Dictionary
|
|
57
|
+
define_field :Font, type: Dictionary
|
|
58
|
+
define_field :ProcSet, type: PDFArray
|
|
59
59
|
define_field :Properties, type: Dictionary, version: '1.2'
|
|
60
60
|
|
|
61
61
|
# Returns the color space stored under the given name.
|
|
@@ -56,19 +56,20 @@ module HexaPDF
|
|
|
56
56
|
define_field :CenterWindow, type: Boolean, default: false
|
|
57
57
|
define_field :DisplayDocTitle, type: Boolean, default: false, version: '1.4'
|
|
58
58
|
define_field :NonFullScreenPageMode, type: Symbol, default: :UseNone,
|
|
59
|
-
|
|
59
|
+
allowed_values: [:UseNone, :UseOutlines, :UseThumbs, :UseOC]
|
|
60
60
|
define_field :Direction, type: Symbol, default: :L2R, version: '1.3',
|
|
61
|
-
|
|
61
|
+
allowed_values: [:L2R, :R2L]
|
|
62
62
|
define_field :ViewArea, type: Symbol, default: :CropBox, version: '1.4'
|
|
63
63
|
define_field :ViewClip, type: Symbol, default: :CropBox, version: '1.4'
|
|
64
64
|
define_field :PrintArea, type: Symbol, default: :CropBox, version: '1.4'
|
|
65
65
|
define_field :PrintClip, type: Symbol, default: :CropBox, version: '1.4'
|
|
66
66
|
define_field :PrintScaling, type: Symbol, default: :AppDefault, version: '1.6'
|
|
67
67
|
define_field :Duplex, type: Symbol, version: '1.7',
|
|
68
|
-
|
|
68
|
+
allowed_values: [:Simplex, :DuplexFlipShortEdge, :DuplexFlipLongEdge]
|
|
69
69
|
define_field :PickTrayByPDFSize, type: Boolean, version: '1.7'
|
|
70
70
|
define_field :PrintPageRange, type: PDFArray, version: '1.7'
|
|
71
71
|
define_field :NumCopies, type: Integer, version: '1.7'
|
|
72
|
+
define_field :Enforce, type: PDFArray, version: '2.0'
|
|
72
73
|
|
|
73
74
|
end
|
|
74
75
|
|
|
@@ -174,6 +174,7 @@ module HexaPDF
|
|
|
174
174
|
elsif node.key?(:Kids)
|
|
175
175
|
index = find_in_intermediate_node(node[:Kids], key)
|
|
176
176
|
node = node[:Kids][index]
|
|
177
|
+
node = document.wrap(node, type: self.class) if node
|
|
177
178
|
break unless node && key >= node[:Limits][0] && key <= node[:Limits][1]
|
|
178
179
|
else
|
|
179
180
|
break
|
|
@@ -194,7 +195,7 @@ module HexaPDF
|
|
|
194
195
|
container_name = leaf_node_container_name
|
|
195
196
|
stack = [self]
|
|
196
197
|
until stack.empty?
|
|
197
|
-
node = stack.pop
|
|
198
|
+
node = document.wrap(stack.pop, type: self.class)
|
|
198
199
|
if node.key?(container_name)
|
|
199
200
|
data = node[container_name]
|
|
200
201
|
index = 0
|
|
@@ -217,7 +218,7 @@ module HexaPDF
|
|
|
217
218
|
def path_to_key(node, key, stack)
|
|
218
219
|
return unless node.key?(:Kids)
|
|
219
220
|
index = find_in_intermediate_node(node[:Kids], key)
|
|
220
|
-
stack << node[:Kids][index]
|
|
221
|
+
stack << document.wrap(node[:Kids][index], type: self.class)
|
|
221
222
|
path_to_key(stack.last, key, stack)
|
|
222
223
|
end
|
|
223
224
|
|
|
@@ -307,6 +308,15 @@ module HexaPDF
|
|
|
307
308
|
super
|
|
308
309
|
container_name = leaf_node_container_name
|
|
309
310
|
|
|
311
|
+
if key?(:Kids)
|
|
312
|
+
self[:Kids].each do |kid|
|
|
313
|
+
unless kid.indirect?
|
|
314
|
+
yield("Children of sorted tree nodes must be indirect", true)
|
|
315
|
+
document.add(kid)
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
310
320
|
# All keys of the container must be lexically ordered strings and the container must be
|
|
311
321
|
# correctly formatted
|
|
312
322
|
if key?(container_name)
|
data/lib/hexapdf/version.rb
CHANGED
data/lib/hexapdf/writer.rb
CHANGED
|
@@ -149,6 +149,7 @@ module HexaPDF
|
|
|
149
149
|
obj_to_stm = object_streams.each_with_object({}) {|stm, m| m.update(stm.write_objects(rev)) }
|
|
150
150
|
|
|
151
151
|
xref_section = XRefSection.new
|
|
152
|
+
xref_section.mark_as_initial_section! unless previous_xref_pos
|
|
152
153
|
xref_section.add_free_entry(0, 65535) if previous_xref_pos.nil?
|
|
153
154
|
rev.each do |obj|
|
|
154
155
|
if obj.null?
|
data/lib/hexapdf/xref_section.rb
CHANGED
|
@@ -111,6 +111,13 @@ module HexaPDF
|
|
|
111
111
|
# used.
|
|
112
112
|
private :'[]='
|
|
113
113
|
|
|
114
|
+
# Marks this XRefSection object as being the first cross-reference section in a PDF file.
|
|
115
|
+
#
|
|
116
|
+
# This has the consequence that only a single sub-section is created.
|
|
117
|
+
def mark_as_initial_section!
|
|
118
|
+
@initial_section = true
|
|
119
|
+
end
|
|
120
|
+
|
|
114
121
|
# Adds an in-use entry to the cross-reference section.
|
|
115
122
|
#
|
|
116
123
|
# See: ::in_use_entry
|
|
@@ -147,15 +154,24 @@ module HexaPDF
|
|
|
147
154
|
# If this section contains no objects, a single empty array is yielded (corresponding to a
|
|
148
155
|
# subsection with zero elements).
|
|
149
156
|
#
|
|
150
|
-
# The subsections are dynamically generated based on the object numbers in this section.
|
|
157
|
+
# The subsections are dynamically generated based on the object numbers in this section. In case
|
|
158
|
+
# the section was marked as the initial section (see #mark_as_initial_section!) only a single
|
|
159
|
+
# subsection is yielded.
|
|
151
160
|
def each_subsection
|
|
152
161
|
return to_enum(__method__) unless block_given?
|
|
153
162
|
|
|
154
163
|
temp = []
|
|
155
164
|
oids.sort.each do |oid|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
expected_next_oid = !temp.empty? && temp[-1].oid + 1
|
|
166
|
+
if expected_next_oid && expected_next_oid != oid
|
|
167
|
+
if @initial_section
|
|
168
|
+
expected_next_oid.upto(oid - 1) do |free_oid|
|
|
169
|
+
temp << self.class.free_entry(free_oid, 0)
|
|
170
|
+
end
|
|
171
|
+
else
|
|
172
|
+
yield(temp)
|
|
173
|
+
temp = []
|
|
174
|
+
end
|
|
159
175
|
end
|
|
160
176
|
temp << self[oid]
|
|
161
177
|
end
|
|
@@ -104,6 +104,11 @@ module CommonTokenizerTests
|
|
|
104
104
|
assert_raises(HexaPDF::MalformedPDFError) { @tokenizer.next_token }
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
+
it "next_token: fails on a closing parenthesis that is not part of a literal string" do
|
|
108
|
+
create_tokenizer(" )")
|
|
109
|
+
assert_raises(HexaPDF::MalformedPDFError) { @tokenizer.next_token }
|
|
110
|
+
end
|
|
111
|
+
|
|
107
112
|
it "next_token: fails on a missing greater than sign in a hex string" do
|
|
108
113
|
create_tokenizer("<ABCD")
|
|
109
114
|
assert_raises(HexaPDF::MalformedPDFError) { @tokenizer.next_token }
|
|
@@ -157,6 +157,12 @@ describe HexaPDF::DigitalSignature::Signing::DefaultHandler do
|
|
|
157
157
|
assert_same(@obj, @doc.catalog[:Perms][:DocMDP])
|
|
158
158
|
end
|
|
159
159
|
|
|
160
|
+
it "updates the document version if :pades signing is used" do
|
|
161
|
+
@handler.signature_type = :pades
|
|
162
|
+
@handler.finalize_objects(@field, @obj)
|
|
163
|
+
assert_equal('2.0', @doc.version)
|
|
164
|
+
end
|
|
165
|
+
|
|
160
166
|
it "fails if DocMDP should be set but there is already a signature" do
|
|
161
167
|
@handler.doc_mdp_permissions = :no_changes
|
|
162
168
|
2.times do
|
|
@@ -62,7 +62,7 @@ describe HexaPDF::DigitalSignature::CMSHandler do
|
|
|
62
62
|
@dict.contents = @pkcs7.to_der
|
|
63
63
|
@handler = HexaPDF::DigitalSignature::CMSHandler.new(@dict)
|
|
64
64
|
result = @handler.verify(@store)
|
|
65
|
-
assert_equal(
|
|
65
|
+
assert_equal(3, result.messages.size)
|
|
66
66
|
assert_equal(:error, result.messages.first.type)
|
|
67
67
|
assert_match(/Exactly one signer needed/, result.messages.first.content)
|
|
68
68
|
end
|
|
@@ -100,13 +100,13 @@ describe HexaPDF::DigitalSignature::CMSHandler do
|
|
|
100
100
|
|
|
101
101
|
it "verifies the signature itself" do
|
|
102
102
|
result = @handler.verify(@store)
|
|
103
|
-
assert_equal(:info, result.messages.
|
|
104
|
-
assert_match(/Signature valid/, result.messages.
|
|
103
|
+
assert_equal(:info, result.messages[-2].type)
|
|
104
|
+
assert_match(/Signature valid/, result.messages[-2].content)
|
|
105
105
|
|
|
106
106
|
@dict.signed_data = 'other data'
|
|
107
107
|
result = @handler.verify(@store)
|
|
108
|
-
assert_equal(:error, result.messages.
|
|
109
|
-
assert_match(/Signature verification failed/, result.messages.
|
|
108
|
+
assert_equal(:error, result.messages[-2].type)
|
|
109
|
+
assert_match(/Signature verification failed/, result.messages[-2].content)
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
it "verifies a timestamp signature" do
|
|
@@ -125,8 +125,13 @@ describe HexaPDF::DigitalSignature::CMSHandler do
|
|
|
125
125
|
@handler = HexaPDF::DigitalSignature::CMSHandler.new(@dict)
|
|
126
126
|
|
|
127
127
|
result = @handler.verify(@store)
|
|
128
|
-
assert_equal(:info, result.messages.
|
|
129
|
-
assert_match(/Signature valid/, result.messages.
|
|
128
|
+
assert_equal(:info, result.messages[-2].type)
|
|
129
|
+
assert_match(/Signature valid/, result.messages[-2].content)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it "provides information on the certificate chain" do
|
|
133
|
+
result = @handler.verify(@store)
|
|
134
|
+
assert_match(/RSA signer -> HexaPDF Test Root CA/, result.messages.last.content)
|
|
130
135
|
end
|
|
131
136
|
end
|
|
132
137
|
|
|
@@ -111,6 +111,13 @@ describe HexaPDF::DigitalSignature::Signature do
|
|
|
111
111
|
assert_equal((MINIMAL_PDF[0, 400] << MINIMAL_PDF[500, 333]).b, @sig.signed_data)
|
|
112
112
|
end
|
|
113
113
|
|
|
114
|
+
it "works for invalid offsets" do
|
|
115
|
+
doc = HexaPDF::Document.new(io: StringIO.new(MINIMAL_PDF))
|
|
116
|
+
@sig.document = doc
|
|
117
|
+
@sig[:ByteRange] = [0, 400, 9000, 333]
|
|
118
|
+
assert_equal(MINIMAL_PDF[0, 400], @sig.signed_data)
|
|
119
|
+
end
|
|
120
|
+
|
|
114
121
|
it "fails if the document isn't associated with an existing PDF file" do
|
|
115
122
|
assert_raises(HexaPDF::Error) { @sig.signed_data }
|
|
116
123
|
end
|
|
@@ -70,7 +70,8 @@ describe HexaPDF::DigitalSignature::Signatures do
|
|
|
70
70
|
end
|
|
71
71
|
@doc.signatures.add(@io, @handler, write_options: {update_fields: false})
|
|
72
72
|
sig = @doc.signatures.first
|
|
73
|
-
assert_equal([0, 925, 925 + sig[:Contents].size * 2 + 2,
|
|
73
|
+
assert_equal([0, 925, 925 + sig[:Contents].size * 2 + 2, 2455 + HexaPDF::VERSION.length],
|
|
74
|
+
sig[:ByteRange].value)
|
|
74
75
|
assert_equal(:sig, sig[:key])
|
|
75
76
|
assert_equal(:sig_field, @doc.acro_form.each_field.first[:key])
|
|
76
77
|
assert(sig.key?(:Contents))
|
|
@@ -132,14 +133,18 @@ describe HexaPDF::DigitalSignature::Signatures do
|
|
|
132
133
|
it "handles different xref section types correctly when determing the offsets" do
|
|
133
134
|
@doc.delete(7)
|
|
134
135
|
sig = @doc.signatures.add(@io, @handler, write_options: {update_fields: false})
|
|
135
|
-
|
|
136
|
+
l1 = 1030 + HexaPDF::VERSION.length
|
|
137
|
+
assert_equal([0, l1, l1 + sig[:Contents].size * 2 + 2, 2437 + HexaPDF::VERSION.length],
|
|
138
|
+
sig[:ByteRange].value)
|
|
136
139
|
end
|
|
137
140
|
|
|
138
141
|
it "works if the signature object is the last object of the xref section" do
|
|
139
142
|
field = @doc.acro_form(create: true).create_signature_field('Signature2')
|
|
140
143
|
field.create_widget(@doc.pages[0], Rect: [0, 0, 0, 0])
|
|
141
144
|
sig = @doc.signatures.add(@io, @handler, signature: field, write_options: {update_fields: false})
|
|
142
|
-
|
|
145
|
+
l1 = 3097 + HexaPDF::VERSION.length
|
|
146
|
+
assert_equal([0, l1, l1 + sig[:Contents].size * 2 + 2, 374 + HexaPDF::VERSION.length],
|
|
147
|
+
sig[:ByteRange].value)
|
|
143
148
|
end
|
|
144
149
|
|
|
145
150
|
it "allows writing to a file in addition to writing to an IO" do
|
|
@@ -5,7 +5,7 @@ require 'hexapdf/font/cmap/writer'
|
|
|
5
5
|
|
|
6
6
|
describe HexaPDF::Font::CMap::Writer do
|
|
7
7
|
before do
|
|
8
|
-
@
|
|
8
|
+
@to_unicode_cmap_data = <<~EOF
|
|
9
9
|
/CIDInit /ProcSet findresource begin
|
|
10
10
|
12 dict begin
|
|
11
11
|
begincmap
|
|
@@ -32,35 +32,92 @@ describe HexaPDF::Font::CMap::Writer do
|
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
EOF
|
|
35
|
-
@
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
@cid_cmap_data = <<~EOF
|
|
36
|
+
%!PS-Adobe-3.0 Resource-CMap
|
|
37
|
+
%%DocumentNeededResources: ProcSet (CIDInit)
|
|
38
|
+
%%IncludeResource: ProcSet (CIDInit)
|
|
39
|
+
%%BeginResource: CMap (Custom)
|
|
40
|
+
%%Title: (Custom Adobe Identity 0)
|
|
41
|
+
%%Version: 1
|
|
42
|
+
/CIDInit /ProcSet findresource begin
|
|
43
|
+
12 dict begin
|
|
44
|
+
begincmap
|
|
45
|
+
/CIDSystemInfo 3 dict dup begin
|
|
46
|
+
/Registry (Adobe) def
|
|
47
|
+
/Ordering (Identity) def
|
|
48
|
+
/Supplement 0 def
|
|
49
|
+
end def
|
|
50
|
+
/CMapName /Custom def
|
|
51
|
+
/CMapType 1 def
|
|
52
|
+
/CMapVersion 1 def
|
|
53
|
+
/WMode 0 def
|
|
54
|
+
1 begincodespacerange
|
|
55
|
+
<0000> <FFFF>
|
|
56
|
+
endcodespacerange
|
|
57
|
+
1 begincidchar
|
|
58
|
+
<0060> 144
|
|
59
|
+
endcidchar
|
|
60
|
+
1 begincidrange
|
|
61
|
+
<0000><005E> 32
|
|
62
|
+
endcidrange
|
|
63
|
+
endcmap
|
|
64
|
+
CMapName currentdict /CMap defineresource pop
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
%%EndResource
|
|
68
|
+
%%EOF
|
|
69
|
+
EOF
|
|
70
|
+
|
|
71
|
+
@to_unicode_mapping = []
|
|
72
|
+
@cid_mapping = []
|
|
73
|
+
0x00.upto(0x5e) do |i|
|
|
74
|
+
@to_unicode_mapping << [i, 0x20 + i]
|
|
75
|
+
@cid_mapping << [i, 0x20 + i]
|
|
76
|
+
end
|
|
77
|
+
@to_unicode_mapping << [0x60, 0x90]
|
|
78
|
+
@cid_mapping << [0x60, 0x90]
|
|
79
|
+
0x1379.upto(0x137B) do |i|
|
|
80
|
+
@to_unicode_mapping << [i, 0x90FE + i - 0x1379]
|
|
81
|
+
end
|
|
82
|
+
@to_unicode_mapping << [0x3A51, 0x2003E]
|
|
40
83
|
end
|
|
41
84
|
|
|
42
85
|
describe "create_to_unicode_cmap" do
|
|
43
86
|
it "creates a correct CMap file" do
|
|
44
|
-
assert_equal(@
|
|
87
|
+
assert_equal(@to_unicode_cmap_data,
|
|
88
|
+
HexaPDF::Font::CMap.create_to_unicode_cmap(@to_unicode_mapping))
|
|
45
89
|
end
|
|
46
90
|
|
|
47
91
|
it "works if the last item is a range" do
|
|
48
|
-
@
|
|
49
|
-
@
|
|
50
|
-
@
|
|
51
|
-
assert_equal(@
|
|
92
|
+
@to_unicode_mapping.pop
|
|
93
|
+
@to_unicode_cmap_data.sub!(/2 beginbfchar/, '1 beginbfchar')
|
|
94
|
+
@to_unicode_cmap_data.sub!(/<3A51><d840dc3e>\n/, '')
|
|
95
|
+
assert_equal(@to_unicode_cmap_data,
|
|
96
|
+
HexaPDF::Font::CMap.create_to_unicode_cmap(@to_unicode_mapping))
|
|
52
97
|
end
|
|
53
98
|
|
|
54
99
|
it "works with only ranges" do
|
|
55
|
-
@
|
|
56
|
-
@
|
|
57
|
-
@
|
|
58
|
-
assert_equal(@
|
|
100
|
+
@to_unicode_mapping.delete_at(-1)
|
|
101
|
+
@to_unicode_mapping.delete_at(0x5f)
|
|
102
|
+
@to_unicode_cmap_data.sub!(/\n2 beginbfchar.*endbfchar/m, '')
|
|
103
|
+
assert_equal(@to_unicode_cmap_data,
|
|
104
|
+
HexaPDF::Font::CMap.create_to_unicode_cmap(@to_unicode_mapping))
|
|
59
105
|
end
|
|
60
106
|
|
|
61
107
|
it "returns an empty CMap if the mapping is empty" do
|
|
62
|
-
assert_equal(@
|
|
108
|
+
assert_equal(@to_unicode_cmap_data.sub(/\d+ beginbfchar.*endbfrange/m, ''),
|
|
63
109
|
HexaPDF::Font::CMap.create_to_unicode_cmap([]))
|
|
64
110
|
end
|
|
65
111
|
end
|
|
112
|
+
|
|
113
|
+
describe "create_cid_cmap" do
|
|
114
|
+
it "creates a correct CMap file" do
|
|
115
|
+
assert_equal(@cid_cmap_data, HexaPDF::Font::CMap.create_cid_cmap(@cid_mapping))
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "returns an empty CMap if the mapping is empty" do
|
|
119
|
+
assert_equal(@cid_cmap_data.sub(/\d+ begincidchar.*endcidrange/m, ''),
|
|
120
|
+
HexaPDF::Font::CMap.create_cid_cmap([]))
|
|
121
|
+
end
|
|
122
|
+
end
|
|
66
123
|
end
|
|
@@ -71,6 +71,12 @@ describe HexaPDF::Font::TrueTypeWrapper do
|
|
|
71
71
|
glyph.inspect)
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
it "caches glyphs based on the id and string" do
|
|
75
|
+
glyph = @font_wrapper.glyph(17)
|
|
76
|
+
assert_same(glyph, @font_wrapper.glyph(17))
|
|
77
|
+
refute_same(glyph, @font_wrapper.glyph(17, "1"))
|
|
78
|
+
end
|
|
79
|
+
|
|
74
80
|
it "invokes font.on_missing_glyph for missing glyphs" do
|
|
75
81
|
glyph = @font_wrapper.glyph(9999)
|
|
76
82
|
assert_kind_of(HexaPDF::Font::InvalidGlyph, glyph)
|
|
@@ -99,14 +105,18 @@ describe HexaPDF::Font::TrueTypeWrapper do
|
|
|
99
105
|
assert_equal([1].pack('n'), code)
|
|
100
106
|
code = @font_wrapper.encode(@font_wrapper.glyph(10))
|
|
101
107
|
assert_equal([2].pack('n'), code)
|
|
108
|
+
code = @font_wrapper.encode(@font_wrapper.glyph(10, "o"))
|
|
109
|
+
assert_equal([3].pack('n'), code)
|
|
102
110
|
end
|
|
103
111
|
|
|
104
112
|
it "returns the encoded glyph ID for fonts that are not subset" do
|
|
105
113
|
@font_wrapper = HexaPDF::Font::TrueTypeWrapper.new(@doc, @font, subset: false)
|
|
106
114
|
code = @font_wrapper.encode(@font_wrapper.glyph(3))
|
|
107
|
-
assert_equal([
|
|
115
|
+
assert_equal([1].pack('n'), code)
|
|
108
116
|
code = @font_wrapper.encode(@font_wrapper.glyph(10))
|
|
109
|
-
assert_equal([
|
|
117
|
+
assert_equal([2].pack('n'), code)
|
|
118
|
+
code = @font_wrapper.encode(@font_wrapper.glyph(10, "o"))
|
|
119
|
+
assert_equal([3].pack('n'), code)
|
|
110
120
|
end
|
|
111
121
|
|
|
112
122
|
it "raises an error if an InvalidGlyph is encoded" do
|
|
@@ -180,14 +190,18 @@ describe HexaPDF::Font::TrueTypeWrapper do
|
|
|
180
190
|
it "with fonts that are not subset (only differences to other case)" do
|
|
181
191
|
@font_wrapper = HexaPDF::Font::TrueTypeWrapper.new(@doc, @font, subset: false)
|
|
182
192
|
@font_wrapper.encode(@font_wrapper.glyph(3))
|
|
193
|
+
@font_wrapper.encode(@font_wrapper.glyph(3, "-"))
|
|
183
194
|
glyph = @font_wrapper.decode_utf8('H').first
|
|
184
195
|
@font_wrapper.encode(glyph)
|
|
185
196
|
@doc.dispatch_message(:complete_objects)
|
|
186
197
|
|
|
187
198
|
dict = @font_wrapper.pdf_object
|
|
188
199
|
|
|
189
|
-
assert_equal(HexaPDF::Font::CMap.create_to_unicode_cmap([[
|
|
200
|
+
assert_equal(HexaPDF::Font::CMap.create_to_unicode_cmap([[1, ' '.ord], [2, '-'.ord],
|
|
201
|
+
[3, 'H'.ord]]),
|
|
190
202
|
dict[:ToUnicode].stream)
|
|
203
|
+
assert_equal(HexaPDF::Font::CMap.create_cid_cmap([[1, 3], [2, 3], [3, glyph.id]]),
|
|
204
|
+
dict[:Encoding].stream)
|
|
191
205
|
assert_equal([glyph.id, [glyph.width]], dict[:DescendantFonts][0][:W].value)
|
|
192
206
|
end
|
|
193
207
|
end
|
|
@@ -185,7 +185,7 @@ describe HexaPDF::Layout::ListBox do
|
|
|
185
185
|
[:set_font_and_size, [:F1, 11]],
|
|
186
186
|
[:set_device_gray_non_stroking_color, [0.5]],
|
|
187
187
|
[:begin_text],
|
|
188
|
-
[:
|
|
188
|
+
[:move_text, [1.15, 92.487]],
|
|
189
189
|
[:show_text, ["\x95".b]],
|
|
190
190
|
[:end_text],
|
|
191
191
|
[:restore_graphics_state],
|
|
@@ -197,7 +197,7 @@ describe HexaPDF::Layout::ListBox do
|
|
|
197
197
|
[:set_font_and_size, [:F1, 11]],
|
|
198
198
|
[:set_device_gray_non_stroking_color, [0.5]],
|
|
199
199
|
[:begin_text],
|
|
200
|
-
[:
|
|
200
|
+
[:move_text, [1.15, 82.487]],
|
|
201
201
|
[:show_text, ["\x95".b]],
|
|
202
202
|
[:end_text],
|
|
203
203
|
[:restore_graphics_state],
|
|
@@ -219,7 +219,7 @@ describe HexaPDF::Layout::ListBox do
|
|
|
219
219
|
[:set_text_rise, [-6.111111]],
|
|
220
220
|
[:set_device_gray_non_stroking_color, [0.5]],
|
|
221
221
|
[:begin_text],
|
|
222
|
-
[:
|
|
222
|
+
[:move_text, [0.1985, 100]],
|
|
223
223
|
[:show_text, ["m".b]],
|
|
224
224
|
[:end_text],
|
|
225
225
|
[:restore_graphics_state],
|
|
@@ -241,7 +241,7 @@ describe HexaPDF::Layout::ListBox do
|
|
|
241
241
|
[:set_text_rise, [-6.111111]],
|
|
242
242
|
[:set_device_gray_non_stroking_color, [0.5]],
|
|
243
243
|
[:begin_text],
|
|
244
|
-
[:
|
|
244
|
+
[:move_text, [0.8145, 100]],
|
|
245
245
|
[:show_text, ["n".b]],
|
|
246
246
|
[:end_text],
|
|
247
247
|
[:restore_graphics_state],
|
|
@@ -263,7 +263,7 @@ describe HexaPDF::Layout::ListBox do
|
|
|
263
263
|
[:set_font_and_size, [:F1, 11]],
|
|
264
264
|
[:set_device_gray_non_stroking_color, [0.5]],
|
|
265
265
|
[:begin_text],
|
|
266
|
-
[:
|
|
266
|
+
[:move_text, [6.75, 92.487]],
|
|
267
267
|
[:show_text, ["1.".b]],
|
|
268
268
|
[:end_text],
|
|
269
269
|
[:restore_graphics_state],
|
|
@@ -275,7 +275,7 @@ describe HexaPDF::Layout::ListBox do
|
|
|
275
275
|
[:set_font_and_size, [:F1, 11]],
|
|
276
276
|
[:set_device_gray_non_stroking_color, [0.5]],
|
|
277
277
|
[:begin_text],
|
|
278
|
-
[:
|
|
278
|
+
[:move_text, [6.75, 82.487]],
|
|
279
279
|
[:show_text, ["2.".b]],
|
|
280
280
|
[:end_text],
|
|
281
281
|
[:restore_graphics_state],
|
|
@@ -314,7 +314,7 @@ describe HexaPDF::Layout::ListBox do
|
|
|
314
314
|
[:save_graphics_state],
|
|
315
315
|
[:set_font_and_size, [:F1, 10]],
|
|
316
316
|
[:begin_text],
|
|
317
|
-
[:
|
|
317
|
+
[:move_text, [1.5, 93.17]],
|
|
318
318
|
[:show_text, ["\x95".b]],
|
|
319
319
|
[:end_text],
|
|
320
320
|
[:restore_graphics_state],
|
|
@@ -139,7 +139,7 @@ describe HexaPDF::Layout::TextFragment do
|
|
|
139
139
|
[:set_text_rise, [2]],
|
|
140
140
|
*middle,
|
|
141
141
|
[:begin_text],
|
|
142
|
-
[:
|
|
142
|
+
[:move_text, [10, 15]],
|
|
143
143
|
[:show_text, ['!']],
|
|
144
144
|
*back,
|
|
145
145
|
].compact
|
|
@@ -156,7 +156,7 @@ describe HexaPDF::Layout::TextFragment do
|
|
|
156
156
|
@canvas = @doc.pages.add.canvas
|
|
157
157
|
@fragment.draw(@canvas, 10, 15, ignore_text_properties: true)
|
|
158
158
|
assert_operators(@canvas.contents, [[:begin_text],
|
|
159
|
-
[:
|
|
159
|
+
[:move_text, [10, 15]]])
|
|
160
160
|
end
|
|
161
161
|
|
|
162
162
|
describe "uses an appropriate text position setter" do
|
|
@@ -188,7 +188,7 @@ describe HexaPDF::Layout::TextFragment do
|
|
|
188
188
|
it "horizontal and vertical movement" do
|
|
189
189
|
@fragment.draw(@canvas, 10, 10, ignore_text_properties: true)
|
|
190
190
|
assert_operators(@canvas.contents, [[:begin_text],
|
|
191
|
-
[:
|
|
191
|
+
[:move_text, [10, 10]]])
|
|
192
192
|
end
|
|
193
193
|
end
|
|
194
194
|
|
|
@@ -743,10 +743,12 @@ describe HexaPDF::Layout::TextLayouter do
|
|
|
743
743
|
result = processor.recorded_ops
|
|
744
744
|
leading = (result.select {|name, _| name == :set_leading } || [0]).map(&:last).flatten.first
|
|
745
745
|
pos = [0, 0]
|
|
746
|
-
result.select!
|
|
747
|
-
|
|
746
|
+
result.select! do |name, _|
|
|
747
|
+
name == :set_text_matrix || name == :move_text || name == :move_text_next_line
|
|
748
|
+
end.map! do |name, ops|
|
|
748
749
|
case name
|
|
749
750
|
when :set_text_matrix then pos = ops[-2, 2]
|
|
751
|
+
when :move_text then pos = ops
|
|
750
752
|
when :move_text_next_line then pos[1] -= leading
|
|
751
753
|
end
|
|
752
754
|
pos.dup
|