hexapdf 0.12.3 → 0.14.3

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.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +132 -0
  3. data/examples/019-acro_form.rb +41 -4
  4. data/lib/hexapdf/cli/command.rb +4 -2
  5. data/lib/hexapdf/cli/image2pdf.rb +2 -1
  6. data/lib/hexapdf/cli/info.rb +51 -2
  7. data/lib/hexapdf/cli/inspect.rb +30 -8
  8. data/lib/hexapdf/cli/merge.rb +1 -1
  9. data/lib/hexapdf/cli/split.rb +74 -14
  10. data/lib/hexapdf/configuration.rb +15 -0
  11. data/lib/hexapdf/content/graphic_object/arc.rb +3 -3
  12. data/lib/hexapdf/dictionary.rb +12 -6
  13. data/lib/hexapdf/dictionary_fields.rb +2 -10
  14. data/lib/hexapdf/document.rb +41 -16
  15. data/lib/hexapdf/document/files.rb +0 -1
  16. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  17. data/lib/hexapdf/encryption/security_handler.rb +1 -0
  18. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -0
  19. data/lib/hexapdf/font/cmap.rb +1 -4
  20. data/lib/hexapdf/font/true_type/subsetter.rb +16 -3
  21. data/lib/hexapdf/font/true_type/table/head.rb +1 -0
  22. data/lib/hexapdf/font/true_type/table/os2.rb +2 -0
  23. data/lib/hexapdf/font/true_type/table/post.rb +15 -10
  24. data/lib/hexapdf/font_loader/from_configuration.rb +2 -2
  25. data/lib/hexapdf/font_loader/from_file.rb +18 -8
  26. data/lib/hexapdf/image_loader/png.rb +3 -2
  27. data/lib/hexapdf/importer.rb +3 -2
  28. data/lib/hexapdf/layout/line.rb +1 -1
  29. data/lib/hexapdf/layout/style.rb +23 -23
  30. data/lib/hexapdf/layout/text_layouter.rb +2 -2
  31. data/lib/hexapdf/layout/text_shaper.rb +3 -2
  32. data/lib/hexapdf/object.rb +52 -25
  33. data/lib/hexapdf/parser.rb +107 -7
  34. data/lib/hexapdf/pdf_array.rb +15 -5
  35. data/lib/hexapdf/revisions.rb +29 -21
  36. data/lib/hexapdf/serializer.rb +37 -10
  37. data/lib/hexapdf/task/optimize.rb +6 -4
  38. data/lib/hexapdf/tokenizer.rb +22 -0
  39. data/lib/hexapdf/type/acro_form/appearance_generator.rb +130 -27
  40. data/lib/hexapdf/type/acro_form/button_field.rb +5 -2
  41. data/lib/hexapdf/type/acro_form/choice_field.rb +68 -14
  42. data/lib/hexapdf/type/acro_form/field.rb +35 -5
  43. data/lib/hexapdf/type/acro_form/form.rb +139 -14
  44. data/lib/hexapdf/type/acro_form/text_field.rb +70 -4
  45. data/lib/hexapdf/type/actions/uri.rb +3 -2
  46. data/lib/hexapdf/type/annotations/widget.rb +3 -4
  47. data/lib/hexapdf/type/catalog.rb +2 -2
  48. data/lib/hexapdf/type/cid_font.rb +1 -1
  49. data/lib/hexapdf/type/file_specification.rb +1 -1
  50. data/lib/hexapdf/type/font.rb +1 -1
  51. data/lib/hexapdf/type/font_simple.rb +4 -2
  52. data/lib/hexapdf/type/font_true_type.rb +6 -2
  53. data/lib/hexapdf/type/font_type0.rb +4 -4
  54. data/lib/hexapdf/type/form.rb +6 -2
  55. data/lib/hexapdf/type/image.rb +2 -2
  56. data/lib/hexapdf/type/page.rb +21 -12
  57. data/lib/hexapdf/type/page_tree_node.rb +29 -5
  58. data/lib/hexapdf/type/resources.rb +5 -0
  59. data/lib/hexapdf/type/trailer.rb +2 -3
  60. data/lib/hexapdf/utils/object_hash.rb +0 -1
  61. data/lib/hexapdf/utils/sorted_tree_node.rb +18 -15
  62. data/lib/hexapdf/version.rb +1 -1
  63. data/test/hexapdf/common_tokenizer_tests.rb +2 -2
  64. data/test/hexapdf/content/graphic_object/test_arc.rb +4 -4
  65. data/test/hexapdf/content/test_canvas.rb +3 -3
  66. data/test/hexapdf/content/test_color_space.rb +1 -1
  67. data/test/hexapdf/encryption/test_aes.rb +4 -4
  68. data/test/hexapdf/encryption/test_standard_security_handler.rb +11 -11
  69. data/test/hexapdf/filter/test_ascii85_decode.rb +1 -1
  70. data/test/hexapdf/filter/test_ascii_hex_decode.rb +1 -1
  71. data/test/hexapdf/font/true_type/table/test_post.rb +1 -1
  72. data/test/hexapdf/font/true_type/test_subsetter.rb +10 -0
  73. data/test/hexapdf/font_loader/test_from_configuration.rb +7 -3
  74. data/test/hexapdf/font_loader/test_from_file.rb +7 -0
  75. data/test/hexapdf/layout/test_text_layouter.rb +12 -5
  76. data/test/hexapdf/test_configuration.rb +2 -2
  77. data/test/hexapdf/test_dictionary.rb +8 -1
  78. data/test/hexapdf/test_dictionary_fields.rb +9 -2
  79. data/test/hexapdf/test_document.rb +18 -10
  80. data/test/hexapdf/test_object.rb +71 -26
  81. data/test/hexapdf/test_parser.rb +205 -51
  82. data/test/hexapdf/test_pdf_array.rb +8 -1
  83. data/test/hexapdf/test_revisions.rb +35 -0
  84. data/test/hexapdf/test_serializer.rb +7 -0
  85. data/test/hexapdf/test_tokenizer.rb +28 -0
  86. data/test/hexapdf/test_writer.rb +2 -2
  87. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +288 -35
  88. data/test/hexapdf/type/acro_form/test_button_field.rb +15 -0
  89. data/test/hexapdf/type/acro_form/test_choice_field.rb +92 -9
  90. data/test/hexapdf/type/acro_form/test_field.rb +39 -0
  91. data/test/hexapdf/type/acro_form/test_form.rb +87 -15
  92. data/test/hexapdf/type/acro_form/test_text_field.rb +77 -1
  93. data/test/hexapdf/type/test_font_simple.rb +2 -1
  94. data/test/hexapdf/type/test_font_true_type.rb +6 -0
  95. data/test/hexapdf/type/test_form.rb +8 -1
  96. data/test/hexapdf/type/test_page.rb +8 -1
  97. data/test/hexapdf/type/test_page_tree_node.rb +42 -0
  98. data/test/hexapdf/type/test_resources.rb +6 -0
  99. data/test/hexapdf/utils/test_bit_field.rb +2 -0
  100. data/test/hexapdf/utils/test_object_hash.rb +5 -0
  101. data/test/hexapdf/utils/test_sorted_tree_node.rb +10 -9
  102. data/test/test_helper.rb +2 -0
  103. metadata +6 -12
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
40
40
  219
41
41
  %%EOF
42
42
  3 0 obj
43
- <</Producer(HexaPDF version 0.12.3)>>
43
+ <</Producer(HexaPDF version 0.14.3)>>
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.12.3)>>
75
+ <</Producer(HexaPDF version 0.14.3)>>
76
76
  endobj
77
77
  2 0 obj
78
78
  <</Length 10>>stream
@@ -25,14 +25,6 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
25
25
  assert_raises(HexaPDF::Error) { @generator.create_appearances }
26
26
  end
27
27
 
28
- it "fails for unsupported choice fields" do
29
- @field = @doc.wrap(@field, type: :XXAcroFormField, subtype: :Ch)
30
- @field[:FT] = :Ch
31
- @field.initialize_as_list_box
32
- @generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
33
- assert_raises(HexaPDF::Error) { @generator.create_appearances }
34
- end
35
-
36
28
  it "fails for unsupported field types" do
37
29
  @field[:FT] = :Unknown
38
30
  assert_raises(HexaPDF::Error) { @generator.create_appearances }
@@ -121,6 +113,24 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
121
113
  [:curve_to, [8.236068, 7.249543, 9.0, 8.572712, 9.0, 10.0]],
122
114
  [:stroke_path], [:restore_graphics_state]])
123
115
  end
116
+
117
+ it "handles the special case of a comb field" do
118
+ @field = @doc.add({FT: :Tx, MaxLen: 4}, type: :XXAcroFormField, subtype: :Tx)
119
+ @field.initialize_as_comb_text_field
120
+ @widget = @field.create_widget(@page, Rect: [0, 0, 10, 20])
121
+ @xform = @doc.add({Type: :XObject, Subtype: :Form, BBox: @widget[:Rect]})
122
+ @generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
123
+ @widget.border_style(width: 2)
124
+ execute
125
+ assert_operators(@xform.stream,
126
+ [[:save_graphics_state],
127
+ [:set_line_width, [2]],
128
+ [:append_rectangle, [1, 1, 8, 18]],
129
+ [:move_to, [2.5, 2]], [:line_to, [2.5, 20.0]],
130
+ [:move_to, [5.0, 2]], [:line_to, [5.0, 20.0]],
131
+ [:move_to, [7.5, 2]], [:line_to, [7.5, 20.0]],
132
+ [:stroke_path], [:restore_graphics_state]])
133
+ end
124
134
  end
125
135
 
126
136
  describe "draw_marker" do
@@ -342,7 +352,7 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
342
352
  end
343
353
  end
344
354
 
345
- describe "text field" do
355
+ describe "text fields" do
346
356
  before do
347
357
  @form.set_default_appearance_string
348
358
  @field = @doc.add({FT: :Tx}, type: :XXAcroFormField, subtype: :Tx)
@@ -392,35 +402,60 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
392
402
  assert_equal(@doc.acro_form.default_resources[:Font][:F1], form[:Resources][:Font][:F1])
393
403
  end
394
404
 
395
- describe "single line text fields" do
396
- describe "font size calculation" do
397
- before do
398
- @widget[:Rect].height = 20
399
- @widget[:Rect].width = 100
400
- @field.field_value = ''
401
- end
405
+ it "re-uses the existing form XObject" do
406
+ @field[:V] = 'test'
407
+ @generator.create_appearances
408
+ form = @widget[:AP][:N]
409
+ form[:key] = :value
410
+ form.delete(:Subtype)
411
+ @widget[:AP][:N] = @doc.wrap(form, type: HexaPDF::Dictionary)
402
412
 
403
- it "uses the non-zero font size" do
404
- @field.set_default_appearance_string(font_size: 10)
405
- @generator.create_appearances
406
- assert_operators(@widget[:AP][:N].stream,
407
- [:set_font_and_size, [:F1, 10]],
408
- range: 5)
409
- end
413
+ @field[:V] = 'test1'
414
+ @generator.create_appearances
415
+ assert_equal(form, @widget[:AP][:N])
416
+ refute(form.key?(:key))
417
+ assert_match(/test1/, form.contents)
418
+ end
410
419
 
411
- it "calculates the font size based on the rectangle height and border width" do
412
- @generator.create_appearances
413
- assert_operators(@widget[:AP][:N].stream,
414
- [:set_font_and_size, [:F1, 12.923875]],
415
- range: 5)
416
- @widget.border_style(width: 2, color: :transparent)
417
- @generator.create_appearances
418
- assert_operators(@widget[:AP][:N].stream,
419
- [:set_font_and_size, [:F1, 11.487889]],
420
- range: 5)
421
- end
420
+ describe "font size calculation" do
421
+ before do
422
+ @widget[:Rect].height = 20
423
+ @widget[:Rect].width = 100
424
+ @field.field_value = ''
425
+ end
426
+
427
+ it "uses the non-zero font size" do
428
+ @field.set_default_appearance_string(font_size: 10)
429
+ @generator.create_appearances
430
+ assert_operators(@widget[:AP][:N].stream,
431
+ [:set_font_and_size, [:F1, 10]],
432
+ range: 5)
422
433
  end
423
434
 
435
+ it "calculates the font size based on the rectangle height and border width" do
436
+ @generator.create_appearances
437
+ assert_operators(@widget[:AP][:N].stream,
438
+ [:set_font_and_size, [:F1, 12.923875]],
439
+ range: 5)
440
+ @widget.border_style(width: 2, color: :transparent)
441
+ @generator.create_appearances
442
+ assert_operators(@widget[:AP][:N].stream,
443
+ [:set_font_and_size, [:F1, 11.487889]],
444
+ range: 5)
445
+ end
446
+
447
+ it " in case of mulitline auto-sizing" do
448
+ @field.initialize_as_multiline_text_field
449
+ @field[:V] = 'a'
450
+ @field.set_default_appearance_string(font_size: 0)
451
+ @generator.create_appearances
452
+ assert_operators(@widget[:AP][:N].stream,
453
+ [:set_font_and_size, [:F1, 12]],
454
+ range: 6)
455
+ end
456
+ end
457
+
458
+ describe "single line text fields" do
424
459
  describe "quadding e.g. text alignment" do
425
460
  before do
426
461
  @field.field_value = 'Test'
@@ -478,7 +513,166 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
478
513
  [:restore_graphics_state],
479
514
  [:end_marked_content]])
480
515
  end
516
+ end
517
+
518
+ describe "multiline text fields" do
519
+ before do
520
+ @field.set_default_appearance_string(font_size: 10)
521
+ @field.initialize_as_multiline_text_field
522
+ @widget[:Rect].height = 30
523
+ @widget[:Rect].width = 100
524
+ end
525
+
526
+ describe "quadding e.g. text alignment" do
527
+ before do
528
+ @field[:V] = "Test\nValue"
529
+ end
530
+
531
+ it "works for left aligned text" do
532
+ @field.text_alignment(:left)
533
+ @generator.create_appearances
534
+ assert_operators(@widget[:AP][:N].stream,
535
+ [:set_text_matrix, [1, 0, 0, 1, 2, 16.195]],
536
+ range: 9)
537
+ end
538
+
539
+ it "works for right aligned text" do
540
+ @field.text_alignment(:right)
541
+ @generator.create_appearances
542
+ assert_operators(@widget[:AP][:N].stream,
543
+ [:set_text_matrix, [1, 0, 0, 1, 78.55, 16.195]],
544
+ range: 9)
545
+ end
546
+
547
+ it "works for center aligned text" do
548
+ @field.text_alignment(:center)
549
+ @generator.create_appearances
550
+ assert_operators(@widget[:AP][:N].stream,
551
+ [:set_text_matrix, [1, 0, 0, 1, 40.275, 16.195]],
552
+ range: 9)
553
+ end
554
+ end
555
+
556
+ it "creates the /N appearance stream according to the set string" do
557
+ @field.field_value = "Test\nValue"
558
+ @generator.create_appearances
559
+ assert_operators(@widget[:AP][:N].stream,
560
+ [[:begin_marked_content, [:Tx]],
561
+ [:save_graphics_state],
562
+ [:append_rectangle, [1, 1, 98, 28]],
563
+ [:clip_path_non_zero],
564
+ [:end_path],
565
+ [:save_graphics_state],
566
+ [:set_leading, [11.5625]],
567
+ [:set_font_and_size, [:F1, 10]],
568
+ [:begin_text],
569
+ [:set_text_matrix, [1, 0, 0, 1, 2, 16.195]],
570
+ [:show_text, ['Test']],
571
+ [:move_text_next_line],
572
+ [:show_text, ['Value']],
573
+ [:end_text],
574
+ [:restore_graphics_state],
575
+ [:restore_graphics_state],
576
+ [:end_marked_content]])
577
+
578
+ @field.field_value = "Test\nTest\nTest"
579
+ @field.set_default_appearance_string(font_size: 0)
580
+ @generator.create_appearances
581
+ assert_operators(@widget[:AP][:N].stream,
582
+ [[:begin_marked_content, [:Tx]],
583
+ [:save_graphics_state],
584
+ [:append_rectangle, [1, 1, 98, 28]],
585
+ [:clip_path_non_zero],
586
+ [:end_path],
587
+ [:save_graphics_state],
588
+ [:set_leading, [9.25]],
589
+ [:set_font_and_size, [:F1, 8]],
590
+ [:begin_text],
591
+ [:set_text_matrix, [1, 0, 0, 1, 2, 18.556]],
592
+ [:show_text, ['Test']],
593
+ [:move_text_next_line],
594
+ [:show_text, ['Test']],
595
+ [:move_text_next_line],
596
+ [:show_text, ['Test']],
597
+ [:end_text],
598
+ [:restore_graphics_state],
599
+ [:restore_graphics_state],
600
+ [:end_marked_content]],
601
+ )
602
+ end
603
+ end
604
+
605
+ describe "comb text fields" do
606
+ before do
607
+ @field.set_default_appearance_string(font_size: 10)
608
+ @field.initialize_as_comb_text_field
609
+ @field[:MaxLen] = 10
610
+ @widget[:Rect].height = 20
611
+ @widget[:Rect].width = 100
612
+ end
481
613
 
614
+ describe "quadding e.g. text alignment" do
615
+ before do
616
+ @field[:V] = 'Test'
617
+ end
618
+
619
+ it "works for left aligned text" do
620
+ @field.text_alignment(:left)
621
+ @generator.create_appearances
622
+ assert_operators(@widget[:AP][:N].stream,
623
+ [:set_text_matrix, [1, 0, 0, 1, 2.945, 6.41]],
624
+ range: 7)
625
+ end
626
+
627
+ it "works for right aligned text" do
628
+ @field.text_alignment(:right)
629
+ @generator.create_appearances
630
+ assert_operators(@widget[:AP][:N].stream,
631
+ [:set_text_matrix, [1, 0, 0, 1, 62.945, 6.41]],
632
+ range: 7)
633
+ end
634
+
635
+ it "works for center aligned text" do
636
+ @field.text_alignment(:center)
637
+ @generator.create_appearances
638
+ assert_operators(@widget[:AP][:N].stream,
639
+ [:set_text_matrix, [1, 0, 0, 1, 32.945, 6.41]],
640
+ range: 7)
641
+ end
642
+
643
+ it "handles centering like Adobe, e.g. shift left, when text cannot be completely centered" do
644
+ @field.field_value = 'Hello'
645
+ @field.text_alignment(:center)
646
+ @generator.create_appearances
647
+ assert_operators(@widget[:AP][:N].stream,
648
+ [:set_text_matrix, [1, 0, 0, 1, 22.39, 6.41]],
649
+ range: 7)
650
+ end
651
+ end
652
+
653
+ it "creates the /N appearance stream according to the set string" do
654
+ @field.field_value = 'Text'
655
+ @generator.create_appearances
656
+ assert_operators(@widget[:AP][:N].stream,
657
+ [[:begin_marked_content, [:Tx]],
658
+ [:save_graphics_state],
659
+ [:append_rectangle, [1, 1, 98, 18]],
660
+ [:clip_path_non_zero],
661
+ [:end_path],
662
+ [:set_font_and_size, [:F1, 10]],
663
+ [:begin_text],
664
+ [:set_text_matrix, [1, 0, 0, 1, 2.945, 6.41]],
665
+ [:show_text_with_positioning, [['T', -416.5, 'e', -472, 'x', -611, 't']]],
666
+ [:end_text],
667
+ [:restore_graphics_state],
668
+ [:end_marked_content]])
669
+ end
670
+
671
+ it "fails if the /MaxLen key is not set" do
672
+ @field.delete(:MaxLen)
673
+ @field[:V] = 't'
674
+ assert_raises(HexaPDF::Error) { @generator.create_appearances }
675
+ end
482
676
  end
483
677
 
484
678
  describe "choice fields" do
@@ -486,16 +680,75 @@ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
486
680
  @form.set_default_appearance_string
487
681
  field = @doc.add({FT: :Ch}, type: :XXAcroFormField, subtype: :Ch)
488
682
  field.initialize_as_combo_box
683
+ field.flag(:edit)
684
+ field.field_value = 'Test'
489
685
  widget = field.create_widget(@page, Rect: [0, 0, 0, 0])
490
686
  generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(widget)
491
687
  generator.create_appearances
492
688
  assert_kind_of(HexaPDF::Type::Form, widget[:AP][:N])
493
689
  end
690
+
691
+ describe "list boxes" do
692
+ before do
693
+ @field = @doc.add({FT: :Ch}, type: :XXAcroFormField, subtype: :Ch)
694
+ @field.initialize_as_list_box
695
+ @field.flag(:multi_select)
696
+ @field.option_items = ['a', 'b', 'c']
697
+ @widget = @field.create_widget(@page, Rect: [0, 0, 90, 36])
698
+ @generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
699
+ end
700
+
701
+ it "uses a fixed font size for list box items if auto-sizing is used" do
702
+ @field.set_default_appearance_string(font_size: 0)
703
+ @generator.create_appearances
704
+ assert_operators(@widget[:AP][:N].stream,
705
+ [:set_font_and_size, [:F1, 12]],
706
+ range: 8)
707
+ end
708
+
709
+ it "uses the set values instead of the ones from /I if in conflict" do
710
+ @field[:I] = [0, 1]
711
+ @field[:V] = ['b']
712
+ @generator.create_appearances
713
+ assert_operators(@widget[:AP][:N].stream,
714
+ [[:set_device_rgb_non_stroking_color, [0.6, 0.756863, 0.854902]],
715
+ [:append_rectangle, [1, 7.25, 88, 13.875]],
716
+ [:fill_path_non_zero]],
717
+ range: 5..7)
718
+ end
719
+
720
+ it "creates the /N appearance stream" do
721
+ @field[:I] = [1, 2]
722
+ @field[:V] = ['b', 'c']
723
+ @generator.create_appearances
724
+ assert_operators(@widget[:AP][:N].stream,
725
+ [[:begin_marked_content, [:Tx]],
726
+ [:save_graphics_state],
727
+ [:append_rectangle, [1, 1, 88, 34]],
728
+ [:clip_path_non_zero], [:end_path],
729
+ [:set_device_rgb_non_stroking_color, [0.6, 0.756863, 0.854902]],
730
+ [:append_rectangle, [1, 7.25, 88, 13.875]],
731
+ [:append_rectangle, [1, -6.625, 88, 13.875]],
732
+ [:fill_path_non_zero],
733
+ [:save_graphics_state],
734
+ [:set_leading, [13.875]],
735
+ [:set_font_and_size, [:F1, 12]],
736
+ [:set_device_gray_non_stroking_color, [0.0]],
737
+ [:begin_text],
738
+ [:set_text_matrix, [1, 0, 0, 1, 2, 23.609]],
739
+ [:show_text, ["a"]],
740
+ [:move_text_next_line],
741
+ [:show_text, ["b"]],
742
+ [:end_text],
743
+ [:restore_graphics_state], [:restore_graphics_state],
744
+ [:end_marked_content]])
745
+ end
746
+ end
494
747
  end
495
748
 
496
749
  describe "font resolution in case the referenced font is not usable" do
497
750
  before do
498
- @doc.config['acro_form.fallback_font'] = ['Times', variant: :none]
751
+ @doc.config['acro_form.fallback_font'] = ['Times', {variant: :none}]
499
752
  @field[:V] = 'Test'
500
753
  end
501
754
 
@@ -233,6 +233,14 @@ describe HexaPDF::Type::AcroForm::ButtonField do
233
233
  refute_same(off, widget.appearance.normal_appearance[:Off])
234
234
  end
235
235
 
236
+ it "always generates appearances if force is true" do
237
+ widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
238
+ @field.create_appearances
239
+ yes = widget.appearance.normal_appearance[:Yes]
240
+ @field.create_appearances(force: true)
241
+ refute_same(yes, widget.appearance.normal_appearance[:Yes])
242
+ end
243
+
236
244
  it "fails for unsupported button types" do
237
245
  @field.flag(:push_button)
238
246
  @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
@@ -263,6 +271,13 @@ describe HexaPDF::Type::AcroForm::ButtonField do
263
271
  @field.update_widgets
264
272
  assert_equal(:Yes, widget[:AS])
265
273
  end
274
+
275
+ it "creates the appearances if necessary" do
276
+ widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
277
+ assert_nil(widget[:AP][:N][:Yes])
278
+ @field.update_widgets
279
+ assert(widget[:AP][:N][:Yes])
280
+ end
266
281
  end
267
282
 
268
283
  describe "validation" do
@@ -36,23 +36,34 @@ describe HexaPDF::Type::AcroForm::ChoiceField do
36
36
 
37
37
  describe "field_value=" do
38
38
  before do
39
- @field.option_items = ["test", "other"]
39
+ @field.option_items = ["test", "something", "other", "neu"]
40
+ end
41
+
42
+ it "updates the widgets to reflect the changed value" do
43
+ @field.initialize_as_combo_box
44
+ widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
45
+ @field.set_default_appearance_string
46
+ @field.field_value = 'test'
47
+ assert(widget[:AP][:N])
40
48
  end
41
49
 
42
50
  describe "combo_box" do
43
51
  before do
44
52
  @field.initialize_as_combo_box
53
+ @field[:I] = 2
45
54
  end
46
55
 
47
56
  it "can set the value for an uneditable combo box" do
48
57
  @field.field_value = 'test'
49
58
  assert_equal("test", @field[:V])
59
+ assert_nil(@field[:I])
50
60
  end
51
61
 
52
62
  it "can set the value for an editable combo box" do
53
63
  @field.flag(:edit)
54
64
  @field.field_value = 'another'
55
65
  assert_equal("another", @field[:V])
66
+ assert_nil(@field[:I])
56
67
  end
57
68
 
58
69
  it "fails if mulitple values are provided for a combo box" do
@@ -76,8 +87,18 @@ describe HexaPDF::Type::AcroForm::ChoiceField do
76
87
 
77
88
  it "can set a multiple values if the list box is a multi-select" do
78
89
  @field.flag(:multi_select)
79
- @field.field_value = ['test', 'other']
80
- assert_equal(['test', 'other'], @field[:V].value)
90
+ @field.field_value = ['other', 'test']
91
+ assert_equal(['other', 'test'], @field[:V].value)
92
+ assert_equal([0, 2], @field[:I].value)
93
+ end
94
+
95
+ it "can read and set the top index" do
96
+ assert_raises(ArgumentError) { @field.list_box_top_index = 4 }
97
+ @field.delete(:Opt)
98
+ assert_raises(ArgumentError) { @field.list_box_top_index = 0 }
99
+ @field.option_items = [1, 2, 3, 4]
100
+ @field.list_box_top_index = 2
101
+ assert_equal(2, @field.list_box_top_index)
81
102
  end
82
103
 
83
104
  it "fails if mulitple values are provided but the list box is not a multi-select" do
@@ -97,10 +118,29 @@ describe HexaPDF::Type::AcroForm::ChoiceField do
97
118
  assert_raises(HexaPDF::Error) { @field.default_field_value = 'unknown' }
98
119
  end
99
120
 
100
- it "sets and returns the array with the option items" do
101
- assert_equal([], @field.option_items)
102
- @field.option_items = ["H\xe4llo".b, "\xFE\xFF".b << "Töne".encode('UTF-16BE').b]
103
- assert_equal(["Hällo", "Töne"], @field.option_items)
121
+ describe "option items" do
122
+ before do
123
+ @items = [["a", "Zx"], "\xFE\xFF".b << "Töne".encode('UTF-16BE').b, "H\xe4llo".b,]
124
+ end
125
+
126
+ it "sets the option items" do
127
+ @field.option_items = @items
128
+ assert_equal(@items, @field[:Opt].value)
129
+
130
+ @field.flag(:sort)
131
+ @field.option_items = @items
132
+ assert_equal(@items.values_at(2, 1, 0), @field[:Opt].value)
133
+ end
134
+
135
+ it "can retrieve the option items" do
136
+ @field[:Opt] = @items
137
+ assert_equal(["Zx", "Töne", "Hällo"], @field.option_items)
138
+ end
139
+
140
+ it "can retrieve the export values" do
141
+ @field[:Opt] = @items
142
+ assert_equal(["a", "Töne", "Hällo"], @field.export_values)
143
+ end
104
144
  end
105
145
 
106
146
  it "returns the correct concrete field type" do
@@ -121,8 +161,51 @@ describe HexaPDF::Type::AcroForm::ChoiceField do
121
161
  assert(@field[:AP][:N])
122
162
  end
123
163
 
124
- it "fails for list boxes" do
125
- assert_raises(HexaPDF::Error) { @field.create_appearances }
164
+ it "works for list box fields" do
165
+ @field.initialize_as_list_box
166
+ @field.set_default_appearance_string
167
+ @field.create_appearances
168
+ assert(@field[:AP][:N])
169
+ end
170
+
171
+ it "only creates a new appearance if the involved dictionary values have changed per widget" do
172
+ @field.initialize_as_list_box
173
+ @field.set_default_appearance_string
174
+ @field.create_appearances
175
+ appearance_stream = @field[:AP][:N].raw_stream
176
+
177
+ @field.create_appearances
178
+ assert_same(appearance_stream, @field[:AP][:N].raw_stream)
179
+
180
+ do_check = lambda do
181
+ @field.create_appearances
182
+ refute_same(appearance_stream, @field[:AP][:N].raw_stream)
183
+ appearance_stream = @field[:AP][:N].raw_stream
184
+ end
185
+
186
+ @field.option_items = ['a', 'b', 'c']
187
+ do_check.call
188
+
189
+ @field.list_box_top_index = 2
190
+ do_check.call
191
+
192
+ @field.field_value = 'b'
193
+ do_check.call
194
+
195
+ widget = @field.create_widget(@doc.pages.add, Rect: [0, 0, 0, 0])
196
+ assert_nil(widget[:AP])
197
+ @field.create_appearances
198
+ refute_nil(widget[:AP][:N])
199
+ end
200
+
201
+ it "force the creation of appearance streams when force: true" do
202
+ @field.initialize_as_list_box
203
+ @field.set_default_appearance_string
204
+ @field.create_appearances
205
+ appearance_stream = @field[:AP][:N].raw_stream
206
+
207
+ @field.create_appearances(force: true)
208
+ refute_same(appearance_stream, @field[:AP][:N].raw_stream)
126
209
  end
127
210
  end
128
211