hexapdf 0.12.3 → 0.14.3

Sign up to get free protection for your applications and to get access to all the features.
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