hexapdf 0.46.0 → 1.0.0
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 +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
|