hexapdf 0.14.4 → 0.15.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 +38 -0
- data/lib/hexapdf/cli/form.rb +30 -8
- data/lib/hexapdf/configuration.rb +18 -3
- data/lib/hexapdf/error.rb +4 -3
- data/lib/hexapdf/parser.rb +18 -6
- data/lib/hexapdf/revision.rb +16 -0
- data/lib/hexapdf/type/acro_form.rb +1 -0
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +29 -17
- data/lib/hexapdf/type/acro_form/button_field.rb +8 -4
- data/lib/hexapdf/type/acro_form/field.rb +1 -0
- data/lib/hexapdf/type/acro_form/form.rb +37 -0
- data/lib/hexapdf/type/acro_form/signature_field.rb +223 -0
- data/lib/hexapdf/type/annotation.rb +13 -9
- data/lib/hexapdf/type/annotations/widget.rb +3 -1
- data/lib/hexapdf/type/font_descriptor.rb +9 -2
- data/lib/hexapdf/type/page.rb +81 -0
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/test_parser.rb +23 -3
- data/test/hexapdf/test_revision.rb +21 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +21 -2
- data/test/hexapdf/type/acro_form/test_button_field.rb +13 -7
- data/test/hexapdf/type/acro_form/test_field.rb +5 -0
- data/test/hexapdf/type/acro_form/test_form.rb +46 -2
- data/test/hexapdf/type/acro_form/test_signature_field.rb +38 -0
- data/test/hexapdf/type/annotations/test_widget.rb +2 -0
- data/test/hexapdf/type/test_annotation.rb +20 -10
- data/test/hexapdf/type/test_font_descriptor.rb +7 -0
- data/test/hexapdf/type/test_page.rb +187 -49
- metadata +4 -2
@@ -105,6 +105,27 @@ describe HexaPDF::Revision do
|
|
105
105
|
end
|
106
106
|
end
|
107
107
|
|
108
|
+
describe "update" do
|
109
|
+
before do
|
110
|
+
@rev.add(@obj)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "updates the object if it has the same data instance" do
|
114
|
+
x = HexaPDF::Object.new(@obj.data)
|
115
|
+
y = @rev.update(x)
|
116
|
+
assert_same(x, y)
|
117
|
+
refute_same(x, @obj)
|
118
|
+
assert_same(x, @rev.object(@ref))
|
119
|
+
end
|
120
|
+
|
121
|
+
it "doesn't update the object if it refers to a different data instance" do
|
122
|
+
x = HexaPDF::Object.new(:value, oid: 5)
|
123
|
+
assert_nil(@rev.update(x))
|
124
|
+
x.data.oid = 1
|
125
|
+
assert_nil(@rev.update(x))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
108
129
|
describe "delete" do
|
109
130
|
before do
|
110
131
|
@rev.add(@obj)
|
data/test/hexapdf/test_writer.rb
CHANGED
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
|
|
40
40
|
219
|
41
41
|
%%EOF
|
42
42
|
3 0 obj
|
43
|
-
<</Producer(HexaPDF version 0.
|
43
|
+
<</Producer(HexaPDF version 0.15.0)>>
|
44
44
|
endobj
|
45
45
|
xref
|
46
46
|
3 1
|
@@ -72,7 +72,7 @@ describe HexaPDF::Writer do
|
|
72
72
|
141
|
73
73
|
%%EOF
|
74
74
|
6 0 obj
|
75
|
-
<</Producer(HexaPDF version 0.
|
75
|
+
<</Producer(HexaPDF version 0.15.0)>>
|
76
76
|
endobj
|
77
77
|
2 0 obj
|
78
78
|
<</Length 10>>stream
|
@@ -748,17 +748,36 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
748
748
|
|
749
749
|
describe "font resolution in case the referenced font is not usable" do
|
750
750
|
before do
|
751
|
-
@doc.config['acro_form.fallback_font'] = ['Times', {variant: :
|
751
|
+
@doc.config['acro_form.fallback_font'] = ['Times', {variant: :italic}]
|
752
752
|
@field[:V] = 'Test'
|
753
753
|
end
|
754
754
|
|
755
755
|
it "uses the fallback font if the font is not usable" do
|
756
756
|
def (@form.default_resources.font(:F1)).font_wrapper; nil; end
|
757
757
|
@generator.create_appearances
|
758
|
-
assert_equal(:'Times-
|
758
|
+
assert_equal(:'Times-Italic', @widget[:AP][:N][:Resources][:Font][:F2][:BaseFont])
|
759
759
|
end
|
760
760
|
|
761
761
|
it "uses the fallback font if the font is not found" do
|
762
|
+
@form.default_resources[:Font].delete(:F1)
|
763
|
+
@generator.create_appearances
|
764
|
+
assert_equal(:'Times-Italic', @widget[:AP][:N][:Resources][:Font][:F1][:BaseFont])
|
765
|
+
end
|
766
|
+
|
767
|
+
it "respects a simple fallback font name" do
|
768
|
+
@doc.config['acro_form.fallback_font'] = 'Times'
|
769
|
+
@form.default_resources[:Font].delete(:F1)
|
770
|
+
@generator.create_appearances
|
771
|
+
assert_equal(:'Times-Roman', @widget[:AP][:N][:Resources][:Font][:F1][:BaseFont])
|
772
|
+
end
|
773
|
+
|
774
|
+
it "respects a fallback font callable object" do
|
775
|
+
field = @field
|
776
|
+
@doc.config['acro_form.fallback_font'] = proc do |field_arg, font_arg|
|
777
|
+
assert_same(field.data, field_arg.data)
|
778
|
+
assert_nil(font_arg)
|
779
|
+
'Times'
|
780
|
+
end
|
762
781
|
@form.default_resources[:Font].delete(:F1)
|
763
782
|
@generator.create_appearances
|
764
783
|
assert_equal(:'Times-Roman', @widget[:AP][:N][:Resources][:Font][:F1][:BaseFont])
|
@@ -225,20 +225,26 @@ describe HexaPDF::Type::AcroForm::ButtonField do
|
|
225
225
|
it "won't generate appearances if they already exist" do
|
226
226
|
widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
227
227
|
@field.create_appearances
|
228
|
-
yes = widget.
|
229
|
-
off = widget.
|
230
|
-
widget.appearance.normal_appearance.delete(:Off)
|
228
|
+
yes = widget.appearance_dict.normal_appearance[:Yes]
|
229
|
+
off = widget.appearance_dict.normal_appearance[:Off]
|
231
230
|
@field.create_appearances
|
232
|
-
assert_same(yes, widget.
|
233
|
-
|
231
|
+
assert_same(yes, widget.appearance_dict.normal_appearance[:Yes])
|
232
|
+
assert_same(off, widget.appearance_dict.normal_appearance[:Off])
|
233
|
+
|
234
|
+
@field.delete_widget(widget)
|
235
|
+
@field.flag(:push_button)
|
236
|
+
widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0], AP: {N: @doc.wrap({}, stream: '')})
|
237
|
+
appearance = widget.appearance_dict.normal_appearance
|
238
|
+
@field.create_appearances
|
239
|
+
assert_same(appearance, widget.appearance_dict.normal_appearance)
|
234
240
|
end
|
235
241
|
|
236
242
|
it "always generates appearances if force is true" do
|
237
243
|
widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
238
244
|
@field.create_appearances
|
239
|
-
yes = widget.
|
245
|
+
yes = widget.appearance_dict.normal_appearance[:Yes]
|
240
246
|
@field.create_appearances(force: true)
|
241
|
-
refute_same(yes, widget.
|
247
|
+
refute_same(yes, widget.appearance_dict.normal_appearance[:Yes])
|
242
248
|
end
|
243
249
|
|
244
250
|
it "fails for unsupported button types" do
|
@@ -200,9 +200,14 @@ describe HexaPDF::Type::AcroForm::Field do
|
|
200
200
|
|
201
201
|
it "deletes the widget if it is embedded" do
|
202
202
|
widget = @field.create_widget(@page)
|
203
|
+
@doc.revisions.current.update(widget)
|
204
|
+
assert_same(widget, @doc.object(widget))
|
205
|
+
refute_same(@field, @doc.object(@field))
|
206
|
+
|
203
207
|
@field.delete_widget(widget)
|
204
208
|
refute(@field.key?(:Subtype))
|
205
209
|
assert(@page[:Annots].empty?)
|
210
|
+
assert_same(@field, @doc.object(@field))
|
206
211
|
end
|
207
212
|
|
208
213
|
it "deletes the widget if it is not embedded" do
|
@@ -254,8 +254,8 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
254
254
|
|
255
255
|
it "creates the appearances of all field widgets if necessary" do
|
256
256
|
@acro_form.create_appearances
|
257
|
-
assert(@tf.each_widget.all? {|w| w.
|
258
|
-
assert(@cb.each_widget.all? {|w| w.
|
257
|
+
assert(@tf.each_widget.all? {|w| w.appearance_dict.normal_appearance.kind_of?(HexaPDF::Stream) })
|
258
|
+
assert(@cb.each_widget.all? {|w| w.appearance_dict.normal_appearance[:Yes].kind_of?(HexaPDF::Stream) })
|
259
259
|
end
|
260
260
|
|
261
261
|
it "force the creation of appearances if force is true" do
|
@@ -268,6 +268,50 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
268
268
|
end
|
269
269
|
end
|
270
270
|
|
271
|
+
describe "flatten" do
|
272
|
+
before do
|
273
|
+
@acro_form.root_fields << @doc.wrap({T: 'test'})
|
274
|
+
@tf = @acro_form.create_text_field('textfields')
|
275
|
+
@tf.set_default_appearance_string
|
276
|
+
@tf[:V] = 'Test'
|
277
|
+
@tf.create_widget(@doc.pages.add)
|
278
|
+
@cb = @acro_form.create_check_box('test.checkbox')
|
279
|
+
@cb.create_widget(@doc.pages[0])
|
280
|
+
@cb.create_widget(@doc.pages.add)
|
281
|
+
end
|
282
|
+
|
283
|
+
it "creates the missing appearances if instructed to do so" do
|
284
|
+
assert_equal(3, @acro_form.flatten(create_appearances: false).size)
|
285
|
+
assert_equal(0, @acro_form.flatten(create_appearances: true).size)
|
286
|
+
end
|
287
|
+
|
288
|
+
it "flattens the whole interactive form" do
|
289
|
+
result = @acro_form.flatten
|
290
|
+
assert(result.empty?)
|
291
|
+
assert(@tf.null?)
|
292
|
+
assert(@cb.null?)
|
293
|
+
assert(@acro_form.null?)
|
294
|
+
refute(@doc.catalog.key?(:AcroForm))
|
295
|
+
end
|
296
|
+
|
297
|
+
it "flattens the given fields" do
|
298
|
+
result = @acro_form.flatten(fields: [@cb])
|
299
|
+
assert(result.empty?)
|
300
|
+
assert(@cb.null?)
|
301
|
+
refute(@tf.null?)
|
302
|
+
refute(@acro_form.null?)
|
303
|
+
assert(@doc.catalog.key?(:AcroForm))
|
304
|
+
end
|
305
|
+
|
306
|
+
it "doesn't delete the form object if not all fields were flattened" do
|
307
|
+
@acro_form.create_appearances
|
308
|
+
@tf.delete(:AP)
|
309
|
+
result = @acro_form.flatten(create_appearances: false)
|
310
|
+
assert_equal(1, result.size)
|
311
|
+
assert(@doc.catalog.key?(:AcroForm))
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
271
315
|
describe "perform_validation" do
|
272
316
|
it "checks whether the /DR field is available when /DA is set" do
|
273
317
|
@acro_form[:DA] = 'test'
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'hexapdf/document'
|
5
|
+
require 'hexapdf/type/acro_form/signature_field'
|
6
|
+
|
7
|
+
describe HexaPDF::Type::AcroForm::SignatureField::LockDictionary do
|
8
|
+
it "validates the presence of the /Fields key" do
|
9
|
+
doc = HexaPDF::Document.new
|
10
|
+
obj = HexaPDF::Type::AcroForm::SignatureField::LockDictionary.new({Action: :All}, document: doc)
|
11
|
+
assert(obj.validate)
|
12
|
+
obj[:Action] = :Include
|
13
|
+
refute(obj.validate)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe HexaPDF::Type::AcroForm::SignatureField do
|
18
|
+
before do
|
19
|
+
@doc = HexaPDF::Document.new
|
20
|
+
@field = @doc.wrap({}, type: :XXAcroFormField, subtype: :Sig)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "sets the field value" do
|
24
|
+
@field.field_value = {Empty: :True}
|
25
|
+
assert_equal({Empty: :True}, @field[:V].value)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "gets the field value" do
|
29
|
+
@field[:V] = {Empty: :True}
|
30
|
+
assert_equal({Empty: :True}, @field.field_value.value)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "validates the value of the /FT field" do
|
34
|
+
refute(@field.validate(auto_correct: false))
|
35
|
+
assert(@field.validate)
|
36
|
+
assert_equal(:Sig, @field.field_type)
|
37
|
+
end
|
38
|
+
end
|
@@ -56,6 +56,8 @@ describe HexaPDF::Type::Annotations::Widget do
|
|
56
56
|
|
57
57
|
describe "background_color" do
|
58
58
|
it "returns the current background color" do
|
59
|
+
assert_nil(@widget.background_color)
|
60
|
+
@widget[:MK] = {BG: []}
|
59
61
|
assert_nil(@widget.background_color)
|
60
62
|
@widget[:MK] = {BG: [1]}
|
61
63
|
assert_equal([1], @widget.background_color.components)
|
@@ -40,19 +40,29 @@ describe HexaPDF::Type::Annotation do
|
|
40
40
|
|
41
41
|
it "returns the appearance dictionary" do
|
42
42
|
@annot[:AP] = :yes
|
43
|
-
assert_equal(:yes, @annot.
|
43
|
+
assert_equal(:yes, @annot.appearance_dict)
|
44
44
|
end
|
45
45
|
|
46
|
-
it "
|
47
|
-
|
46
|
+
it "returns the appearance stream of the given type" do
|
47
|
+
assert_nil(@annot.appearance)
|
48
|
+
|
48
49
|
@annot[:AP] = {N: {}}
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
@annot[:AP][:N] =
|
53
|
-
|
54
|
-
|
55
|
-
|
50
|
+
assert_nil(@annot.appearance)
|
51
|
+
|
52
|
+
stream = @doc.wrap({}, stream: '')
|
53
|
+
@annot[:AP][:N] = stream
|
54
|
+
appearance = @annot.appearance
|
55
|
+
assert_same(stream.data, appearance.data)
|
56
|
+
assert_equal(:Form, appearance[:Subtype])
|
57
|
+
|
58
|
+
@annot[:AP][:N] = {X: stream}
|
59
|
+
assert_nil(@annot.appearance)
|
60
|
+
|
61
|
+
@annot[:AS] = :X
|
62
|
+
assert_same(stream.data, @annot.appearance.data)
|
63
|
+
|
64
|
+
@annot[:AP][:D] = {X: stream}
|
65
|
+
assert_same(stream.data, @annot.appearance(:down).data)
|
56
66
|
end
|
57
67
|
|
58
68
|
describe "flags" do
|
@@ -44,6 +44,13 @@ describe HexaPDF::Type::FontDescriptor do
|
|
44
44
|
refute(@font_desc.validate)
|
45
45
|
end
|
46
46
|
|
47
|
+
it "deletes the /FontWeight value if it doesn't contain a valid value" do
|
48
|
+
@font_desc[:FontWeight] = 350
|
49
|
+
refute(@font_desc.validate(auto_correct: false))
|
50
|
+
assert(@font_desc.validate)
|
51
|
+
refute(@font_desc.key?(:FontWeight))
|
52
|
+
end
|
53
|
+
|
47
54
|
it "updates the /Descent value if it is not a negative number" do
|
48
55
|
@font_desc[:Descent] = 5
|
49
56
|
refute(@font_desc.validate(auto_correct: false))
|
@@ -26,7 +26,7 @@ describe HexaPDF::Type::Page do
|
|
26
26
|
end
|
27
27
|
|
28
28
|
# Asserts that the page's contents contains the operators.
|
29
|
-
def
|
29
|
+
def assert_page_operators(page, operators)
|
30
30
|
processor = TestHelper::OperatorRecorder.new
|
31
31
|
page.process_contents(processor)
|
32
32
|
assert_equal(operators, processor.recorded_ops)
|
@@ -293,8 +293,8 @@ describe HexaPDF::Type::Page do
|
|
293
293
|
it "parses the contents and processes it" do
|
294
294
|
page = @doc.pages.add
|
295
295
|
page[:Contents] = @doc.wrap({}, stream: 'q 10 w Q')
|
296
|
-
|
297
|
-
|
296
|
+
assert_page_operators(page, [[:save_graphics_state], [:set_line_width, [10]],
|
297
|
+
[:restore_graphics_state]])
|
298
298
|
end
|
299
299
|
end
|
300
300
|
|
@@ -333,60 +333,60 @@ describe HexaPDF::Type::Page do
|
|
333
333
|
|
334
334
|
it "works correctly if invoked on an empty page, using type :page in first invocation" do
|
335
335
|
@page.canvas.line_width = 10
|
336
|
-
|
336
|
+
assert_page_operators(@page, [[:set_line_width, [10]]])
|
337
337
|
|
338
338
|
@page.canvas(type: :overlay).line_width = 5
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
339
|
+
assert_page_operators(@page, [[:save_graphics_state], [:restore_graphics_state],
|
340
|
+
[:save_graphics_state], [:set_line_width, [10]],
|
341
|
+
[:restore_graphics_state], [:save_graphics_state],
|
342
|
+
[:set_line_width, [5]], [:restore_graphics_state]])
|
343
343
|
|
344
344
|
@page.canvas(type: :underlay).line_width = 2
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
345
|
+
assert_page_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
|
346
|
+
[:restore_graphics_state], [:save_graphics_state],
|
347
|
+
[:set_line_width, [10]],
|
348
|
+
[:restore_graphics_state], [:save_graphics_state],
|
349
|
+
[:set_line_width, [5]], [:restore_graphics_state]])
|
350
350
|
end
|
351
351
|
|
352
352
|
it "works correctly if invoked on an empty page, using type :underlay in first invocation" do
|
353
353
|
@page.canvas(type: :underlay).line_width = 2
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
354
|
+
assert_page_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
|
355
|
+
[:restore_graphics_state], [:save_graphics_state],
|
356
|
+
[:restore_graphics_state], [:save_graphics_state],
|
357
|
+
[:restore_graphics_state]])
|
358
358
|
|
359
359
|
@page.canvas.line_width = 10
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
360
|
+
assert_page_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
|
361
|
+
[:restore_graphics_state], [:save_graphics_state],
|
362
|
+
[:set_line_width, [10]], [:restore_graphics_state],
|
363
|
+
[:save_graphics_state], [:restore_graphics_state]])
|
364
364
|
|
365
365
|
@page.canvas(type: :overlay).line_width = 5
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
366
|
+
assert_page_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
|
367
|
+
[:restore_graphics_state], [:save_graphics_state],
|
368
|
+
[:set_line_width, [10]],
|
369
|
+
[:restore_graphics_state], [:save_graphics_state],
|
370
|
+
[:set_line_width, [5]], [:restore_graphics_state]])
|
371
371
|
end
|
372
372
|
|
373
373
|
it "works correctly if invoked on a page with existing contents" do
|
374
374
|
@page.contents = "10 w"
|
375
375
|
|
376
376
|
@page.canvas(type: :overlay).line_width = 5
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
377
|
+
assert_page_operators(@page, [[:save_graphics_state], [:restore_graphics_state],
|
378
|
+
[:save_graphics_state], [:set_line_width, [10]],
|
379
|
+
[:restore_graphics_state],
|
380
|
+
[:save_graphics_state], [:set_line_width, [5]],
|
381
|
+
[:restore_graphics_state]])
|
382
382
|
|
383
383
|
@page.canvas(type: :underlay).line_width = 2
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
384
|
+
assert_page_operators(@page, [[:save_graphics_state], [:set_line_width, [2]],
|
385
|
+
[:restore_graphics_state], [:save_graphics_state],
|
386
|
+
[:set_line_width, [10]],
|
387
|
+
[:restore_graphics_state],
|
388
|
+
[:save_graphics_state], [:set_line_width, [5]],
|
389
|
+
[:restore_graphics_state]])
|
390
390
|
end
|
391
391
|
|
392
392
|
it "works correctly if the page has its origin not at (0,0)" do
|
@@ -395,20 +395,20 @@ describe HexaPDF::Type::Page do
|
|
395
395
|
@page.canvas(type: :page).line_width = 2
|
396
396
|
@page.canvas(type: :overlay).line_width = 2
|
397
397
|
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
398
|
+
assert_page_operators(@page, [[:save_graphics_state],
|
399
|
+
[:concatenate_matrix, [1, 0, 0, 1, -10, -5]],
|
400
|
+
[:set_line_width, [2]],
|
401
|
+
[:restore_graphics_state],
|
402
402
|
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
403
|
+
[:save_graphics_state],
|
404
|
+
[:concatenate_matrix, [1, 0, 0, 1, -10, -5]],
|
405
|
+
[:set_line_width, [2]],
|
406
|
+
[:restore_graphics_state],
|
407
407
|
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
408
|
+
[:save_graphics_state],
|
409
|
+
[:concatenate_matrix, [1, 0, 0, 1, -10, -5]],
|
410
|
+
[:set_line_width, [2]],
|
411
|
+
[:restore_graphics_state]])
|
412
412
|
end
|
413
413
|
|
414
414
|
it "fails if the page canvas is requested for a page with existing contents" do
|
@@ -446,4 +446,142 @@ describe HexaPDF::Type::Page do
|
|
446
446
|
refute_same(form.raw_stream, page[:Contents].raw_stream)
|
447
447
|
end
|
448
448
|
end
|
449
|
+
|
450
|
+
describe "flatten_annotations" do
|
451
|
+
before do
|
452
|
+
@page = @doc.pages.add
|
453
|
+
@appearance = @doc.add({Type: :XObject, Subtype: :Form, BBox: [-10, -5, 50, 20]}, stream: "")
|
454
|
+
@annot1 = @doc.add({Type: :Annot, Subtype: :Text, Rect: [100, 100, 160, 125], AP: {N: @appearance}})
|
455
|
+
@annot2 = @doc.add({Rect: [10, 10, 70, 35], AP: {N: @appearance}})
|
456
|
+
@page[:Annots] = [@annot1, @annot2]
|
457
|
+
@canvas = @page.canvas(type: :overlay)
|
458
|
+
end
|
459
|
+
|
460
|
+
it "does nothing if the page doesn't have any annotations" do
|
461
|
+
@page.delete(:Annots)
|
462
|
+
result = @page.flatten_annotations
|
463
|
+
assert(result.empty?)
|
464
|
+
assert_operators(@canvas.contents, [])
|
465
|
+
end
|
466
|
+
|
467
|
+
it "flattens all annotations of the page by default" do
|
468
|
+
result = @page.flatten_annotations
|
469
|
+
assert(result.empty?)
|
470
|
+
assert_operators(@canvas.contents, [[:save_graphics_state],
|
471
|
+
[:save_graphics_state],
|
472
|
+
[:concatenate_matrix, [1.0, 0, 0, 1.0, 110, 105]],
|
473
|
+
[:paint_xobject, [:XO1]],
|
474
|
+
[:restore_graphics_state],
|
475
|
+
[:save_graphics_state],
|
476
|
+
[:concatenate_matrix, [1.0, 0, 0, 1.0, 20, 15]],
|
477
|
+
[:paint_xobject, [:XO1]],
|
478
|
+
[:restore_graphics_state],
|
479
|
+
[:restore_graphics_state]])
|
480
|
+
assert(@annot1.null?)
|
481
|
+
assert(@annot2.null?)
|
482
|
+
end
|
483
|
+
|
484
|
+
it "only deletes the widget annotation of a form field even if it is embedded in the field object" do
|
485
|
+
form = @doc.acro_form(create: true)
|
486
|
+
field = form.create_text_field('test')
|
487
|
+
widget = field.create_widget(@page, Rect: [200, 200, 250, 215])
|
488
|
+
field.field_value = 'hello'
|
489
|
+
|
490
|
+
assert_same(widget.data, field.data)
|
491
|
+
result = @page.flatten_annotations([widget])
|
492
|
+
assert(result.empty?)
|
493
|
+
refute(field.null?)
|
494
|
+
end
|
495
|
+
|
496
|
+
it "does nothing if a given annotation is not part of the page" do
|
497
|
+
annots = [{Test: :Object}]
|
498
|
+
result = @page.flatten_annotations(annots)
|
499
|
+
assert_equal(annots, result)
|
500
|
+
end
|
501
|
+
|
502
|
+
it "deletes hidden annotations but doesn't include them in the content stream" do
|
503
|
+
@annot1.flag(:hidden)
|
504
|
+
result = @page.flatten_annotations
|
505
|
+
assert(result.empty?)
|
506
|
+
assert(@annot1.null?)
|
507
|
+
assert_operators(@canvas.contents, [[:save_graphics_state],
|
508
|
+
[:save_graphics_state],
|
509
|
+
[:concatenate_matrix, [1.0, 0, 0, 1.0, 20, 15]],
|
510
|
+
[:paint_xobject, [:XO1]],
|
511
|
+
[:restore_graphics_state],
|
512
|
+
[:restore_graphics_state]])
|
513
|
+
end
|
514
|
+
|
515
|
+
it "deletes invisible annotations but doesn't include them in the content stream" do
|
516
|
+
@annot1.flag(:invisible)
|
517
|
+
result = @page.flatten_annotations
|
518
|
+
assert(result.empty?)
|
519
|
+
assert(@annot1.null?)
|
520
|
+
assert_operators(@canvas.contents, [[:save_graphics_state],
|
521
|
+
[:save_graphics_state],
|
522
|
+
[:concatenate_matrix, [1.0, 0, 0, 1.0, 20, 15]],
|
523
|
+
[:paint_xobject, [:XO1]],
|
524
|
+
[:restore_graphics_state],
|
525
|
+
[:restore_graphics_state]])
|
526
|
+
end
|
527
|
+
|
528
|
+
it "ignores annotations without appearane stream" do
|
529
|
+
@annot1.delete(:AP)
|
530
|
+
result = @page.flatten_annotations
|
531
|
+
assert_equal([@annot1], result)
|
532
|
+
refute(@annot1.empty?)
|
533
|
+
assert_operators(@canvas.contents, [[:save_graphics_state],
|
534
|
+
[:save_graphics_state],
|
535
|
+
[:concatenate_matrix, [1.0, 0, 0, 1.0, 20, 15]],
|
536
|
+
[:paint_xobject, [:XO1]],
|
537
|
+
[:restore_graphics_state],
|
538
|
+
[:restore_graphics_state]])
|
539
|
+
end
|
540
|
+
|
541
|
+
it "adjusts the position to counter the translation in #canvas based on the page's media box" do
|
542
|
+
@page[:MediaBox] = [-15, -15, 100, 100]
|
543
|
+
@page.flatten_annotations
|
544
|
+
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 15, 15]], range: 1)
|
545
|
+
end
|
546
|
+
|
547
|
+
it "adjusts the position in case the form /Matrix has an offset" do
|
548
|
+
@appearance[:Matrix] = [1, 0, 0, 1, 15, 15]
|
549
|
+
@page.flatten_annotations
|
550
|
+
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 95, 90]], range: 2)
|
551
|
+
end
|
552
|
+
|
553
|
+
it "adjusts the position for an appearance with a 90 degree rotation" do
|
554
|
+
@appearance[:Matrix] = [0, 1, -1, 0, 0, 0]
|
555
|
+
@annot1[:Rect] = [100, 100, 125, 160]
|
556
|
+
@page.flatten_annotations
|
557
|
+
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 120, 110]], range: 2)
|
558
|
+
end
|
559
|
+
|
560
|
+
it "adjusts the position for an appearance with a -90 degree rotation" do
|
561
|
+
@appearance[:Matrix] = [0, -1, 1, 0, 0, 0]
|
562
|
+
@annot1[:Rect] = [100, 100, 125, 160]
|
563
|
+
@page.flatten_annotations
|
564
|
+
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 105, 150]], range: 2)
|
565
|
+
end
|
566
|
+
|
567
|
+
it "adjusts the position for an appearance with a 180 degree rotation" do
|
568
|
+
@appearance[:Matrix] = [-1, 0, 0, -1, 0, 0]
|
569
|
+
@page.flatten_annotations
|
570
|
+
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 150, 120]], range: 2)
|
571
|
+
end
|
572
|
+
|
573
|
+
it "ignores an appearance with a rotation that is not a mulitple of 90" do
|
574
|
+
@appearance[:Matrix] = [-1, 0.5, 0.5, -1, 0, 0]
|
575
|
+
result = @page.flatten_annotations
|
576
|
+
assert_equal([@annot1, @annot2], result)
|
577
|
+
assert_operators(@canvas.contents, [[:save_graphics_state], [:restore_graphics_state]])
|
578
|
+
end
|
579
|
+
|
580
|
+
it "scales the appearance to fit into the annotations's rectangle" do
|
581
|
+
@annot1[:Rect] = [100, 100, 130, 150]
|
582
|
+
@page.flatten_annotations
|
583
|
+
assert_operators(@canvas.contents, [:concatenate_matrix, [0.5, 0, 0, 2, 110, 105]], range: 2)
|
584
|
+
end
|
585
|
+
|
586
|
+
end
|
449
587
|
end
|