hexapdf 0.46.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +83 -16
- data/lib/hexapdf/composer.rb +7 -0
- data/lib/hexapdf/configuration.rb +13 -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/encryption/standard_security_handler.rb +32 -26
- 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/importer.rb +1 -1
- data/lib/hexapdf/layout/table_box.rb +57 -10
- data/lib/hexapdf/layout/text_fragment.rb +2 -1
- data/lib/hexapdf/object.rb +1 -1
- data/lib/hexapdf/parser.rb +1 -1
- data/lib/hexapdf/reference.rb +1 -1
- data/lib/hexapdf/task/merge_acro_form.rb +164 -0
- data/lib/hexapdf/task/optimize.rb +4 -4
- data/lib/hexapdf/task.rb +1 -0
- data/lib/hexapdf/tokenizer.rb +2 -0
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +8 -4
- data/lib/hexapdf/type/acro_form/form.rb +14 -24
- data/lib/hexapdf/type/acro_form/signature_field.rb +18 -7
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +12 -4
- 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/annotations/widget.rb +4 -2
- 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/version.rb +1 -1
- data/lib/hexapdf/writer.rb +1 -0
- data/test/data/standard-security-handler/bothpwd-aes-256bit-V5-R5.pdf +43 -0
- data/test/data/standard-security-handler/nopwd-aes-256bit-V5-R5.pdf +44 -0
- data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5-R5.pdf +43 -0
- data/test/data/standard-security-handler/userpwd-aes-256bit-V5-R5.pdf +0 -0
- 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 +12 -7
- data/test/hexapdf/encryption/test_standard_security_handler.rb +5 -2
- 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_table_box.rb +52 -0
- 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/task/test_optimize.rb +2 -0
- data/test/hexapdf/test_composer.rb +8 -0
- data/test/hexapdf/test_document.rb +12 -3
- data/test/hexapdf/test_importer.rb +7 -0
- data/test/hexapdf/test_parser.rb +7 -0
- data/test/hexapdf/test_writer.rb +19 -5
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +40 -23
- data/test/hexapdf/type/acro_form/test_form.rb +7 -8
- data/test/hexapdf/type/acro_form/test_signature_field.rb +3 -1
- data/test/hexapdf/type/acro_form/test_variable_text_field.rb +14 -1
- data/test/hexapdf/type/actions/test_launch.rb +6 -2
- data/test/hexapdf/type/annotations/test_widget.rb +4 -0
- 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
- metadata +8 -2
@@ -0,0 +1,104 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'hexapdf/document'
|
5
|
+
require 'hexapdf/task/merge_acro_form'
|
6
|
+
|
7
|
+
describe HexaPDF::Task::MergeAcroForm do
|
8
|
+
before do
|
9
|
+
@doc = HexaPDF::Document.new
|
10
|
+
@doc.pages.add
|
11
|
+
@doc.pages.add
|
12
|
+
form = @doc.acro_form(create: true)
|
13
|
+
field = form.create_text_field("Text")
|
14
|
+
field.create_widget(@doc.pages[0], Rect: [0, 0, 0, 0])
|
15
|
+
field.create_widget(@doc.pages[1], Rect: [0, 0, 0, 0])
|
16
|
+
|
17
|
+
form.create_text_field("Calc.Field a")
|
18
|
+
form.create_text_field("Calc.Field b")
|
19
|
+
field = form.create_text_field('Other.Calculation 1')
|
20
|
+
field.set_calculate_action(:sum, fields: ["Calc.Field a", "Calc.Field b"])
|
21
|
+
field.create_widget(@doc.pages[1])
|
22
|
+
field = form.create_text_field('Other.Calculation 2')
|
23
|
+
field.set_calculate_action(:sfn, fields: "Calc.Field\\ a + Calc.Field\\ b")
|
24
|
+
field.create_widget(@doc.pages[1])
|
25
|
+
|
26
|
+
@root_fields = @doc.acro_form.root_fields
|
27
|
+
|
28
|
+
@doc.dispatch_message(:complete_objects)
|
29
|
+
@doc.validate
|
30
|
+
@doc1 = @doc.duplicate
|
31
|
+
@pages = []
|
32
|
+
@pages << @doc.pages.add(@doc.import(@doc1.pages[0]))
|
33
|
+
@pages << @doc.pages.add(@doc.import(@doc1.pages[1]))
|
34
|
+
end
|
35
|
+
|
36
|
+
it "selects a unique name for the root field" do
|
37
|
+
@doc.task(:merge_acro_form, source: @doc1, pages: @pages)
|
38
|
+
assert_equal('merged_1', @root_fields[3][:T])
|
39
|
+
|
40
|
+
@root_fields << @doc.wrap({T: 'merged_23'})
|
41
|
+
@doc.task(:merge_acro_form, source: @doc1, pages: @pages)
|
42
|
+
assert_equal('merged_24', @root_fields[5][:T])
|
43
|
+
end
|
44
|
+
|
45
|
+
it "merges the /DR entry of the main AcroForm dictionary" do
|
46
|
+
@doc.task(:merge_acro_form, source: @doc1, pages: @pages)
|
47
|
+
assert(@doc.acro_form.default_resources[:Font].key?(:F2))
|
48
|
+
end
|
49
|
+
|
50
|
+
it "updates the /SigFlags if necessary" do
|
51
|
+
@doc.task(:merge_acro_form, source: @doc1, pages: [@pages[0]])
|
52
|
+
refute(@doc.acro_form.signature_flag?(:signatures_exist))
|
53
|
+
|
54
|
+
@pages[0][:Annots][0].form_field[:FT] = :Sig
|
55
|
+
@doc.task(:merge_acro_form, source: @doc1, pages: [@pages[0]])
|
56
|
+
refute(@doc.acro_form.signature_flag?(:signatures_exist))
|
57
|
+
|
58
|
+
@doc1.acro_form.signature_flag(:signatures_exist)
|
59
|
+
@doc.task(:merge_acro_form, source: @doc1, pages: [@pages[0]])
|
60
|
+
assert(@doc.acro_form.signature_flag?(:signatures_exist))
|
61
|
+
end
|
62
|
+
|
63
|
+
it "applies the /DA and /Q entries of the source AcroForm to the created root field" do
|
64
|
+
@doc1.acro_form.set_default_appearance_string
|
65
|
+
@doc1.acro_form[:Q] = @doc1.add(5)
|
66
|
+
@doc.task(:merge_acro_form, source: @doc1, pages: [@pages[0]])
|
67
|
+
assert_equal('0.0 g /F2 0 Tf', @root_fields[3][:DA])
|
68
|
+
assert_equal(5, @root_fields[3][:Q])
|
69
|
+
end
|
70
|
+
|
71
|
+
it "merges only the fields references in the given pages" do
|
72
|
+
@doc.task(:merge_acro_form, source: @doc1, pages: [@pages[0]])
|
73
|
+
assert_equal('merged_1', @root_fields[3][:T])
|
74
|
+
assert_equal(1, @root_fields[3][:Kids].size)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "only merges fields that have at least one widget" do
|
78
|
+
@doc.task(:merge_acro_form, source: @doc1, pages: @pages)
|
79
|
+
assert_equal(2, @root_fields[3][:Kids].size)
|
80
|
+
assert_nil(@doc.acro_form.field_by_name('merged_1.Calc'))
|
81
|
+
end
|
82
|
+
|
83
|
+
it "updates the /DA entries of widgets and fields" do
|
84
|
+
@pages[0][:Annots][0][:DA] = '/F1 10 Tf'
|
85
|
+
@doc.task(:merge_acro_form, source: @doc1, pages: @pages)
|
86
|
+
field = @doc.acro_form.field_by_name('merged_1.Text')
|
87
|
+
assert_equal('0.0 g /F2 0 Tf', field[:DA])
|
88
|
+
assert_equal('/F2 10 Tf', field.each_widget.to_a[0][:DA])
|
89
|
+
end
|
90
|
+
|
91
|
+
it "doesn't update the calculation actions if no field with one is merged" do
|
92
|
+
@doc.task(:merge_acro_form, source: @doc1, pages: [@pages[0]])
|
93
|
+
assert_equal(2, @doc.acro_form[:CO].size)
|
94
|
+
end
|
95
|
+
|
96
|
+
it "updates the field names in known calculation actions" do
|
97
|
+
@doc.task(:merge_acro_form, source: @doc1, pages: @pages)
|
98
|
+
assert_equal(4, @doc.acro_form[:CO].size)
|
99
|
+
js = @doc.acro_form.field_by_name('merged_1.Other.Calculation 1')[:AA][:C][:JS]
|
100
|
+
assert_match(/merged_1.Calc.Field a/, js)
|
101
|
+
js = @doc.acro_form.field_by_name('merged_1.Other.Calculation 2')[:AA][:C][:JS]
|
102
|
+
assert_match(/merged_1.Calc.Field a/, js)
|
103
|
+
end
|
104
|
+
end
|
@@ -8,6 +8,7 @@ describe HexaPDF::Task::Optimize do
|
|
8
8
|
class TestType < HexaPDF::Dictionary
|
9
9
|
|
10
10
|
define_type :Test
|
11
|
+
define_field :Type, type: Symbol, default: type
|
11
12
|
define_field :Optional, type: Symbol, default: :Optional
|
12
13
|
|
13
14
|
end
|
@@ -46,6 +47,7 @@ describe HexaPDF::Task::Optimize do
|
|
46
47
|
end
|
47
48
|
|
48
49
|
def assert_default_deleted
|
50
|
+
assert(@doc.object(1).key?(:Type))
|
49
51
|
refute(@doc.object(1).key?(:Optional))
|
50
52
|
end
|
51
53
|
|
@@ -63,6 +63,14 @@ describe HexaPDF::Composer do
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
+
it "writes the document to a string" do
|
67
|
+
pdf = HexaPDF::Composer.new
|
68
|
+
pdf.new_page
|
69
|
+
str = pdf.write_to_string
|
70
|
+
doc = HexaPDF::Document.new(io: StringIO.new(str))
|
71
|
+
assert_equal(2, doc.pages.count)
|
72
|
+
end
|
73
|
+
|
66
74
|
describe "new_page" do
|
67
75
|
it "creates a new page" do
|
68
76
|
c = HexaPDF::Composer.new(page_size: [0, 0, 50, 100], margin: 10)
|
@@ -497,10 +497,10 @@ describe HexaPDF::Document do
|
|
497
497
|
|
498
498
|
it "returns all signature fields of the document" do
|
499
499
|
form = @doc.acro_form(create: true)
|
500
|
-
sig1 = @doc.add({FT: :Sig, T: 'sig1', V: :sig1})
|
501
|
-
sig2 = @doc.add({FT: :Sig, T: 'sig2', V: :sig2})
|
500
|
+
sig1 = @doc.add({FT: :Sig, T: 'sig1', V: {k: :sig1}})
|
501
|
+
sig2 = @doc.add({FT: :Sig, T: 'sig2', V: {k: :sig2}})
|
502
502
|
form.root_fields << sig1 << sig2
|
503
|
-
assert_equal([:sig1, :sig2], @doc.signatures.to_a)
|
503
|
+
assert_equal([{k: :sig1}, {k: :sig2}], @doc.signatures.to_a)
|
504
504
|
end
|
505
505
|
|
506
506
|
it "allows to conveniently sign a document" do
|
@@ -595,4 +595,13 @@ describe HexaPDF::Document do
|
|
595
595
|
refute(dupped.encrypted?)
|
596
596
|
end
|
597
597
|
end
|
598
|
+
|
599
|
+
it "writes the document to a string" do
|
600
|
+
doc = HexaPDF::Document.new
|
601
|
+
doc.trailer.info[:test] = :test
|
602
|
+
str = doc.write_to_string(update_fields: false)
|
603
|
+
assert_equal(Encoding::ASCII_8BIT, str.encoding)
|
604
|
+
doc = HexaPDF::Document.new(io: StringIO.new(str))
|
605
|
+
assert_equal(:test, doc.trailer.info[:test])
|
606
|
+
end
|
598
607
|
end
|
@@ -147,6 +147,13 @@ describe HexaPDF::Importer do
|
|
147
147
|
assert_nil(obj[:pages])
|
148
148
|
end
|
149
149
|
|
150
|
+
it "handles null values correctly" do
|
151
|
+
@source.add(@hash)
|
152
|
+
@source.delete(@hash)
|
153
|
+
obj = @importer.import(@obj)
|
154
|
+
assert_nil(obj[:hash])
|
155
|
+
end
|
156
|
+
|
150
157
|
it "imports Page objects correctly by copying the inherited values" do
|
151
158
|
page = @importer.import(@source.pages[0])
|
152
159
|
assert_equal(90, page[:Rotate])
|
data/test/hexapdf/test_parser.rb
CHANGED
@@ -152,6 +152,13 @@ describe HexaPDF::Parser do
|
|
152
152
|
assert_equal('12', collector(stream.fiber))
|
153
153
|
end
|
154
154
|
|
155
|
+
it "recovers from a non-existing indirect reference to a stream length value" do
|
156
|
+
create_parser("1 0 obj<</Length 2 0 R>> stream\n12(ab\nendstream endobj")
|
157
|
+
obj, _, _, stream = @parser.parse_indirect_object
|
158
|
+
assert_equal(5, obj[:Length])
|
159
|
+
assert_equal('12(ab', collector(stream.fiber))
|
160
|
+
end
|
161
|
+
|
155
162
|
it "works even if the keyword endobj is missing or mangled" do
|
156
163
|
create_parser("1 0 obj<</Length 4>>5")
|
157
164
|
object, * = @parser.parse_indirect_object
|
data/test/hexapdf/test_writer.rb
CHANGED
@@ -48,10 +48,15 @@ describe HexaPDF::Writer do
|
|
48
48
|
trailer
|
49
49
|
<</Size 4/Root<</Type/Catalog>>/Info 3 0 R/Prev 219>>
|
50
50
|
startxref
|
51
|
-
|
51
|
+
#{343 + HexaPDF::VERSION.length}
|
52
52
|
%%EOF
|
53
53
|
EOF
|
54
54
|
|
55
|
+
xref_stream = case HexaPDF::VERSION.length
|
56
|
+
when 5 then "x\xDAcbdlc``b`\xB0\x04\x93\x93\x19\x18\x00\f\x0F\x01["
|
57
|
+
when 6 then "x\xDAcbdlg``b`\xB0\x04\x93\x93\x18\x18\x00\f\e\x01["
|
58
|
+
else fail
|
59
|
+
end
|
55
60
|
@compressed_input_io = StringIO.new(<<~EOF.force_encoding(Encoding::BINARY))
|
56
61
|
%PDF-1.7
|
57
62
|
%\xCF\xEC\xFF\xE8\xD7\xCB\xCD
|
@@ -64,7 +69,7 @@ describe HexaPDF::Writer do
|
|
64
69
|
20
|
65
70
|
endobj
|
66
71
|
3 0 obj
|
67
|
-
<</Type/XRef/
|
72
|
+
<</Size 6/Type/XRef/W[1 1 2]/Index[0 4 5 1]/Filter/FlateDecode/DecodeParms<</Columns 4/Predictor 12>>/Length 31>>stream
|
68
73
|
x\xDAcb`\xF8\xFF\x9F\x89\x89\x95\x91\x91\xE9\x7F\x19\x03\x03\x13\x83\x10\x88he`\x00\x00B4\x04\x1E
|
69
74
|
endstream
|
70
75
|
endobj
|
@@ -80,12 +85,12 @@ describe HexaPDF::Writer do
|
|
80
85
|
endstream
|
81
86
|
endobj
|
82
87
|
4 0 obj
|
83
|
-
<</
|
84
|
-
|
88
|
+
<</Size 7/Root<</Type/Catalog>>/Info 6 0 R/Prev 141/Type/XRef/W[1 2 2]/Index[2 1 4 1 6 1]/Filter/FlateDecode/DecodeParms<</Columns 5/Predictor 12>>/Length 22>>stream
|
89
|
+
#{xref_stream}
|
85
90
|
endstream
|
86
91
|
endobj
|
87
92
|
startxref
|
88
|
-
|
93
|
+
#{442 + HexaPDF::VERSION.length}
|
89
94
|
%%EOF
|
90
95
|
EOF
|
91
96
|
end
|
@@ -270,4 +275,13 @@ describe HexaPDF::Writer do
|
|
270
275
|
doc = HexaPDF::Document.new(io: io)
|
271
276
|
refute(doc.trailer.key?(:XRefStm))
|
272
277
|
end
|
278
|
+
|
279
|
+
it "removes the /Type entry in a non-xref stream trailer" do
|
280
|
+
io = StringIO.new
|
281
|
+
doc = HexaPDF::Document.new
|
282
|
+
doc.trailer[:Type] = :XRef
|
283
|
+
doc.write(io)
|
284
|
+
doc = HexaPDF::Document.new(io: io)
|
285
|
+
refute(doc.trailer.key?(:Type))
|
286
|
+
end
|
273
287
|
end
|
@@ -291,6 +291,22 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
291
291
|
assert_equal(:XObject, @widget[:AP][:N][:Other].type)
|
292
292
|
end
|
293
293
|
|
294
|
+
it "uses the field's value or :Yes for the on state if the appearance dictionary doesn't contain a name for it" do
|
295
|
+
@widget[:AP][:N].delete(:Yes)
|
296
|
+
@generator.create_appearances
|
297
|
+
assert_equal(:XObject, @widget[:AP][:N][:Yes].type)
|
298
|
+
|
299
|
+
@widget[:AP][:N].delete(:Yes)
|
300
|
+
@field[:V] = nil
|
301
|
+
@generator.create_appearances
|
302
|
+
assert_equal(:XObject, @widget[:AP][:N][:Yes].type)
|
303
|
+
|
304
|
+
@widget[:AP][:N].delete(:Yes)
|
305
|
+
@field[:V] = "other" # some PDFs use a string instead of the correct symbol
|
306
|
+
@generator.create_appearances
|
307
|
+
assert_equal(:XObject, @widget[:AP][:N][:other].type)
|
308
|
+
end
|
309
|
+
|
294
310
|
it "creates the needed appearance streams" do
|
295
311
|
@widget[:AP][:N].delete(:Off)
|
296
312
|
@generator.create_appearances
|
@@ -327,11 +343,6 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
327
343
|
[:end_text],
|
328
344
|
[:restore_graphics_state]])
|
329
345
|
end
|
330
|
-
|
331
|
-
it "fails if the appearance dictionary doesn't contain a name for the on state" do
|
332
|
-
@widget[:AP][:N].delete(:Yes)
|
333
|
-
assert_raises(HexaPDF::Error) { @generator.create_appearances }
|
334
|
-
end
|
335
346
|
end
|
336
347
|
|
337
348
|
describe "radio button" do
|
@@ -441,6 +452,12 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
441
452
|
assert_equal(form, @widget[:AP][:N])
|
442
453
|
refute(form.key?(:key))
|
443
454
|
assert_match(/test1/, form.contents)
|
455
|
+
|
456
|
+
form.delete(:Type)
|
457
|
+
@widget[:AP][:N] = @doc.wrap(form, type: HexaPDF::Type::Annotation)
|
458
|
+
@field[:V] = 'test2'
|
459
|
+
@generator.create_appearances
|
460
|
+
assert_match(/test2/, form.contents)
|
444
461
|
end
|
445
462
|
|
446
463
|
describe "takes the rotation into account" do
|
@@ -532,7 +549,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
532
549
|
@field.text_alignment(:left)
|
533
550
|
@generator.create_appearances
|
534
551
|
assert_operators(@widget[:AP][:N].stream,
|
535
|
-
[:
|
552
|
+
[:move_text, [2, 6.41]],
|
536
553
|
range: 7)
|
537
554
|
end
|
538
555
|
|
@@ -540,7 +557,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
540
557
|
@field.text_alignment(:right)
|
541
558
|
@generator.create_appearances
|
542
559
|
assert_operators(@widget[:AP][:N].stream,
|
543
|
-
[:
|
560
|
+
[:move_text, [78.55, 6.41]],
|
544
561
|
range: 7)
|
545
562
|
end
|
546
563
|
|
@@ -548,7 +565,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
548
565
|
@field.text_alignment(:center)
|
549
566
|
@generator.create_appearances
|
550
567
|
assert_operators(@widget[:AP][:N].stream,
|
551
|
-
[:
|
568
|
+
[:move_text, [40.275, 6.41]],
|
552
569
|
range: 7)
|
553
570
|
end
|
554
571
|
|
@@ -559,7 +576,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
559
576
|
|
560
577
|
@generator.create_appearances
|
561
578
|
assert_operators(@widget[:AP][:N].stream,
|
562
|
-
[:
|
579
|
+
[:move_text, [2, 6.41]],
|
563
580
|
range: 7)
|
564
581
|
ensure
|
565
582
|
font_metrics.cap_height = cap_height
|
@@ -569,7 +586,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
569
586
|
@widget[:Rect].height = 5
|
570
587
|
@generator.create_appearances
|
571
588
|
assert_operators(@widget[:AP][:N].stream,
|
572
|
-
[:
|
589
|
+
[:move_text, [2, 3.07]],
|
573
590
|
range: 7)
|
574
591
|
end
|
575
592
|
end
|
@@ -597,7 +614,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
597
614
|
[:set_font_and_size, [:F1, 10]],
|
598
615
|
[:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
|
599
616
|
[:begin_text],
|
600
|
-
[:
|
617
|
+
[:move_text, [2, 2.035]],
|
601
618
|
[:show_text, ["Te "]],
|
602
619
|
[:set_font_and_size, [:F2, 10]],
|
603
620
|
[:move_text, [14.45, 0]],
|
@@ -628,7 +645,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
628
645
|
@field.text_alignment(:left)
|
629
646
|
@generator.create_appearances
|
630
647
|
assert_operators(@widget[:AP][:N].stream,
|
631
|
-
[:
|
648
|
+
[:move_text, [2, 16.195]],
|
632
649
|
range: 9)
|
633
650
|
end
|
634
651
|
|
@@ -636,7 +653,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
636
653
|
@field.text_alignment(:right)
|
637
654
|
@generator.create_appearances
|
638
655
|
assert_operators(@widget[:AP][:N].stream,
|
639
|
-
[:
|
656
|
+
[:move_text, [78.55, 16.195]],
|
640
657
|
range: 9)
|
641
658
|
end
|
642
659
|
|
@@ -644,7 +661,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
644
661
|
@field.text_alignment(:center)
|
645
662
|
@generator.create_appearances
|
646
663
|
assert_operators(@widget[:AP][:N].stream,
|
647
|
-
[:
|
664
|
+
[:move_text, [40.275, 16.195]],
|
648
665
|
range: 9)
|
649
666
|
end
|
650
667
|
end
|
@@ -664,7 +681,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
664
681
|
[:set_font_and_size, [:F1, 10]],
|
665
682
|
[:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
|
666
683
|
[:begin_text],
|
667
|
-
[:
|
684
|
+
[:move_text, [2, 16.195]],
|
668
685
|
[:show_text, ['Test']],
|
669
686
|
[:move_text_next_line],
|
670
687
|
[:show_text, ['Value']],
|
@@ -686,7 +703,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
686
703
|
[:set_leading, [9.25]],
|
687
704
|
[:set_font_and_size, [:F1, 8]],
|
688
705
|
[:begin_text],
|
689
|
-
[:
|
706
|
+
[:move_text, [2, 18.556]],
|
690
707
|
[:show_text, ['Test']],
|
691
708
|
[:move_text_next_line],
|
692
709
|
[:show_text, ['Test']],
|
@@ -717,7 +734,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
717
734
|
@field.text_alignment(:left)
|
718
735
|
@generator.create_appearances
|
719
736
|
assert_operators(@widget[:AP][:N].stream,
|
720
|
-
[:
|
737
|
+
[:move_text, [2.945, 6.41]],
|
721
738
|
range: 7)
|
722
739
|
end
|
723
740
|
|
@@ -725,7 +742,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
725
742
|
@field.text_alignment(:right)
|
726
743
|
@generator.create_appearances
|
727
744
|
assert_operators(@widget[:AP][:N].stream,
|
728
|
-
[:
|
745
|
+
[:move_text, [62.945, 6.41]],
|
729
746
|
range: 7)
|
730
747
|
end
|
731
748
|
|
@@ -733,7 +750,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
733
750
|
@field.text_alignment(:center)
|
734
751
|
@generator.create_appearances
|
735
752
|
assert_operators(@widget[:AP][:N].stream,
|
736
|
-
[:
|
753
|
+
[:move_text, [32.945, 6.41]],
|
737
754
|
range: 7)
|
738
755
|
end
|
739
756
|
|
@@ -742,7 +759,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
742
759
|
@field.text_alignment(:center)
|
743
760
|
@generator.create_appearances
|
744
761
|
assert_operators(@widget[:AP][:N].stream,
|
745
|
-
[:
|
762
|
+
[:move_text, [22.39, 6.41]],
|
746
763
|
range: 7)
|
747
764
|
end
|
748
765
|
end
|
@@ -760,7 +777,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
760
777
|
[:set_font_and_size, [:F1, 10]],
|
761
778
|
[:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
|
762
779
|
[:begin_text],
|
763
|
-
[:
|
780
|
+
[:move_text, [2.945, 6.41]],
|
764
781
|
[:show_text_with_positioning, [['T', -416.5, 'e', -472, 'x', -611, 't']]],
|
765
782
|
[:end_text],
|
766
783
|
[:restore_graphics_state],
|
@@ -772,7 +789,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
772
789
|
@generator.create_appearances
|
773
790
|
assert_operators(@widget[:AP][:N].stream,
|
774
791
|
[[:begin_text],
|
775
|
-
[:
|
792
|
+
[:move_text, [2, 6.41]],
|
776
793
|
[:end_text]], range: 6..8)
|
777
794
|
end
|
778
795
|
|
@@ -853,7 +870,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
853
870
|
[:set_font_and_size, [:F1, 12]],
|
854
871
|
[:set_device_rgb_non_stroking_color, [1.0, 0.0, 0.0]],
|
855
872
|
[:begin_text],
|
856
|
-
[:
|
873
|
+
[:move_text, [2, 23.609]],
|
857
874
|
[:show_text, ["a"]],
|
858
875
|
[:move_text_next_line],
|
859
876
|
[:show_text, ["b"]],
|
@@ -159,11 +159,16 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
159
159
|
end
|
160
160
|
|
161
161
|
def applies_variable_text_properties(method, **args)
|
162
|
-
field = @acro_form.send(method, "field", **args
|
162
|
+
field = @acro_form.send(method, "field", **args)
|
163
163
|
font_name, font_size, font_color = field.parse_default_appearance_string
|
164
|
-
assert_equal(:'
|
164
|
+
assert_equal(:'Helvetica', @acro_form.default_resources.font(font_name)[:BaseFont])
|
165
165
|
assert_equal(0, font_size)
|
166
166
|
assert_equal(HexaPDF::Content::ColorSpace::DeviceGray.new.color(0), font_color)
|
167
|
+
assert_equal(0, field.value[:Q])
|
168
|
+
|
169
|
+
field = @acro_form.send(method, "field", **args, font: 'Times')
|
170
|
+
font_name, font_size, font_color = field.parse_default_appearance_string
|
171
|
+
assert_equal(:'Times-Roman', @acro_form.default_resources.font(font_name)[:BaseFont])
|
167
172
|
|
168
173
|
field = @acro_form.send(method, "field", **args, font_options: {variant: :bold})
|
169
174
|
font_name, = field.parse_default_appearance_string
|
@@ -171,7 +176,6 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
171
176
|
|
172
177
|
field = @acro_form.send(method, "field", **args, font_size: 10)
|
173
178
|
font_name, font_size = field.parse_default_appearance_string
|
174
|
-
assert_equal(:Helvetica, @acro_form.default_resources.font(font_name)[:BaseFont])
|
175
179
|
assert_equal(10, font_size)
|
176
180
|
|
177
181
|
field = @acro_form.send(method, "field", **args, font_color: "red")
|
@@ -507,11 +511,6 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
507
511
|
assert(@acro_form.validate)
|
508
512
|
end
|
509
513
|
|
510
|
-
it "set the default appearance string, though optional, to a valid value to avoid problems" do
|
511
|
-
assert(@acro_form.validate)
|
512
|
-
assert_equal("0.0 g /F1 0 Tf", @acro_form[:DA])
|
513
|
-
end
|
514
|
-
|
515
514
|
describe "field hierarchy validation" do
|
516
515
|
before do
|
517
516
|
@acro_form[:Fields] = [
|
@@ -31,7 +31,9 @@ describe HexaPDF::Type::AcroForm::SignatureField do
|
|
31
31
|
|
32
32
|
it "gets the field value" do
|
33
33
|
@field[:V] = {Empty: :True}
|
34
|
-
|
34
|
+
value = @field.field_value
|
35
|
+
assert_kind_of(HexaPDF::DigitalSignature::Signature, value)
|
36
|
+
assert_equal({Empty: :True}, value)
|
35
37
|
end
|
36
38
|
|
37
39
|
it "validates the value of the /FT field" do
|
@@ -102,9 +102,22 @@ describe HexaPDF::Type::AcroForm::VariableTextField do
|
|
102
102
|
@field.parse_default_appearance_string)
|
103
103
|
end
|
104
104
|
|
105
|
-
it "
|
105
|
+
it "sets a standard /DA value if no other /DA is found" do
|
106
106
|
@doc.acro_form.delete(:DA)
|
107
|
+
assert_equal([:F1, 0, HexaPDF::Content::ColorSpace.prenormalized_device_color([0])],
|
108
|
+
@field.parse_default_appearance_string)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "converts the /DA to a string in case an invalid PDF uses a Symbol" do
|
112
|
+
@field[:DA] = :"1 g /F1 20 Tf"
|
113
|
+
assert_equal([:F1, 20, @color], @field.parse_default_appearance_string)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "fails if no /DA value is set and no default appearance string should be set" do
|
117
|
+
@doc.acro_form.delete(:DA)
|
118
|
+
@doc.config['acro_form.fallback_default_appearance'] = nil
|
107
119
|
assert_raises(HexaPDF::Error) { @field.parse_default_appearance_string }
|
108
120
|
end
|
121
|
+
|
109
122
|
end
|
110
123
|
end
|
@@ -14,10 +14,14 @@ describe HexaPDF::Type::Actions::Launch do
|
|
14
14
|
it "needs a launch target" do
|
15
15
|
refute(@action.validate)
|
16
16
|
|
17
|
-
@action.value = {F:
|
17
|
+
@action.value = {Win: {F: "test.exe"}}
|
18
|
+
assert(@action.validate)
|
19
|
+
@action.value = {Mac: 'test'}
|
20
|
+
assert(@action.validate)
|
21
|
+
@action.value = {Unix: 'test'}
|
18
22
|
assert(@action.validate)
|
19
23
|
|
20
|
-
@action.value = {
|
24
|
+
@action.value = {F: {}}
|
21
25
|
assert(@action.validate)
|
22
26
|
end
|
23
27
|
end
|
@@ -194,6 +194,8 @@ describe HexaPDF::Type::Annotations::Widget do
|
|
194
194
|
|
195
195
|
it "returns the default size if none is set" do
|
196
196
|
assert_equal(0, @widget.marker_style.size)
|
197
|
+
@widget.form_field[:DA] = "0.0 g"
|
198
|
+
assert_equal(0, @widget.marker_style.size)
|
197
199
|
end
|
198
200
|
|
199
201
|
it "sets the given size" do
|
@@ -212,6 +214,8 @@ describe HexaPDF::Type::Annotations::Widget do
|
|
212
214
|
|
213
215
|
it "returns the default color if none is set" do
|
214
216
|
assert_equal([0], @widget.marker_style.color.components)
|
217
|
+
@widget.form_field[:DA] = "/ZaDb 10 Tfg"
|
218
|
+
assert_equal([0], @widget.marker_style.color.components)
|
215
219
|
end
|
216
220
|
|
217
221
|
it "sets the given color" do
|
@@ -138,5 +138,10 @@ describe HexaPDF::Type::FontType1 do
|
|
138
138
|
@embedded_font.delete(:FontDescriptor)
|
139
139
|
refute(@embedded_font.validate)
|
140
140
|
end
|
141
|
+
|
142
|
+
it "ensures a correct Symbol value for the /Encoding key" do
|
143
|
+
@font[:Encoding] = :Other
|
144
|
+
refute(@font.validate)
|
145
|
+
end
|
141
146
|
end
|
142
147
|
end
|
@@ -57,7 +57,7 @@ describe HexaPDF::Type::Form do
|
|
57
57
|
it "creates the resource dictionary if it is not found" do
|
58
58
|
resources = @form.resources
|
59
59
|
assert_equal(:XXResources, resources.type)
|
60
|
-
assert_equal({
|
60
|
+
assert_equal({}, resources.value)
|
61
61
|
end
|
62
62
|
|
63
63
|
it "returns the already used resource dictionary" do
|
@@ -358,6 +358,12 @@ describe HexaPDF::Type::Page do
|
|
358
358
|
page[:Contents] = [@doc.wrap({}, stream: 'q 10'), @doc.wrap({}, stream: 'w Q')]
|
359
359
|
assert_equal('q 10 w Q', page.contents)
|
360
360
|
end
|
361
|
+
|
362
|
+
it "handles null objects in the /Contents array" do
|
363
|
+
page = @doc.pages.add
|
364
|
+
page[:Contents] = [@doc.wrap({}, stream: 'q 10'), nil]
|
365
|
+
assert_equal('q 10 ', page.contents)
|
366
|
+
end
|
361
367
|
end
|
362
368
|
|
363
369
|
describe "contents=" do
|
@@ -389,7 +395,7 @@ describe HexaPDF::Type::Page do
|
|
389
395
|
page = @doc.add({Type: :Page, Parent: @doc.pages.root})
|
390
396
|
resources = page.resources
|
391
397
|
assert_equal(:XXResources, resources.type)
|
392
|
-
assert_equal({
|
398
|
+
assert_equal({}, resources.value)
|
393
399
|
end
|
394
400
|
|
395
401
|
it "returns the already used resource dictionary" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hexapdf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Leitner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-10-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cmdparse
|
@@ -469,6 +469,7 @@ files:
|
|
469
469
|
- lib/hexapdf/stream.rb
|
470
470
|
- lib/hexapdf/task.rb
|
471
471
|
- lib/hexapdf/task/dereference.rb
|
472
|
+
- lib/hexapdf/task/merge_acro_form.rb
|
472
473
|
- lib/hexapdf/task/optimize.rb
|
473
474
|
- lib/hexapdf/task/pdfa.rb
|
474
475
|
- lib/hexapdf/test_utils.rb
|
@@ -594,21 +595,25 @@ files:
|
|
594
595
|
- test/data/minimal.pdf
|
595
596
|
- test/data/standard-security-handler/README
|
596
597
|
- test/data/standard-security-handler/bothpwd-aes-128bit-V4.pdf
|
598
|
+
- test/data/standard-security-handler/bothpwd-aes-256bit-V5-R5.pdf
|
597
599
|
- test/data/standard-security-handler/bothpwd-aes-256bit-V5.pdf
|
598
600
|
- test/data/standard-security-handler/bothpwd-arc4-128bit-V2.pdf
|
599
601
|
- test/data/standard-security-handler/bothpwd-arc4-128bit-V4.pdf
|
600
602
|
- test/data/standard-security-handler/bothpwd-arc4-40bit-V1.pdf
|
601
603
|
- test/data/standard-security-handler/nopwd-aes-128bit-V4.pdf
|
604
|
+
- test/data/standard-security-handler/nopwd-aes-256bit-V5-R5.pdf
|
602
605
|
- test/data/standard-security-handler/nopwd-aes-256bit-V5.pdf
|
603
606
|
- test/data/standard-security-handler/nopwd-arc4-128bit-V2.pdf
|
604
607
|
- test/data/standard-security-handler/nopwd-arc4-128bit-V4.pdf
|
605
608
|
- test/data/standard-security-handler/nopwd-arc4-40bit-V1.pdf
|
606
609
|
- test/data/standard-security-handler/ownerpwd-aes-128bit-V4.pdf
|
610
|
+
- test/data/standard-security-handler/ownerpwd-aes-256bit-V5-R5.pdf
|
607
611
|
- test/data/standard-security-handler/ownerpwd-aes-256bit-V5.pdf
|
608
612
|
- test/data/standard-security-handler/ownerpwd-arc4-128bit-V2.pdf
|
609
613
|
- test/data/standard-security-handler/ownerpwd-arc4-128bit-V4.pdf
|
610
614
|
- test/data/standard-security-handler/ownerpwd-arc4-40bit-V1.pdf
|
611
615
|
- test/data/standard-security-handler/userpwd-aes-128bit-V4.pdf
|
616
|
+
- test/data/standard-security-handler/userpwd-aes-256bit-V5-R5.pdf
|
612
617
|
- test/data/standard-security-handler/userpwd-aes-256bit-V5.pdf
|
613
618
|
- test/data/standard-security-handler/userpwd-arc4-128bit-V2.pdf
|
614
619
|
- test/data/standard-security-handler/userpwd-arc4-128bit-V4.pdf
|
@@ -724,6 +729,7 @@ files:
|
|
724
729
|
- test/hexapdf/layout/test_text_shaper.rb
|
725
730
|
- test/hexapdf/layout/test_width_from_polygon.rb
|
726
731
|
- test/hexapdf/task/test_dereference.rb
|
732
|
+
- test/hexapdf/task/test_merge_acro_form.rb
|
727
733
|
- test/hexapdf/task/test_optimize.rb
|
728
734
|
- test/hexapdf/task/test_pdfa.rb
|
729
735
|
- test/hexapdf/test_composer.rb
|