hexapdf 0.14.4 → 0.15.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +68 -0
- data/lib/hexapdf/cli/form.rb +30 -8
- data/lib/hexapdf/configuration.rb +18 -3
- data/lib/hexapdf/content/canvas.rb +1 -0
- data/lib/hexapdf/encryption/standard_security_handler.rb +16 -0
- 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 +18 -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/utils/graphics_helpers.rb +4 -4
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/test_canvas.rb +21 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +27 -0
- 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 +24 -10
- data/test/hexapdf/type/test_font_descriptor.rb +7 -0
- data/test/hexapdf/type/test_page.rb +187 -49
- data/test/hexapdf/utils/test_graphics_helpers.rb +8 -0
- metadata +4 -2
@@ -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
|
@@ -15,9 +15,17 @@ describe HexaPDF::Utils::GraphicsHelpers do
|
|
15
15
|
assert_equal([10, 12], calculate_dimensions(5, 6, rwidth: 10))
|
16
16
|
end
|
17
17
|
|
18
|
+
it "returns the requested width and the given height if width is zero" do
|
19
|
+
assert_equal([10, 6], calculate_dimensions(0, 6, rwidth: 10))
|
20
|
+
end
|
21
|
+
|
18
22
|
it "returns the requested height and an adjusted width" do
|
19
23
|
assert_equal([10, 12], calculate_dimensions(5, 6, rheight: 12))
|
20
24
|
end
|
25
|
+
|
26
|
+
it "returns the requested height and the given width if height is zero" do
|
27
|
+
assert_equal([5, 12], calculate_dimensions(5, 0, rheight: 12))
|
28
|
+
end
|
21
29
|
end
|
22
30
|
|
23
31
|
describe "point_on_line" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hexapdf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.15.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Leitner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-05-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cmdparse
|
@@ -359,6 +359,7 @@ files:
|
|
359
359
|
- lib/hexapdf/type/acro_form/choice_field.rb
|
360
360
|
- lib/hexapdf/type/acro_form/field.rb
|
361
361
|
- lib/hexapdf/type/acro_form/form.rb
|
362
|
+
- lib/hexapdf/type/acro_form/signature_field.rb
|
362
363
|
- lib/hexapdf/type/acro_form/text_field.rb
|
363
364
|
- lib/hexapdf/type/acro_form/variable_text_field.rb
|
364
365
|
- lib/hexapdf/type/action.rb
|
@@ -595,6 +596,7 @@ files:
|
|
595
596
|
- test/hexapdf/type/acro_form/test_choice_field.rb
|
596
597
|
- test/hexapdf/type/acro_form/test_field.rb
|
597
598
|
- test/hexapdf/type/acro_form/test_form.rb
|
599
|
+
- test/hexapdf/type/acro_form/test_signature_field.rb
|
598
600
|
- test/hexapdf/type/acro_form/test_text_field.rb
|
599
601
|
- test/hexapdf/type/acro_form/test_variable_text_field.rb
|
600
602
|
- test/hexapdf/type/actions/test_launch.rb
|