hexapdf 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -0
  3. data/examples/019-acro_form.rb +41 -4
  4. data/lib/hexapdf/cli/split.rb +74 -14
  5. data/lib/hexapdf/document.rb +10 -4
  6. data/lib/hexapdf/layout/text_layouter.rb +2 -2
  7. data/lib/hexapdf/object.rb +22 -0
  8. data/lib/hexapdf/parser.rb +23 -1
  9. data/lib/hexapdf/pdf_array.rb +2 -2
  10. data/lib/hexapdf/type/acro_form/appearance_generator.rb +127 -27
  11. data/lib/hexapdf/type/acro_form/button_field.rb +5 -2
  12. data/lib/hexapdf/type/acro_form/choice_field.rb +64 -10
  13. data/lib/hexapdf/type/acro_form/form.rb +133 -10
  14. data/lib/hexapdf/type/acro_form/text_field.rb +68 -3
  15. data/lib/hexapdf/type/cid_font.rb +1 -1
  16. data/lib/hexapdf/type/font.rb +1 -1
  17. data/lib/hexapdf/type/font_simple.rb +1 -1
  18. data/lib/hexapdf/type/font_type0.rb +3 -3
  19. data/lib/hexapdf/type/form.rb +4 -1
  20. data/lib/hexapdf/type/page.rb +5 -5
  21. data/lib/hexapdf/utils/object_hash.rb +0 -1
  22. data/lib/hexapdf/version.rb +1 -1
  23. data/test/hexapdf/layout/test_text_layouter.rb +9 -1
  24. data/test/hexapdf/test_document.rb +14 -6
  25. data/test/hexapdf/test_object.rb +27 -0
  26. data/test/hexapdf/test_parser.rb +46 -0
  27. data/test/hexapdf/test_pdf_array.rb +1 -1
  28. data/test/hexapdf/test_writer.rb +2 -2
  29. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +286 -34
  30. data/test/hexapdf/type/acro_form/test_button_field.rb +15 -0
  31. data/test/hexapdf/type/acro_form/test_choice_field.rb +92 -9
  32. data/test/hexapdf/type/acro_form/test_form.rb +83 -11
  33. data/test/hexapdf/type/acro_form/test_text_field.rb +75 -1
  34. data/test/hexapdf/type/test_form.rb +7 -0
  35. data/test/hexapdf/utils/test_object_hash.rb +5 -0
  36. data/test/test_helper.rb +2 -0
  37. metadata +4 -3
@@ -233,6 +233,14 @@ describe HexaPDF::Type::AcroForm::ButtonField do
233
233
  refute_same(off, widget.appearance.normal_appearance[:Off])
234
234
  end
235
235
 
236
+ it "always generates appearances if force is true" do
237
+ widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
238
+ @field.create_appearances
239
+ yes = widget.appearance.normal_appearance[:Yes]
240
+ @field.create_appearances(force: true)
241
+ refute_same(yes, widget.appearance.normal_appearance[:Yes])
242
+ end
243
+
236
244
  it "fails for unsupported button types" do
237
245
  @field.flag(:push_button)
238
246
  @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
@@ -263,6 +271,13 @@ describe HexaPDF::Type::AcroForm::ButtonField do
263
271
  @field.update_widgets
264
272
  assert_equal(:Yes, widget[:AS])
265
273
  end
274
+
275
+ it "creates the appearances if necessary" do
276
+ widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
277
+ assert_nil(widget[:AP][:N][:Yes])
278
+ @field.update_widgets
279
+ assert(widget[:AP][:N][:Yes])
280
+ end
266
281
  end
267
282
 
268
283
  describe "validation" do
@@ -36,23 +36,34 @@ describe HexaPDF::Type::AcroForm::ChoiceField do
36
36
 
37
37
  describe "field_value=" do
38
38
  before do
39
- @field.option_items = ["test", "other"]
39
+ @field.option_items = ["test", "something", "other", "neu"]
40
+ end
41
+
42
+ it "updates the widgets to reflect the changed value" do
43
+ @field.initialize_as_combo_box
44
+ widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
45
+ @field.set_default_appearance_string
46
+ @field.field_value = 'test'
47
+ assert(widget[:AP][:N])
40
48
  end
41
49
 
42
50
  describe "combo_box" do
43
51
  before do
44
52
  @field.initialize_as_combo_box
53
+ @field[:I] = 2
45
54
  end
46
55
 
47
56
  it "can set the value for an uneditable combo box" do
48
57
  @field.field_value = 'test'
49
58
  assert_equal("test", @field[:V])
59
+ assert_nil(@field[:I])
50
60
  end
51
61
 
52
62
  it "can set the value for an editable combo box" do
53
63
  @field.flag(:edit)
54
64
  @field.field_value = 'another'
55
65
  assert_equal("another", @field[:V])
66
+ assert_nil(@field[:I])
56
67
  end
57
68
 
58
69
  it "fails if mulitple values are provided for a combo box" do
@@ -76,8 +87,18 @@ describe HexaPDF::Type::AcroForm::ChoiceField do
76
87
 
77
88
  it "can set a multiple values if the list box is a multi-select" do
78
89
  @field.flag(:multi_select)
79
- @field.field_value = ['test', 'other']
80
- assert_equal(['test', 'other'], @field[:V].value)
90
+ @field.field_value = ['other', 'test']
91
+ assert_equal(['other', 'test'], @field[:V].value)
92
+ assert_equal([0, 2], @field[:I].value)
93
+ end
94
+
95
+ it "can read and set the top index" do
96
+ assert_raises(ArgumentError) { @field.list_box_top_index = 4 }
97
+ @field.delete(:Opt)
98
+ assert_raises(ArgumentError) { @field.list_box_top_index = 0 }
99
+ @field.option_items = [1, 2, 3, 4]
100
+ @field.list_box_top_index = 2
101
+ assert_equal(2, @field.list_box_top_index)
81
102
  end
82
103
 
83
104
  it "fails if mulitple values are provided but the list box is not a multi-select" do
@@ -97,10 +118,29 @@ describe HexaPDF::Type::AcroForm::ChoiceField do
97
118
  assert_raises(HexaPDF::Error) { @field.default_field_value = 'unknown' }
98
119
  end
99
120
 
100
- it "sets and returns the array with the option items" do
101
- assert_equal([], @field.option_items)
102
- @field.option_items = ["H\xe4llo".b, "\xFE\xFF".b << "Töne".encode('UTF-16BE').b]
103
- assert_equal(["Hällo", "Töne"], @field.option_items)
121
+ describe "option items" do
122
+ before do
123
+ @items = [["a", "Zx"], "\xFE\xFF".b << "Töne".encode('UTF-16BE').b, "H\xe4llo".b,]
124
+ end
125
+
126
+ it "sets the option items" do
127
+ @field.option_items = @items
128
+ assert_equal(@items, @field[:Opt].value)
129
+
130
+ @field.flag(:sort)
131
+ @field.option_items = @items
132
+ assert_equal(@items.values_at(2, 1, 0), @field[:Opt].value)
133
+ end
134
+
135
+ it "can retrieve the option items" do
136
+ @field[:Opt] = @items
137
+ assert_equal(["Zx", "Töne", "Hällo"], @field.option_items)
138
+ end
139
+
140
+ it "can retrieve the export values" do
141
+ @field[:Opt] = @items
142
+ assert_equal(["a", "Töne", "Hällo"], @field.export_values)
143
+ end
104
144
  end
105
145
 
106
146
  it "returns the correct concrete field type" do
@@ -121,8 +161,51 @@ describe HexaPDF::Type::AcroForm::ChoiceField do
121
161
  assert(@field[:AP][:N])
122
162
  end
123
163
 
124
- it "fails for list boxes" do
125
- assert_raises(HexaPDF::Error) { @field.create_appearances }
164
+ it "works for list box fields" do
165
+ @field.initialize_as_list_box
166
+ @field.set_default_appearance_string
167
+ @field.create_appearances
168
+ assert(@field[:AP][:N])
169
+ end
170
+
171
+ it "only creates a new appearance if the involved dictionary values have changed per widget" do
172
+ @field.initialize_as_list_box
173
+ @field.set_default_appearance_string
174
+ @field.create_appearances
175
+ appearance_stream = @field[:AP][:N].raw_stream
176
+
177
+ @field.create_appearances
178
+ assert_same(appearance_stream, @field[:AP][:N].raw_stream)
179
+
180
+ do_check = lambda do
181
+ @field.create_appearances
182
+ refute_same(appearance_stream, @field[:AP][:N].raw_stream)
183
+ appearance_stream = @field[:AP][:N].raw_stream
184
+ end
185
+
186
+ @field.option_items = ['a', 'b', 'c']
187
+ do_check.call
188
+
189
+ @field.list_box_top_index = 2
190
+ do_check.call
191
+
192
+ @field.field_value = 'b'
193
+ do_check.call
194
+
195
+ widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
196
+ assert_nil(widget[:AP])
197
+ @field.create_appearances
198
+ refute_nil(widget[:AP][:N])
199
+ end
200
+
201
+ it "force the creation of appearance streams when force: true" do
202
+ @field.initialize_as_list_box
203
+ @field.set_default_appearance_string
204
+ @field.create_appearances
205
+ appearance_stream = @field[:AP][:N].raw_stream
206
+
207
+ @field.create_appearances(force: true)
208
+ refute_same(appearance_stream, @field[:AP][:N].raw_stream)
126
209
  end
127
210
  end
128
211
 
@@ -115,6 +115,10 @@ describe HexaPDF::Type::AcroForm::Form do
115
115
  end
116
116
 
117
117
  describe "create fields" do
118
+ before do
119
+ @acro_form = @doc.acro_form(create: true)
120
+ end
121
+
118
122
  describe "handles the general case" do
119
123
  it "works for names with a dot" do
120
124
  @acro_form[:Fields] = [{T: "root"}]
@@ -134,9 +138,57 @@ describe HexaPDF::Type::AcroForm::Form do
134
138
  end
135
139
  end
136
140
 
141
+ def applies_variable_text_properties(method, **args)
142
+ field = @acro_form.send(method, "field", **args, font: 'Times')
143
+ font_name, font_size = field.parse_default_appearance_string
144
+ assert_equal(:'Times-Roman', @acro_form.default_resources.font(font_name)[:BaseFont])
145
+ assert_equal(0, font_size)
146
+
147
+ field = @acro_form.send(method, "field", **args, font_size: 10)
148
+ font_name, font_size = field.parse_default_appearance_string
149
+ assert_equal(:Helvetica, @acro_form.default_resources.font(font_name)[:BaseFont])
150
+ assert_equal(10, font_size)
151
+
152
+ field = @acro_form.send(method, "field", **args, font: 'Courier', font_size: 10, align: :center)
153
+ font_name, font_size = field.parse_default_appearance_string
154
+ assert_equal(:Courier, @acro_form.default_resources.font(font_name)[:BaseFont])
155
+ assert_equal(10, font_size)
156
+ assert_equal(:center, field.text_alignment)
157
+ end
158
+
137
159
  it "creates a text field" do
138
160
  field = @acro_form.create_text_field("field")
139
161
  assert_equal(:Tx, field.field_type)
162
+ applies_variable_text_properties(:create_text_field)
163
+ end
164
+
165
+ it "creates a multiline text field" do
166
+ field = @acro_form.create_multiline_text_field("field")
167
+ assert_equal(:Tx, field.field_type)
168
+ assert(field.multiline_text_field?)
169
+ applies_variable_text_properties(:create_multiline_text_field)
170
+ end
171
+
172
+ it "creates a comb text field" do
173
+ field = @acro_form.create_comb_text_field("field", max_chars: 9)
174
+ assert_equal(:Tx, field.field_type)
175
+ assert_equal(9, field[:MaxLen])
176
+ assert(field.comb_text_field?)
177
+ applies_variable_text_properties(:create_comb_text_field, max_chars: 9)
178
+ end
179
+
180
+ it "creates a password field" do
181
+ field = @acro_form.create_password_field("field")
182
+ assert_equal(:Tx, field.field_type)
183
+ assert(field.password_field?)
184
+ applies_variable_text_properties(:create_password_field)
185
+ end
186
+
187
+ it "creates a file select field" do
188
+ field = @acro_form.create_file_select_field("field")
189
+ assert_equal(:Tx, field.field_type)
190
+ assert(field.file_select_field?)
191
+ applies_variable_text_properties(:create_file_select_field)
140
192
  end
141
193
 
142
194
  it "creates a check box" do
@@ -150,13 +202,19 @@ describe HexaPDF::Type::AcroForm::Form do
150
202
  end
151
203
 
152
204
  it "creates a combo box" do
153
- field = @acro_form.create_combo_box("field")
205
+ field = @acro_form.create_combo_box("field", option_items: ['a', 'b', 'c'], editable: true)
154
206
  assert(field.combo_box?)
207
+ assert_equal(['a', 'b', 'c'], field.option_items)
208
+ assert(field.flagged?(:edit))
209
+ applies_variable_text_properties(:create_combo_box)
155
210
  end
156
211
 
157
212
  it "creates a list box" do
158
- field = @acro_form.create_list_box("field")
213
+ field = @acro_form.create_list_box("field", option_items: ['a', 'b', 'c'], multi_select: true)
159
214
  assert(field.list_box?)
215
+ assert_equal(['a', 'b', 'c'], field.option_items)
216
+ assert(field.flagged?(:multi_select))
217
+ applies_variable_text_properties(:create_list_box)
160
218
  end
161
219
  end
162
220
 
@@ -185,15 +243,29 @@ describe HexaPDF::Type::AcroForm::Form do
185
243
  assert(@acro_form[:NeedAppearances])
186
244
  end
187
245
 
188
- it "creates the appearances of all field widgets if necessary" do
189
- tf = @acro_form.create_text_field('test')
190
- tf.set_default_appearance_string
191
- tf.create_widget(@doc.pages.add)
192
- cb = @acro_form.create_check_box('test2')
193
- cb.create_widget(@doc.pages.add)
194
- @acro_form.create_appearances
195
- assert(tf.each_widget.all? {|w| w.appearance.normal_appearance.kind_of?(HexaPDF::Stream) })
196
- assert(cb.each_widget.all? {|w| w.appearance.normal_appearance[:Yes].kind_of?(HexaPDF::Stream) })
246
+ describe "create_appearances" do
247
+ before do
248
+ @tf = @acro_form.create_text_field('test')
249
+ @tf.set_default_appearance_string
250
+ @tf.create_widget(@doc.pages.add)
251
+ @cb = @acro_form.create_check_box('test2')
252
+ @cb.create_widget(@doc.pages.add)
253
+ end
254
+
255
+ it "creates the appearances of all field widgets if necessary" do
256
+ @acro_form.create_appearances
257
+ assert(@tf.each_widget.all? {|w| w.appearance.normal_appearance.kind_of?(HexaPDF::Stream) })
258
+ assert(@cb.each_widget.all? {|w| w.appearance.normal_appearance[:Yes].kind_of?(HexaPDF::Stream) })
259
+ end
260
+
261
+ it "force the creation of appearances if force is true" do
262
+ @acro_form.create_appearances
263
+ text_stream = @tf[:AP][:N].raw_stream
264
+ @acro_form.create_appearances
265
+ assert_same(text_stream, @tf[:AP][:N].raw_stream)
266
+ @acro_form.create_appearances(force: true)
267
+ refute_same(text_stream, @tf[:AP][:N].raw_stream)
268
+ end
197
269
  end
198
270
 
199
271
  describe "perform_validation" do
@@ -21,6 +21,56 @@ describe HexaPDF::Type::AcroForm::TextField do
21
21
  assert_equal(6, @field[:MaxLen])
22
22
  end
23
23
 
24
+ it "can be initialized as a multiline text field" do
25
+ @field.flag(:comb)
26
+ @field.initialize_as_multiline_text_field
27
+ assert(@field.multiline_text_field?)
28
+ end
29
+
30
+ it "can be initialized as comb text field" do
31
+ @field.flag(:multiline)
32
+ @field.initialize_as_comb_text_field
33
+ assert(@field.comb_text_field?)
34
+ end
35
+
36
+ it "can be initialized as password field" do
37
+ @field.flag(:multiline)
38
+ @field[:V] = 'test'
39
+ @field.initialize_as_password_field
40
+ assert_nil(@field[:V])
41
+ assert(@field.password_field?)
42
+ end
43
+
44
+ it "can be initialized as a file select field" do
45
+ @field.flag(:multiline)
46
+ @field.initialize_as_file_select_field
47
+ assert(@field.file_select_field?)
48
+ end
49
+
50
+ it "can check whether the field is a multiline text field" do
51
+ refute(@field.multiline_text_field?)
52
+ @field.flag(:multiline)
53
+ assert(@field.multiline_text_field?)
54
+ end
55
+
56
+ it "can check whether the field is a comb text field" do
57
+ refute(@field.comb_text_field?)
58
+ @field.flag(:comb)
59
+ assert(@field.comb_text_field?)
60
+ end
61
+
62
+ it "can check whether the field is a password field" do
63
+ refute(@field.password_field?)
64
+ @field.flag(:password)
65
+ assert(@field.password_field?)
66
+ end
67
+
68
+ it "can check whether the field is a file select field" do
69
+ refute(@field.file_select_field?)
70
+ @field.flag(:file_select)
71
+ assert(@field.file_select_field?)
72
+ end
73
+
24
74
  describe "field_value" do
25
75
  it "handles unset values" do
26
76
  assert_nil(@field.field_value)
@@ -81,14 +131,38 @@ describe HexaPDF::Type::AcroForm::TextField do
81
131
  end
82
132
 
83
133
  describe "create_appearances" do
84
- it "creates the needed streams" do
134
+ before do
85
135
  @doc.acro_form(create: true)
86
136
  @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
87
137
  @field.set_default_appearance_string
138
+ end
139
+
140
+ it "creates the needed streams" do
88
141
  @field.create_appearances
89
142
  assert(@field[:AP][:N])
90
143
  end
91
144
 
145
+ it "doesn't create a new appearance stream if the field value hasn't changed, checked per widget" do
146
+ @field.create_appearances
147
+ stream = @field[:AP][:N].raw_stream
148
+ @field.create_appearances
149
+ assert_same(stream, @field[:AP][:N].raw_stream)
150
+ @field.field_value = 'test'
151
+ refute_same(stream, @field[:AP][:N].raw_stream)
152
+
153
+ widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
154
+ assert_nil(widget[:AP])
155
+ @field.create_appearances
156
+ refute_nil(widget[:AP][:N])
157
+ end
158
+
159
+ it "always creates a new appearance stream if force is true" do
160
+ @field.create_appearances
161
+ stream = @field[:AP][:N].raw_stream
162
+ @field.create_appearances(force: true)
163
+ refute_same(stream, @field[:AP][:N].raw_stream)
164
+ end
165
+
92
166
  it "uses the configuration option acro_form.appearance_generator" do
93
167
  @doc.config['acro_form.appearance_generator'] = 'NonExistent'
94
168
  assert_raises(Exception) { @field.create_appearances }
@@ -43,6 +43,13 @@ describe HexaPDF::Type::Form do
43
43
  @form.contents = 'test'
44
44
  assert_equal('test', @form.stream)
45
45
  end
46
+
47
+ it "clears the cache to make sure that a new canvas can be created" do
48
+ @form[:BBox] = [0, 0, 100, 100]
49
+ canvas = @form.canvas
50
+ @form.contents = ''
51
+ refute_same(canvas, @form.canvas)
52
+ end
46
53
  end
47
54
 
48
55
  describe "resources" do
@@ -112,4 +112,9 @@ describe HexaPDF::Utils::ObjectHash do
112
112
  assert_equal(3, @hash.max_oid)
113
113
  end
114
114
  end
115
+
116
+ it "can return a list of all object IDs" do
117
+ @hash[1, 0] = @hash[3, 1] = 7
118
+ assert_equal([3, 1], @hash.oids)
119
+ end
115
120
  end
@@ -16,6 +16,8 @@ require 'zlib'
16
16
  TEST_DATA_DIR = File.join(__dir__, 'data')
17
17
  MINIMAL_PDF = File.read(File.join(TEST_DATA_DIR, 'minimal.pdf')).freeze
18
18
 
19
+ Minitest::Test.make_my_diffs_pretty!
20
+
19
21
  module TestHelper
20
22
 
21
23
  # Asserts that the method +name+ of +object+ gets invoked with the +expected_values+ when
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.13.0
4
+ version: 0.14.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: 2020-11-14 00:00:00.000000000 Z
11
+ date: 2020-12-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmdparse
@@ -631,6 +631,7 @@ files:
631
631
  homepage: https://hexapdf.gettalong.org
632
632
  licenses:
633
633
  - AGPL-3.0
634
+ - Commercial License
634
635
  metadata: {}
635
636
  post_install_message:
636
637
  rdoc_options: []
@@ -647,7 +648,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
647
648
  - !ruby/object:Gem::Version
648
649
  version: '0'
649
650
  requirements: []
650
- rubygems_version: 3.0.3
651
+ rubygems_version: 3.2.3
651
652
  signing_key:
652
653
  specification_version: 4
653
654
  summary: HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby