hexapdf 0.14.4 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +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
|