hexapdf 0.27.0 → 0.28.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 +59 -1
- data/examples/019-acro_form.rb +14 -3
- data/examples/023-images.rb +30 -0
- data/lib/hexapdf/cli/info.rb +5 -1
- data/lib/hexapdf/cli/inspect.rb +2 -2
- data/lib/hexapdf/cli/split.rb +2 -2
- data/lib/hexapdf/configuration.rb +1 -2
- data/lib/hexapdf/content/canvas.rb +8 -3
- data/lib/hexapdf/dictionary.rb +1 -5
- data/lib/hexapdf/document.rb +6 -10
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/importer.rb +32 -27
- data/lib/hexapdf/layout/list_box.rb +1 -5
- data/lib/hexapdf/object.rb +5 -0
- data/lib/hexapdf/parser.rb +13 -0
- data/lib/hexapdf/revision.rb +15 -12
- data/lib/hexapdf/revisions.rb +4 -0
- data/lib/hexapdf/tokenizer.rb +14 -8
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +174 -128
- data/lib/hexapdf/type/acro_form/button_field.rb +5 -3
- data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/field.rb +11 -5
- data/lib/hexapdf/type/acro_form/form.rb +33 -7
- data/lib/hexapdf/type/acro_form/signature_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/text_field.rb +12 -2
- data/lib/hexapdf/type/annotations/widget.rb +3 -0
- data/lib/hexapdf/type/font_true_type.rb +14 -0
- data/lib/hexapdf/type/object_stream.rb +2 -2
- data/lib/hexapdf/type/outline.rb +1 -1
- data/lib/hexapdf/type/page.rb +56 -46
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +2 -3
- data/test/hexapdf/content/test_canvas.rb +5 -0
- data/test/hexapdf/document/test_pages.rb +2 -2
- data/test/hexapdf/encryption/test_aes.rb +1 -1
- data/test/hexapdf/filter/test_predictor.rb +0 -1
- data/test/hexapdf/layout/test_box.rb +2 -1
- data/test/hexapdf/layout/test_column_box.rb +1 -1
- data/test/hexapdf/layout/test_list_box.rb +1 -1
- data/test/hexapdf/test_document.rb +2 -8
- data/test/hexapdf/test_importer.rb +13 -6
- data/test/hexapdf/test_parser.rb +17 -0
- data/test/hexapdf/test_revision.rb +15 -14
- data/test/hexapdf/test_revisions.rb +43 -0
- data/test/hexapdf/test_stream.rb +1 -1
- data/test/hexapdf/test_tokenizer.rb +3 -4
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +135 -56
- data/test/hexapdf/type/acro_form/test_button_field.rb +6 -1
- data/test/hexapdf/type/acro_form/test_choice_field.rb +4 -0
- data/test/hexapdf/type/acro_form/test_field.rb +4 -4
- data/test/hexapdf/type/acro_form/test_form.rb +18 -0
- data/test/hexapdf/type/acro_form/test_signature_field.rb +4 -0
- data/test/hexapdf/type/acro_form/test_text_field.rb +13 -0
- data/test/hexapdf/type/signature/common.rb +3 -1
- data/test/hexapdf/type/test_font_true_type.rb +20 -0
- data/test/hexapdf/type/test_object_stream.rb +2 -1
- data/test/hexapdf/type/test_outline.rb +3 -0
- data/test/hexapdf/type/test_page.rb +67 -30
- data/test/hexapdf/type/test_page_tree_node.rb +4 -2
- metadata +46 -3
@@ -18,12 +18,6 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
18
18
|
@generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
|
19
19
|
end
|
20
20
|
|
21
|
-
it "fails for unsupported button fields" do
|
22
|
-
@field.flag(:push_button)
|
23
|
-
@generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
|
24
|
-
assert_raises(HexaPDF::Error) { @generator.create_appearances }
|
25
|
-
end
|
26
|
-
|
27
21
|
it "fails for unsupported field types" do
|
28
22
|
@field[:FT] = :Unknown
|
29
23
|
assert_raises(HexaPDF::Error) { @generator.create_appearances }
|
@@ -141,8 +135,8 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
141
135
|
end
|
142
136
|
|
143
137
|
def execute
|
144
|
-
@generator.send(:draw_marker, @xform.canvas, @widget[:Rect], @widget.
|
145
|
-
@widget.marker_style)
|
138
|
+
@generator.send(:draw_marker, @xform.canvas, @widget[:Rect].width, @widget[:Rect].height,
|
139
|
+
@widget.border_style.width, @widget.marker_style)
|
146
140
|
end
|
147
141
|
|
148
142
|
it "handles the marker :circle specially for radio button widgets" do
|
@@ -196,6 +190,18 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
196
190
|
[:show_text, ["4"]],
|
197
191
|
[:end_text]])
|
198
192
|
end
|
193
|
+
|
194
|
+
it "draws the default marker if an empty string is specified as marker" do
|
195
|
+
@widget.marker_style(style: '', color: 0.5, size: 5)
|
196
|
+
execute
|
197
|
+
assert_operators(@xform.stream,
|
198
|
+
[[:set_font_and_size, [:F1, 5]],
|
199
|
+
[:set_device_gray_non_stroking_color, [0.5]],
|
200
|
+
[:begin_text],
|
201
|
+
[:set_text_matrix, [1, 0, 0, 1, 2.885, 8.2725]],
|
202
|
+
[:show_text, ["4"]],
|
203
|
+
[:end_text]])
|
204
|
+
end
|
199
205
|
end
|
200
206
|
end
|
201
207
|
|
@@ -213,7 +219,15 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
213
219
|
|
214
220
|
it "updates the widgets' /AS entry to point to the selected appearance" do
|
215
221
|
@generator.create_appearances
|
216
|
-
assert_equal(
|
222
|
+
assert_equal(:Off, @widget[:AS])
|
223
|
+
|
224
|
+
@field.field_value = :Yes
|
225
|
+
@generator.create_appearances
|
226
|
+
assert_equal(:Yes, @widget[:AS])
|
227
|
+
|
228
|
+
@field.delete(:V)
|
229
|
+
@generator.create_appearances
|
230
|
+
assert_equal(:Off, @widget[:AS])
|
217
231
|
end
|
218
232
|
|
219
233
|
it "set the print flag on the widgets" do
|
@@ -231,6 +245,33 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
231
245
|
assert_equal(12, @widget[:Rect].height)
|
232
246
|
end
|
233
247
|
|
248
|
+
describe "takes the rotation into account" do
|
249
|
+
def check_rotation(angle, width, height, matrix)
|
250
|
+
@widget[:MK] = {R: angle}
|
251
|
+
@field[:V] = :Yes
|
252
|
+
@generator.create_appearances
|
253
|
+
form = @widget[:AP][:N][@widget[:AS]]
|
254
|
+
assert_equal([0, 0, width, height], form[:BBox].value)
|
255
|
+
assert_equal(matrix, form[:Matrix].value)
|
256
|
+
end
|
257
|
+
|
258
|
+
it "works for 0 degrees" do
|
259
|
+
check_rotation(-360, @widget[:Rect].width, @widget[:Rect].height, [1, 0, 0, 1, 0, 0])
|
260
|
+
end
|
261
|
+
|
262
|
+
it "works for 90 degrees" do
|
263
|
+
check_rotation(450, @widget[:Rect].height, @widget[:Rect].width, [0, 1, -1, 0, 0, 0])
|
264
|
+
end
|
265
|
+
|
266
|
+
it "works for 180 degrees" do
|
267
|
+
check_rotation(180, @widget[:Rect].width, @widget[:Rect].height, [0, -1, -1, 0, 0, 0])
|
268
|
+
end
|
269
|
+
|
270
|
+
it "works for 270 degrees" do
|
271
|
+
check_rotation(-90, @widget[:Rect].height, @widget[:Rect].width, [0, -1, 1, 0, 0, 0])
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
234
275
|
it "creates the needed appearance streams" do
|
235
276
|
@widget[:AP][:N].delete(:Off)
|
236
277
|
@generator.create_appearances
|
@@ -292,61 +333,21 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
292
333
|
assert_equal(:Off, @widget[:AS])
|
293
334
|
end
|
294
335
|
|
295
|
-
it "set the print flag on the widgets" do
|
296
|
-
@generator.create_appearances
|
297
|
-
assert(@widget.flagged?(:print))
|
298
|
-
end
|
299
|
-
|
300
|
-
it "adjusts the /Rect if width is zero" do
|
301
|
-
@generator.create_appearances
|
302
|
-
assert_equal(12, @widget[:Rect].width)
|
303
|
-
end
|
304
|
-
|
305
|
-
it "adjusts the /Rect if height is zero" do
|
306
|
-
@generator.create_appearances
|
307
|
-
assert_equal(12, @widget[:Rect].height)
|
308
|
-
end
|
309
|
-
|
310
336
|
it "creates the needed appearance streams" do
|
311
337
|
@generator.create_appearances
|
312
338
|
assert_equal(:XObject, @widget[:AP][:N][:Off].type)
|
313
339
|
assert_equal(:XObject, @widget[:AP][:N][:radio].type)
|
314
340
|
end
|
341
|
+
end
|
315
342
|
|
316
|
-
|
317
|
-
|
318
|
-
@
|
319
|
-
|
320
|
-
|
321
|
-
[:set_device_gray_non_stroking_color, [1.0]],
|
322
|
-
[:append_rectangle, [0, 0, 12, 12]],
|
323
|
-
[:fill_path_non_zero],
|
324
|
-
[:append_rectangle, [0.5, 0.5, 11, 11]],
|
325
|
-
[:stroke_path], [:restore_graphics_state]])
|
326
|
-
end
|
327
|
-
|
328
|
-
it "creates the appearance stream according to the set value" do
|
329
|
-
@widget.marker_style(style: :check)
|
330
|
-
@generator.create_appearances
|
331
|
-
assert_operators(@widget[:AP][:N][:radio].stream,
|
332
|
-
[[:save_graphics_state],
|
333
|
-
[:set_device_gray_non_stroking_color, [1.0]],
|
334
|
-
[:append_rectangle, [0, 0, 12, 12]],
|
335
|
-
[:fill_path_non_zero],
|
336
|
-
[:append_rectangle, [0.5, 0.5, 11, 11]],
|
337
|
-
[:stroke_path], [:restore_graphics_state],
|
338
|
-
|
339
|
-
[:save_graphics_state],
|
340
|
-
[:set_font_and_size, [:F1, 10]],
|
341
|
-
[:begin_text],
|
342
|
-
[:set_text_matrix, [1, 0, 0, 1, 1.77, 2.545]],
|
343
|
-
[:show_text, ["4"]],
|
344
|
-
[:end_text],
|
345
|
-
[:restore_graphics_state]])
|
343
|
+
describe "push buttons" do
|
344
|
+
before do
|
345
|
+
@field.initialize_as_push_button
|
346
|
+
@widget = @field.create_widget(@page, Rect: [0, 0, 0, 0])
|
347
|
+
@generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
|
346
348
|
end
|
347
349
|
|
348
|
-
it "fails
|
349
|
-
@widget[:AP][:N].delete(:radio)
|
350
|
+
it "fails because it is not implemented yet" do
|
350
351
|
assert_raises(HexaPDF::Error) { @generator.create_appearances }
|
351
352
|
end
|
352
353
|
end
|
@@ -417,6 +418,37 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
417
418
|
assert_match(/test1/, form.contents)
|
418
419
|
end
|
419
420
|
|
421
|
+
describe "takes the rotation into account" do
|
422
|
+
before do
|
423
|
+
@widget[:Rect] = [0, 0, 100, 20]
|
424
|
+
end
|
425
|
+
|
426
|
+
def check_rotation(angle, width, height, matrix)
|
427
|
+
@widget[:MK] = {R: angle}
|
428
|
+
@field[:V] = 'test'
|
429
|
+
@generator.create_appearances
|
430
|
+
form = @widget[:AP][:N]
|
431
|
+
assert_equal([0, 0, width, height], form[:BBox].value)
|
432
|
+
assert_equal(matrix, form[:Matrix].value)
|
433
|
+
end
|
434
|
+
|
435
|
+
it "works for 0 degrees" do
|
436
|
+
check_rotation(-360, @widget[:Rect].width, @widget[:Rect].height, [1, 0, 0, 1, 0, 0])
|
437
|
+
end
|
438
|
+
|
439
|
+
it "works for 90 degrees" do
|
440
|
+
check_rotation(450, @widget[:Rect].height, @widget[:Rect].width, [0, 1, -1, 0, 0, 0])
|
441
|
+
end
|
442
|
+
|
443
|
+
it "works for 180 degrees" do
|
444
|
+
check_rotation(180, @widget[:Rect].width, @widget[:Rect].height, [0, -1, -1, 0, 0, 0])
|
445
|
+
end
|
446
|
+
|
447
|
+
it "works for 270 degrees" do
|
448
|
+
check_rotation(-90, @widget[:Rect].height, @widget[:Rect].width, [0, -1, 1, 0, 0, 0])
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
420
452
|
describe "font size calculation" do
|
421
453
|
before do
|
422
454
|
@widget[:Rect].height = 20
|
@@ -496,6 +528,53 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
|
|
496
528
|
end
|
497
529
|
end
|
498
530
|
|
531
|
+
describe "Javascript action AFNumber_Format" do
|
532
|
+
before do
|
533
|
+
@field.field_value = '1234567.898765'
|
534
|
+
@action = {S: :JavaScript, JS: ''}
|
535
|
+
@field[:AA] = {F: @action}
|
536
|
+
end
|
537
|
+
|
538
|
+
def assert_format(arg_string, result, range)
|
539
|
+
@action[:JS] = "AFNumber_Format(#{arg_string});"
|
540
|
+
@generator.create_appearances
|
541
|
+
assert_operators(@widget[:AP][:N].stream, result, range: range)
|
542
|
+
end
|
543
|
+
|
544
|
+
it "respects the set number of decimals" do
|
545
|
+
assert_format('0, 2, 0, 0, "E", false',
|
546
|
+
[:show_text, ["1.234.568E"]], 9)
|
547
|
+
assert_format('2, 2, 0, 0, "E", false',
|
548
|
+
[:show_text, ["1.234.567,90E"]], 9)
|
549
|
+
end
|
550
|
+
|
551
|
+
it "respects the digit separator style" do
|
552
|
+
["1,234,567.90", "1234567.90", "1.234.567,90", "1234567,90"].each_with_index do |result, style|
|
553
|
+
assert_format("2, #{style}, 0, 0, \"\", false", [:show_text, [result]], 9)
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
it "respects the negative value styling" do
|
558
|
+
@field.field_value = '-1234567.898'
|
559
|
+
["-E1234567,90", "E1234567,90", "(E1234567,90)", "(E1234567,90)"].each_with_index do |result, style|
|
560
|
+
assert_format("2, 3, #{style}, 0, \"E\", true",
|
561
|
+
[[:set_device_rgb_non_stroking_color, [style % 2, 0.0, 0.0]],
|
562
|
+
[:begin_text],
|
563
|
+
[:set_text_matrix, [1, 0, 0, 1, 2, 3.240724]],
|
564
|
+
[:show_text, [result]]], 6..9)
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
it "respects the specified currency string and position" do
|
569
|
+
assert_format('2, 3, 0, 0, " E", false', [:show_text, ["1234567,90 E"]], 9)
|
570
|
+
assert_format('2, 3, 0, 0, "E ", true', [:show_text, ["E 1234567,90"]], 9)
|
571
|
+
end
|
572
|
+
|
573
|
+
it "does nothing to the value if the Javascript method could not be determined " do
|
574
|
+
assert_format('2, 3, 0, 0, " E", false, a', [:show_text, ["1234567.898765"]], 8)
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
499
578
|
it "creates the /N appearance stream according to the set string" do
|
500
579
|
@field.field_value = 'Text'
|
501
580
|
@field.set_default_appearance_string(font_color: "red")
|
@@ -10,6 +10,10 @@ describe HexaPDF::Type::AcroForm::ButtonField do
|
|
10
10
|
@field = @doc.add({FT: :Btn, T: 'button'}, type: :XXAcroFormField, subtype: :Btn)
|
11
11
|
end
|
12
12
|
|
13
|
+
it "identifies as an :XXAcroFormField type" do
|
14
|
+
assert_equal(:XXAcroFormField, @field.type)
|
15
|
+
end
|
16
|
+
|
13
17
|
it "can be initialized as push button" do
|
14
18
|
@field.initialize_as_push_button
|
15
19
|
assert_nil(@field[:V])
|
@@ -232,6 +236,7 @@ describe HexaPDF::Type::AcroForm::ButtonField do
|
|
232
236
|
@field.create_appearances
|
233
237
|
yes = widget.appearance_dict.normal_appearance[:Yes]
|
234
238
|
off = widget.appearance_dict.normal_appearance[:Off]
|
239
|
+
widget.appearance_dict.normal_appearance[:Yes] = HexaPDF::Reference.new(yes.oid)
|
235
240
|
@field.create_appearances
|
236
241
|
assert_same(yes, widget.appearance_dict.normal_appearance[:Yes])
|
237
242
|
assert_same(off, widget.appearance_dict.normal_appearance[:Off])
|
@@ -252,7 +257,7 @@ describe HexaPDF::Type::AcroForm::ButtonField do
|
|
252
257
|
refute_same(yes, widget.appearance_dict.normal_appearance[:Yes])
|
253
258
|
end
|
254
259
|
|
255
|
-
it "fails for
|
260
|
+
it "fails for push buttons as they are not implemented yet" do
|
256
261
|
@field.flag(:push_button)
|
257
262
|
@field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
258
263
|
assert_raises(HexaPDF::Error) { @field.create_appearances }
|
@@ -10,6 +10,10 @@ describe HexaPDF::Type::AcroForm::ChoiceField do
|
|
10
10
|
@field = @doc.add({FT: :Ch, T: 'choice'}, type: :XXAcroFormField, subtype: :Ch)
|
11
11
|
end
|
12
12
|
|
13
|
+
it "identifies as an :XXAcroFormField type" do
|
14
|
+
assert_equal(:XXAcroFormField, @field.type)
|
15
|
+
end
|
16
|
+
|
13
17
|
it "can be initialized as list box" do
|
14
18
|
@field.initialize_as_list_box
|
15
19
|
assert_nil(@field[:V])
|
@@ -111,14 +111,14 @@ describe HexaPDF::Type::AcroForm::Field do
|
|
111
111
|
@field[:Subtype] = :Widget
|
112
112
|
@field[:Rect] = [0, 0, 0, 0]
|
113
113
|
widgets = @field.each_widget.to_a
|
114
|
-
assert_kind_of(HexaPDF::Type::Annotations::Widget,
|
114
|
+
assert_kind_of(HexaPDF::Type::Annotations::Widget, widgets.first)
|
115
115
|
assert_same(@field.data, widgets.first.data)
|
116
116
|
end
|
117
117
|
|
118
118
|
it "yields all widgets in the /Kids array" do
|
119
119
|
@field[:Kids] = [{Subtype: :Widget, Rect: [0, 0, 0, 0], X: 1}]
|
120
120
|
widgets = @field.each_widget.to_a
|
121
|
-
assert_kind_of(HexaPDF::Type::Annotations::Widget,
|
121
|
+
assert_kind_of(HexaPDF::Type::Annotations::Widget, widgets.first)
|
122
122
|
assert_equal(1, widgets.first[:X])
|
123
123
|
end
|
124
124
|
|
@@ -128,8 +128,8 @@ describe HexaPDF::Type::AcroForm::Field do
|
|
128
128
|
@doc.add({T: "b", Subtype: :Widget, Rect: [0, 0, 0, 0]}, type: :XXAcroFormField) <<
|
129
129
|
@doc.add({T: "a", X: 1, Subtype: :Widget, Rect: [0, 0, 0, 0]}, type: :XXAcroFormField)
|
130
130
|
|
131
|
-
widgets = @field.each_widget.to_a
|
132
|
-
assert_kind_of(HexaPDF::Type::Annotations::Widget,
|
131
|
+
widgets = @field.each_widget(direct_only: false).to_a
|
132
|
+
assert_kind_of(HexaPDF::Type::Annotations::Widget, widgets.first)
|
133
133
|
assert_equal(1, widgets.first[:X])
|
134
134
|
end
|
135
135
|
|
@@ -396,6 +396,24 @@ describe HexaPDF::Type::AcroForm::Form do
|
|
396
396
|
end
|
397
397
|
end
|
398
398
|
|
399
|
+
describe "combining fields with the same name" do
|
400
|
+
before do
|
401
|
+
@acro_form[:Fields] = [
|
402
|
+
@doc.add({T: 'e', Subtype: :Widget, Rect: [0, 0, 0, 1]}),
|
403
|
+
@doc.add({T: 'e', Subtype: :Widget, Rect: [0, 0, 0, 2]}),
|
404
|
+
@doc.add({T: 'Tx2'}),
|
405
|
+
@doc.add({T: 'e', Kids: [{Subtype: :Widget, Rect: [0, 0, 0, 3]}]}),
|
406
|
+
]
|
407
|
+
end
|
408
|
+
|
409
|
+
it "merges fields with the same name into the first one" do
|
410
|
+
assert(@acro_form.validate)
|
411
|
+
assert_equal(2, @acro_form.root_fields.size)
|
412
|
+
assert_equal([[0, 0, 0, 1], [0, 0, 0, 2], [0, 0, 0, 3]],
|
413
|
+
@acro_form.field_by_name('e').each_widget.map {|w| w[:Rect] })
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
399
417
|
describe "automatically creates the terminal fields; appearances" do
|
400
418
|
before do
|
401
419
|
@cb = @acro_form.create_check_box('test2')
|
@@ -20,6 +20,10 @@ describe HexaPDF::Type::AcroForm::SignatureField do
|
|
20
20
|
@field = @doc.wrap({}, type: :XXAcroFormField, subtype: :Sig)
|
21
21
|
end
|
22
22
|
|
23
|
+
it "identifies as an :XXAcroFormField type" do
|
24
|
+
assert_equal(:XXAcroFormField, @field.type)
|
25
|
+
end
|
26
|
+
|
23
27
|
it "sets the field value" do
|
24
28
|
@field.field_value = {Empty: :True}
|
25
29
|
assert_equal({Empty: :True}, @field[:V].value)
|
@@ -10,6 +10,10 @@ describe HexaPDF::Type::AcroForm::TextField do
|
|
10
10
|
@field = @doc.add({FT: :Tx}, type: :XXAcroFormField, subtype: :Tx)
|
11
11
|
end
|
12
12
|
|
13
|
+
it "identifies as an :XXAcroFormField type" do
|
14
|
+
assert_equal(:XXAcroFormField, @field.type)
|
15
|
+
end
|
16
|
+
|
13
17
|
it "resolves /MaxLen as inheritable field" do
|
14
18
|
assert_nil(@field[:MaxLen])
|
15
19
|
|
@@ -164,11 +168,20 @@ describe HexaPDF::Type::AcroForm::TextField do
|
|
164
168
|
assert_same(stream, @field[:AP][:N].raw_stream)
|
165
169
|
@field.field_value = 'test'
|
166
170
|
refute_same(stream, @field[:AP][:N].raw_stream)
|
171
|
+
stream = @field[:AP][:N].raw_stream
|
167
172
|
|
168
173
|
widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
|
169
174
|
assert_nil(widget[:AP])
|
170
175
|
@field.create_appearances
|
171
176
|
refute_nil(widget[:AP][:N])
|
177
|
+
|
178
|
+
@doc.clear_cache
|
179
|
+
@field.create_appearances
|
180
|
+
assert_same(stream, @field[:Kids][0][:AP][:N].raw_stream)
|
181
|
+
|
182
|
+
@doc.clear_cache
|
183
|
+
@field.field_value = 'other'
|
184
|
+
refute_same(stream, @field[:Kids][0][:AP][:N].raw_stream)
|
172
185
|
end
|
173
186
|
|
174
187
|
it "always creates a new appearance stream if force is true" do
|
@@ -85,7 +85,8 @@ module HexaPDF
|
|
85
85
|
signer_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
|
86
86
|
signer_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE'))
|
87
87
|
signer_cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature'))
|
88
|
-
signer_cert.add_extension(extension_factory.create_extension('extendedKeyUsage',
|
88
|
+
signer_cert.add_extension(extension_factory.create_extension('extendedKeyUsage',
|
89
|
+
'timeStamping', true))
|
89
90
|
signer_cert.sign(ca_key, OpenSSL::Digest.new('SHA1'))
|
90
91
|
|
91
92
|
signer_cert
|
@@ -117,6 +118,7 @@ module HexaPDF
|
|
117
118
|
end
|
118
119
|
Thread.new { @tsa_server.start }
|
119
120
|
end
|
121
|
+
|
120
122
|
end
|
121
123
|
|
122
124
|
end
|
@@ -15,6 +15,26 @@ describe HexaPDF::Type::FontTrueType do
|
|
15
15
|
BaseFont: :Something, FontDescriptor: font_descriptor})
|
16
16
|
end
|
17
17
|
|
18
|
+
describe "font_wrapper" do
|
19
|
+
it "returns the default value if the font is subset" do
|
20
|
+
@font[:BaseFont] = :'ABCDEF+Something'
|
21
|
+
assert_nil(@font.font_wrapper)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "returns the default value if the font has no embedded font file" do
|
25
|
+
assert_nil(@font.font_wrapper)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "uses a fully embedded TrueType font file" do
|
29
|
+
font_file = File.binread(File.join(TEST_DATA_DIR, "fonts", "Ubuntu-Title.ttf"))
|
30
|
+
@font[:FontDescriptor][:FontFile2] = @doc.add({}, stream: font_file)
|
31
|
+
font_wrapper = @font.font_wrapper
|
32
|
+
assert(font_wrapper)
|
33
|
+
assert_equal(font_file, font_wrapper.wrapped_font.io.string)
|
34
|
+
assert_same(font_wrapper, @font.font_wrapper)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
18
38
|
describe "validation" do
|
19
39
|
it "ignores some missing fields if the font name is one of the standard PDF fonts" do
|
20
40
|
@font[:BaseFont] = :'Arial,Bold'
|
@@ -104,7 +104,8 @@ describe HexaPDF::Type::ObjectStream do
|
|
104
104
|
assert_equal("", @obj.stream)
|
105
105
|
end
|
106
106
|
|
107
|
-
it "doesn't allow the Catalog entry to be compressed
|
107
|
+
it "doesn't allow the Catalog entry to be compressed" do
|
108
|
+
@doc.trailer.delete(:Encrypt)
|
108
109
|
@obj.add_object(HexaPDF::Dictionary.new({Type: :Catalog}, oid: 8))
|
109
110
|
@obj.write_objects(@revision)
|
110
111
|
assert_equal(0, @obj.value[:N])
|
@@ -225,29 +225,66 @@ describe HexaPDF::Type::Page do
|
|
225
225
|
@page.box(:art, [0, 0, 4, 5])
|
226
226
|
|
227
227
|
@page.rotate(90, flatten: true)
|
228
|
-
assert_equal([-
|
229
|
-
assert_equal([
|
230
|
-
assert_equal([-
|
231
|
-
assert_equal([-
|
232
|
-
assert_equal([-
|
228
|
+
assert_equal([-298, 50, -98, 200], @page.box(:media).value)
|
229
|
+
assert_equal([0, 0, 2, 1], @page.box(:crop).value)
|
230
|
+
assert_equal([-1, 0, 2, 2], @page.box(:bleed).value)
|
231
|
+
assert_equal([-2, 0, 2, 3], @page.box(:trim).value)
|
232
|
+
assert_equal([-3, 0, 2, 4], @page.box(:art).value)
|
233
233
|
end
|
234
234
|
|
235
235
|
it "works correctly for 90 degrees" do
|
236
236
|
@page.rotate(90, flatten: true)
|
237
|
-
assert_equal([
|
238
|
-
assert_equal(" q 0 1 -1 0
|
237
|
+
assert_equal([0, 0, 200, 150], @page.box(:media).value)
|
238
|
+
assert_equal(" q 0 1 -1 0 300 -50 cm Q ", @page.contents)
|
239
239
|
end
|
240
240
|
|
241
241
|
it "works correctly for 180 degrees" do
|
242
242
|
@page.rotate(180, flatten: true)
|
243
|
-
assert_equal([
|
244
|
-
assert_equal(" q -1 0 0 -1
|
243
|
+
assert_equal([0, 0, 150, 200], @page.box(:media).value)
|
244
|
+
assert_equal(" q -1 0 0 -1 200 300 cm Q ", @page.contents)
|
245
245
|
end
|
246
246
|
|
247
247
|
it "works correctly for 270 degrees" do
|
248
248
|
@page.rotate(270, flatten: true)
|
249
|
-
assert_equal([
|
250
|
-
assert_equal(" q 0 -1 1 0
|
249
|
+
assert_equal([0, 0, 200, 150], @page.box(:media).value)
|
250
|
+
assert_equal(" q 0 -1 1 0 -100 200 cm Q ", @page.contents)
|
251
|
+
end
|
252
|
+
|
253
|
+
describe "annotations" do
|
254
|
+
before do
|
255
|
+
@appearance = @doc.add({Type: :XObject, Subtype: :Form, BBox: [-10, -5, 50, 20]}, stream: "")
|
256
|
+
@annot = @doc.add({Type: :Annot, Subtype: :Widget, Rect: [100, 100, 160, 125],
|
257
|
+
QuadPoints: [0, 0, 100, 200, 300, 400, 500, 600],
|
258
|
+
AP: {N: @appearance}})
|
259
|
+
@page[:Annots] = [@annot]
|
260
|
+
end
|
261
|
+
|
262
|
+
it "rotates the /Rect entry" do
|
263
|
+
@page.rotate(90, flatten: true)
|
264
|
+
assert_equal([175, 50, 200, 110], @annot[:Rect].value)
|
265
|
+
end
|
266
|
+
|
267
|
+
it "rotates all (x,y) pairs in the /QuadPoints entry" do
|
268
|
+
@page.rotate(90, flatten: true)
|
269
|
+
assert_equal([300, -50, 100, 50, -100, 250, -300, 450],
|
270
|
+
@annot[:QuadPoints])
|
271
|
+
end
|
272
|
+
|
273
|
+
it "applies the needed matrix to the annotation's appearance stream's /Matrix entry" do
|
274
|
+
@page.rotate(90, flatten: true)
|
275
|
+
assert_equal([0, 1, -1, 0, 300, -50], @appearance[:Matrix])
|
276
|
+
|
277
|
+
@page.rotate(90, flatten: true)
|
278
|
+
assert_equal([-1, 0, 0, -1, 200, 300], @appearance[:Matrix])
|
279
|
+
end
|
280
|
+
|
281
|
+
it "modified the /R entry in the appearance characteristics dictionary of a widget annotation" do
|
282
|
+
@page.rotate(90, flatten: true)
|
283
|
+
assert_equal(90, @annot[:MK][:R])
|
284
|
+
|
285
|
+
@page.rotate(90, flatten: true)
|
286
|
+
assert_equal(180, @annot[:MK][:R])
|
287
|
+
end
|
251
288
|
end
|
252
289
|
end
|
253
290
|
|
@@ -494,25 +531,30 @@ describe HexaPDF::Type::Page do
|
|
494
531
|
@canvas = @page.canvas(type: :overlay)
|
495
532
|
end
|
496
533
|
|
497
|
-
it "does nothing if the page doesn't have any annotations" do
|
534
|
+
it "does nothing and returns the argument as array if the page doesn't have any annotations" do
|
535
|
+
annots = @page[:Annots]
|
536
|
+
|
498
537
|
@page.delete(:Annots)
|
499
538
|
result = @page.flatten_annotations
|
500
539
|
assert(result.empty?)
|
501
540
|
assert_operators(@canvas.contents, [])
|
541
|
+
|
542
|
+
result = @page.flatten_annotations(annots)
|
543
|
+
assert_kind_of(Array, result)
|
544
|
+
assert_equal([@annot1, @annot2], result)
|
545
|
+
assert_operators(@canvas.contents, [])
|
502
546
|
end
|
503
547
|
|
504
548
|
it "flattens all annotations of the page by default" do
|
505
549
|
result = @page.flatten_annotations
|
506
550
|
assert(result.empty?)
|
507
551
|
assert_operators(@canvas.contents, [[:save_graphics_state],
|
508
|
-
[:save_graphics_state],
|
509
552
|
[:concatenate_matrix, [1.0, 0, 0, 1.0, 110, 105]],
|
510
553
|
[:paint_xobject, [:XO1]],
|
511
554
|
[:restore_graphics_state],
|
512
555
|
[:save_graphics_state],
|
513
556
|
[:concatenate_matrix, [1.0, 0, 0, 1.0, 20, 15]],
|
514
557
|
[:paint_xobject, [:XO1]],
|
515
|
-
[:restore_graphics_state],
|
516
558
|
[:restore_graphics_state]])
|
517
559
|
assert(@annot1.null?)
|
518
560
|
assert(@annot2.null?)
|
@@ -542,10 +584,8 @@ describe HexaPDF::Type::Page do
|
|
542
584
|
assert(result.empty?)
|
543
585
|
assert(@annot1.null?)
|
544
586
|
assert_operators(@canvas.contents, [[:save_graphics_state],
|
545
|
-
[:save_graphics_state],
|
546
587
|
[:concatenate_matrix, [1.0, 0, 0, 1.0, 20, 15]],
|
547
588
|
[:paint_xobject, [:XO1]],
|
548
|
-
[:restore_graphics_state],
|
549
589
|
[:restore_graphics_state]])
|
550
590
|
end
|
551
591
|
|
@@ -555,10 +595,8 @@ describe HexaPDF::Type::Page do
|
|
555
595
|
assert(result.empty?)
|
556
596
|
assert(@annot1.null?)
|
557
597
|
assert_operators(@canvas.contents, [[:save_graphics_state],
|
558
|
-
[:save_graphics_state],
|
559
598
|
[:concatenate_matrix, [1.0, 0, 0, 1.0, 20, 15]],
|
560
599
|
[:paint_xobject, [:XO1]],
|
561
|
-
[:restore_graphics_state],
|
562
600
|
[:restore_graphics_state]])
|
563
601
|
end
|
564
602
|
|
@@ -568,10 +606,8 @@ describe HexaPDF::Type::Page do
|
|
568
606
|
assert_equal([@annot1], result)
|
569
607
|
refute(@annot1.empty?)
|
570
608
|
assert_operators(@canvas.contents, [[:save_graphics_state],
|
571
|
-
[:save_graphics_state],
|
572
609
|
[:concatenate_matrix, [1.0, 0, 0, 1.0, 20, 15]],
|
573
610
|
[:paint_xobject, [:XO1]],
|
574
|
-
[:restore_graphics_state],
|
575
611
|
[:restore_graphics_state]])
|
576
612
|
end
|
577
613
|
|
@@ -584,40 +620,41 @@ describe HexaPDF::Type::Page do
|
|
584
620
|
it "adjusts the position in case the form /Matrix has an offset" do
|
585
621
|
@appearance[:Matrix] = [1, 0, 0, 1, 15, 15]
|
586
622
|
@page.flatten_annotations
|
587
|
-
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 95, 90]], range:
|
623
|
+
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 95, 90]], range: 1)
|
588
624
|
end
|
589
625
|
|
590
626
|
it "adjusts the position for an appearance with a 90 degree rotation" do
|
591
627
|
@appearance[:Matrix] = [0, 1, -1, 0, 0, 0]
|
592
628
|
@annot1[:Rect] = [100, 100, 125, 160]
|
593
629
|
@page.flatten_annotations
|
594
|
-
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 120, 110]], range:
|
630
|
+
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 120, 110]], range: 1)
|
595
631
|
end
|
596
632
|
|
597
633
|
it "adjusts the position for an appearance with a -90 degree rotation" do
|
598
634
|
@appearance[:Matrix] = [0, -1, 1, 0, 0, 0]
|
599
635
|
@annot1[:Rect] = [100, 100, 125, 160]
|
600
636
|
@page.flatten_annotations
|
601
|
-
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 105, 150]], range:
|
637
|
+
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 105, 150]], range: 1)
|
602
638
|
end
|
603
639
|
|
604
640
|
it "adjusts the position for an appearance with a 180 degree rotation" do
|
605
641
|
@appearance[:Matrix] = [-1, 0, 0, -1, 0, 0]
|
606
642
|
@page.flatten_annotations
|
607
|
-
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 150, 120]], range:
|
643
|
+
assert_operators(@canvas.contents, [:concatenate_matrix, [1, 0, 0, 1, 150, 120]], range: 1)
|
608
644
|
end
|
609
645
|
|
610
|
-
it "
|
611
|
-
@appearance[:Matrix] = [
|
612
|
-
|
613
|
-
|
614
|
-
|
646
|
+
it "correctly positions and scales an appearance with a custom rotation" do
|
647
|
+
@appearance[:Matrix] = [0.707106, 0.707106, -0.707106, 0.707106, 10, 30]
|
648
|
+
@page.flatten_annotations
|
649
|
+
assert_operators(@canvas.contents,
|
650
|
+
[:concatenate_matrix, [0.998269, 0.0, 0.0, 0.415946, 111.21318, 80.60659]],
|
651
|
+
range: 1)
|
615
652
|
end
|
616
653
|
|
617
654
|
it "scales the appearance to fit into the annotations's rectangle" do
|
618
655
|
@annot1[:Rect] = [100, 100, 130, 150]
|
619
656
|
@page.flatten_annotations
|
620
|
-
assert_operators(@canvas.contents, [:concatenate_matrix, [0.5, 0, 0, 2, 110, 105]], range:
|
657
|
+
assert_operators(@canvas.contents, [:concatenate_matrix, [0.5, 0, 0, 2, 110, 105]], range: 1)
|
621
658
|
end
|
622
659
|
end
|
623
660
|
|
@@ -241,11 +241,13 @@ describe HexaPDF::Type::PageTreeNode do
|
|
241
241
|
|
242
242
|
it "moves the page to the correct location within the same parent node" do
|
243
243
|
@root.move_page(2, 4)
|
244
|
-
assert_equal([@pages[0], @pages[1], @pages[3], @pages[4], @pages[2], *@pages[5..-1]],
|
244
|
+
assert_equal([@pages[0], @pages[1], @pages[3], @pages[4], @pages[2], *@pages[5..-1]],
|
245
|
+
@root.each_page.to_a)
|
245
246
|
assert(@root.validate)
|
246
247
|
|
247
248
|
@root.move_page(4, 3)
|
248
|
-
assert_equal([@pages[0], @pages[1], @pages[3], @pages[2], @pages[4], *@pages[5..-1]],
|
249
|
+
assert_equal([@pages[0], @pages[1], @pages[3], @pages[2], @pages[4], *@pages[5..-1]],
|
250
|
+
@root.each_page.to_a)
|
249
251
|
assert(@root.validate)
|
250
252
|
end
|
251
253
|
|