hexapdf 0.12.1 → 0.14.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +130 -0
  3. data/examples/019-acro_form.rb +41 -4
  4. data/lib/hexapdf/cli/command.rb +4 -2
  5. data/lib/hexapdf/cli/image2pdf.rb +2 -1
  6. data/lib/hexapdf/cli/info.rb +51 -2
  7. data/lib/hexapdf/cli/inspect.rb +30 -8
  8. data/lib/hexapdf/cli/merge.rb +1 -1
  9. data/lib/hexapdf/cli/split.rb +74 -14
  10. data/lib/hexapdf/configuration.rb +15 -0
  11. data/lib/hexapdf/content/graphic_object/arc.rb +3 -3
  12. data/lib/hexapdf/content/parser.rb +1 -1
  13. data/lib/hexapdf/dictionary.rb +9 -6
  14. data/lib/hexapdf/dictionary_fields.rb +1 -9
  15. data/lib/hexapdf/document.rb +41 -16
  16. data/lib/hexapdf/document/files.rb +0 -1
  17. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  18. data/lib/hexapdf/encryption/security_handler.rb +1 -0
  19. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -0
  20. data/lib/hexapdf/font/cmap.rb +1 -4
  21. data/lib/hexapdf/font/true_type/subsetter.rb +12 -3
  22. data/lib/hexapdf/font/true_type/table/head.rb +1 -0
  23. data/lib/hexapdf/font/true_type/table/os2.rb +2 -0
  24. data/lib/hexapdf/font/true_type/table/post.rb +15 -10
  25. data/lib/hexapdf/font_loader/from_configuration.rb +2 -2
  26. data/lib/hexapdf/font_loader/from_file.rb +18 -8
  27. data/lib/hexapdf/image_loader/png.rb +3 -2
  28. data/lib/hexapdf/importer.rb +3 -2
  29. data/lib/hexapdf/layout/line.rb +1 -1
  30. data/lib/hexapdf/layout/style.rb +23 -23
  31. data/lib/hexapdf/layout/text_layouter.rb +2 -2
  32. data/lib/hexapdf/layout/text_shaper.rb +3 -2
  33. data/lib/hexapdf/object.rb +52 -25
  34. data/lib/hexapdf/parser.rb +96 -4
  35. data/lib/hexapdf/pdf_array.rb +12 -5
  36. data/lib/hexapdf/revisions.rb +29 -21
  37. data/lib/hexapdf/serializer.rb +34 -8
  38. data/lib/hexapdf/task/optimize.rb +6 -4
  39. data/lib/hexapdf/tokenizer.rb +4 -3
  40. data/lib/hexapdf/type/acro_form/appearance_generator.rb +132 -28
  41. data/lib/hexapdf/type/acro_form/button_field.rb +21 -13
  42. data/lib/hexapdf/type/acro_form/choice_field.rb +68 -14
  43. data/lib/hexapdf/type/acro_form/field.rb +35 -5
  44. data/lib/hexapdf/type/acro_form/form.rb +139 -14
  45. data/lib/hexapdf/type/acro_form/text_field.rb +70 -4
  46. data/lib/hexapdf/type/actions/uri.rb +3 -2
  47. data/lib/hexapdf/type/annotations/widget.rb +3 -4
  48. data/lib/hexapdf/type/catalog.rb +2 -2
  49. data/lib/hexapdf/type/cid_font.rb +1 -1
  50. data/lib/hexapdf/type/file_specification.rb +1 -1
  51. data/lib/hexapdf/type/font.rb +1 -1
  52. data/lib/hexapdf/type/font_simple.rb +4 -2
  53. data/lib/hexapdf/type/font_true_type.rb +6 -2
  54. data/lib/hexapdf/type/font_type0.rb +4 -4
  55. data/lib/hexapdf/type/form.rb +15 -2
  56. data/lib/hexapdf/type/image.rb +2 -2
  57. data/lib/hexapdf/type/page.rb +37 -13
  58. data/lib/hexapdf/type/page_tree_node.rb +29 -5
  59. data/lib/hexapdf/type/resources.rb +1 -0
  60. data/lib/hexapdf/type/trailer.rb +2 -3
  61. data/lib/hexapdf/utils/object_hash.rb +0 -1
  62. data/lib/hexapdf/utils/sorted_tree_node.rb +18 -15
  63. data/lib/hexapdf/version.rb +1 -1
  64. data/test/hexapdf/common_tokenizer_tests.rb +6 -1
  65. data/test/hexapdf/content/graphic_object/test_arc.rb +4 -4
  66. data/test/hexapdf/content/test_canvas.rb +3 -3
  67. data/test/hexapdf/content/test_color_space.rb +1 -1
  68. data/test/hexapdf/encryption/test_aes.rb +4 -4
  69. data/test/hexapdf/encryption/test_standard_security_handler.rb +11 -11
  70. data/test/hexapdf/filter/test_ascii85_decode.rb +1 -1
  71. data/test/hexapdf/filter/test_ascii_hex_decode.rb +1 -1
  72. data/test/hexapdf/font/true_type/table/test_post.rb +1 -1
  73. data/test/hexapdf/font/true_type/test_subsetter.rb +5 -0
  74. data/test/hexapdf/font_loader/test_from_configuration.rb +7 -3
  75. data/test/hexapdf/font_loader/test_from_file.rb +7 -0
  76. data/test/hexapdf/layout/test_style.rb +1 -1
  77. data/test/hexapdf/layout/test_text_layouter.rb +12 -5
  78. data/test/hexapdf/test_configuration.rb +2 -2
  79. data/test/hexapdf/test_dictionary.rb +8 -1
  80. data/test/hexapdf/test_dictionary_fields.rb +2 -2
  81. data/test/hexapdf/test_document.rb +18 -10
  82. data/test/hexapdf/test_object.rb +71 -26
  83. data/test/hexapdf/test_parser.rb +171 -53
  84. data/test/hexapdf/test_pdf_array.rb +8 -1
  85. data/test/hexapdf/test_revisions.rb +35 -0
  86. data/test/hexapdf/test_writer.rb +2 -2
  87. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +296 -38
  88. data/test/hexapdf/type/acro_form/test_button_field.rb +22 -2
  89. data/test/hexapdf/type/acro_form/test_choice_field.rb +92 -9
  90. data/test/hexapdf/type/acro_form/test_field.rb +39 -0
  91. data/test/hexapdf/type/acro_form/test_form.rb +87 -15
  92. data/test/hexapdf/type/acro_form/test_text_field.rb +77 -1
  93. data/test/hexapdf/type/test_font_simple.rb +2 -1
  94. data/test/hexapdf/type/test_font_true_type.rb +6 -0
  95. data/test/hexapdf/type/test_form.rb +26 -1
  96. data/test/hexapdf/type/test_page.rb +45 -7
  97. data/test/hexapdf/type/test_page_tree_node.rb +42 -0
  98. data/test/hexapdf/utils/test_bit_field.rb +2 -0
  99. data/test/hexapdf/utils/test_object_hash.rb +5 -0
  100. data/test/hexapdf/utils/test_sorted_tree_node.rb +10 -9
  101. data/test/test_helper.rb +2 -0
  102. metadata +6 -11
@@ -140,7 +140,7 @@ describe HexaPDF::Type::AcroForm::ButtonField do
140
140
  it "sets a correct field value" do
141
141
  @field.create_widget(@doc.pages.add, value: :button1)
142
142
 
143
- @field.field_value = :button1
143
+ @field.field_value = "button1"
144
144
  assert_equal(:button1, @field[:V])
145
145
  @field.field_value = nil
146
146
  assert_equal(:Off, @field[:V])
@@ -158,7 +158,7 @@ describe HexaPDF::Type::AcroForm::ButtonField do
158
158
  end
159
159
 
160
160
  it "returns an array of possible values" do
161
- @field.create_widget(@doc.pages.add, value: :Test)
161
+ @field.create_widget(@doc.pages.add, value: "Test")
162
162
  @field.create_widget(@doc.pages.add, value: :x)
163
163
  @field.create_widget(@doc.pages.add, value: :y)
164
164
  assert_equal([:Test, :x, :y], @field.radio_button_values)
@@ -172,6 +172,7 @@ describe HexaPDF::Type::AcroForm::ButtonField do
172
172
  assert_equal(:solid, border_style.style)
173
173
  assert_equal([1], widget.background_color.components)
174
174
  assert_equal(:circle, widget.marker_style.style)
175
+ assert_equal({test: nil, Off: nil}, widget[:AP][:N].value)
175
176
  end
176
177
 
177
178
  it "always creates standalone widgets" do
@@ -181,6 +182,10 @@ describe HexaPDF::Type::AcroForm::ButtonField do
181
182
  it "fails if the value argument is not provided for create_widget" do
182
183
  assert_raises(ArgumentError) { @field.create_widget(@doc.pages.add) }
183
184
  end
185
+
186
+ it "fails if the value argument for create_widget doesn't respond to to_sym" do
187
+ assert_raises(ArgumentError) { @field.create_widget(@doc.pages.add, value: 5) }
188
+ end
184
189
  end
185
190
 
186
191
  it "returns a default field value" do
@@ -228,6 +233,14 @@ describe HexaPDF::Type::AcroForm::ButtonField do
228
233
  refute_same(off, widget.appearance.normal_appearance[:Off])
229
234
  end
230
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
+
231
244
  it "fails for unsupported button types" do
232
245
  @field.flag(:push_button)
233
246
  @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
@@ -258,6 +271,13 @@ describe HexaPDF::Type::AcroForm::ButtonField do
258
271
  @field.update_widgets
259
272
  assert_equal(:Yes, widget[:AS])
260
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
261
281
  end
262
282
 
263
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
 
@@ -99,6 +99,12 @@ describe HexaPDF::Type::AcroForm::Field do
99
99
  refute(@field.terminal_field?)
100
100
  end
101
101
 
102
+ it "can check whether a widget is embedded in the field" do
103
+ refute(@field.embedded_widget?)
104
+ @field[:Subtype] = :Wdiget
105
+ assert(@field.embedded_widget?)
106
+ end
107
+
102
108
  describe "each_widget" do
103
109
  it "yields a wrapped instance of self if a single widget is embedded" do
104
110
  @field[:Subtype] = :Widget
@@ -175,6 +181,39 @@ describe HexaPDF::Type::AcroForm::Field do
175
181
  end
176
182
  end
177
183
 
184
+ describe "delete_widget" do
185
+ before do
186
+ @page = @doc.pages.add
187
+ end
188
+
189
+ it "does nothing if the provided widget doesn't belong to the field" do
190
+ wrong_widget = @doc.add({Subtype: :Widget})
191
+
192
+ @field.create_widget(@page)
193
+ @field.delete_widget(wrong_widget)
194
+ assert_equal(:Widget, @field[:Subtype])
195
+
196
+ @field.create_widget(@page)
197
+ @field.delete_widget(wrong_widget)
198
+ assert_equal(2, @field[:Kids].size)
199
+ end
200
+
201
+ it "deletes the widget if it is embedded" do
202
+ widget = @field.create_widget(@page)
203
+ @field.delete_widget(widget)
204
+ refute(@field.key?(:Subtype))
205
+ assert(@page[:Annots].empty?)
206
+ end
207
+
208
+ it "deletes the widget if it is not embedded" do
209
+ @field.create_widget(@page)
210
+ widget2 = @field.create_widget(@page)
211
+ @field.delete_widget(widget2)
212
+ assert_equal(1, @field[:Kids].size)
213
+ assert_equal(@field[:Kids].value, @page[:Annots].value)
214
+ end
215
+ end
216
+
178
217
  describe "perform_validation" do
179
218
  before do
180
219
  @field[:FT] = :Tx
@@ -7,7 +7,7 @@ require 'hexapdf/type/acro_form/form'
7
7
  describe HexaPDF::Type::AcroForm::Form do
8
8
  before do
9
9
  @doc = HexaPDF::Document.new
10
- @acro_form = @doc.add({}, type: :XXAcroForm)
10
+ @acro_form = @doc.add({Fields: []}, type: :XXAcroForm)
11
11
  end
12
12
 
13
13
  describe "signature flags" do
@@ -49,7 +49,7 @@ describe HexaPDF::Type::AcroForm::Form do
49
49
  root_fields = @acro_form.find_root_fields
50
50
  assert_equal(result, root_fields.map(&:value))
51
51
  assert_kind_of(HexaPDF::Type::AcroForm::TextField, root_fields[0])
52
- refute(@acro_form.key?(:Fields))
52
+ assert_equal([], @acro_form[:Fields].value)
53
53
 
54
54
  @acro_form.find_root_fields!
55
55
  assert_equal(result, @acro_form[:Fields].value.map(&:value))
@@ -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
@@ -204,9 +276,9 @@ describe HexaPDF::Type::AcroForm::Form do
204
276
 
205
277
  it "checks whether the font used in /DA is available in /DR" do
206
278
  @acro_form[:DA] = '/F2 0 Tf /F1 0 Tf'
207
- refute(@acro_form.validate {|msg, c| assert_match(/DR must also be present/, msg) })
279
+ refute(@acro_form.validate {|msg| assert_match(/DR must also be present/, msg) })
208
280
  @acro_form.default_resources[:Font] = {}
209
- refute(@acro_form.validate {|msg, c| assert_match(/font.*is not.*resource/, msg) })
281
+ refute(@acro_form.validate {|msg| assert_match(/font.*is not.*resource/, msg) })
210
282
  @acro_form.default_resources[:Font][:F1] = :yes
211
283
  assert(@acro_form.validate)
212
284
  end
@@ -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 }
@@ -114,6 +188,8 @@ describe HexaPDF::Type::AcroForm::TextField do
114
188
  assert(@field.validate)
115
189
  @field[:MaxLen] = 2
116
190
  refute(@field.validate)
191
+ @field[:V] = nil
192
+ assert(@field.validate)
117
193
  end
118
194
  end
119
195
  end