hexapdf 0.17.1 → 0.17.2

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 (255) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -0
  3. data/LICENSE +29 -0
  4. data/README.md +129 -0
  5. data/Rakefile +109 -0
  6. data/agpl-3.0.txt +661 -0
  7. data/examples/001-hello_world.rb +16 -0
  8. data/examples/002-graphics.rb +275 -0
  9. data/examples/003-arcs.rb +50 -0
  10. data/examples/004-optimizing.rb +23 -0
  11. data/examples/005-merging.rb +27 -0
  12. data/examples/006-standard_pdf_fonts.rb +73 -0
  13. data/examples/007-truetype.rb +42 -0
  14. data/examples/008-show_char_bboxes.rb +55 -0
  15. data/examples/009-text_layouter_alignment.rb +47 -0
  16. data/examples/010-text_layouter_inline_boxes.rb +64 -0
  17. data/examples/011-text_layouter_line_wrapping.rb +57 -0
  18. data/examples/012-text_layouter_styling.rb +122 -0
  19. data/examples/013-text_layouter_shapes.rb +176 -0
  20. data/examples/014-text_in_polygon.rb +60 -0
  21. data/examples/015-boxes.rb +76 -0
  22. data/examples/016-frame_automatic_box_placement.rb +90 -0
  23. data/examples/017-frame_text_flow.rb +60 -0
  24. data/examples/018-composer.rb +44 -0
  25. data/examples/019-acro_form.rb +88 -0
  26. data/examples/emoji-smile.png +0 -0
  27. data/examples/emoji-wink.png +0 -0
  28. data/examples/machupicchu.jpg +0 -0
  29. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -0
  30. data/lib/hexapdf/content/graphic_object/geom2d.rb +13 -0
  31. data/lib/hexapdf/version.rb +1 -1
  32. data/test/data/aes-test-vectors/CBCGFSbox-128-decrypt.data.gz +0 -0
  33. data/test/data/aes-test-vectors/CBCGFSbox-128-encrypt.data.gz +0 -0
  34. data/test/data/aes-test-vectors/CBCGFSbox-192-decrypt.data.gz +0 -0
  35. data/test/data/aes-test-vectors/CBCGFSbox-192-encrypt.data.gz +0 -0
  36. data/test/data/aes-test-vectors/CBCGFSbox-256-decrypt.data.gz +0 -0
  37. data/test/data/aes-test-vectors/CBCGFSbox-256-encrypt.data.gz +0 -0
  38. data/test/data/aes-test-vectors/CBCKeySbox-128-decrypt.data.gz +0 -0
  39. data/test/data/aes-test-vectors/CBCKeySbox-128-encrypt.data.gz +0 -0
  40. data/test/data/aes-test-vectors/CBCKeySbox-192-decrypt.data.gz +0 -0
  41. data/test/data/aes-test-vectors/CBCKeySbox-192-encrypt.data.gz +0 -0
  42. data/test/data/aes-test-vectors/CBCKeySbox-256-decrypt.data.gz +0 -0
  43. data/test/data/aes-test-vectors/CBCKeySbox-256-encrypt.data.gz +0 -0
  44. data/test/data/aes-test-vectors/CBCVarKey-128-decrypt.data.gz +0 -0
  45. data/test/data/aes-test-vectors/CBCVarKey-128-encrypt.data.gz +0 -0
  46. data/test/data/aes-test-vectors/CBCVarKey-192-decrypt.data.gz +0 -0
  47. data/test/data/aes-test-vectors/CBCVarKey-192-encrypt.data.gz +0 -0
  48. data/test/data/aes-test-vectors/CBCVarKey-256-decrypt.data.gz +0 -0
  49. data/test/data/aes-test-vectors/CBCVarKey-256-encrypt.data.gz +0 -0
  50. data/test/data/aes-test-vectors/CBCVarTxt-128-decrypt.data.gz +0 -0
  51. data/test/data/aes-test-vectors/CBCVarTxt-128-encrypt.data.gz +0 -0
  52. data/test/data/aes-test-vectors/CBCVarTxt-192-decrypt.data.gz +0 -0
  53. data/test/data/aes-test-vectors/CBCVarTxt-192-encrypt.data.gz +0 -0
  54. data/test/data/aes-test-vectors/CBCVarTxt-256-decrypt.data.gz +0 -0
  55. data/test/data/aes-test-vectors/CBCVarTxt-256-encrypt.data.gz +0 -0
  56. data/test/data/fonts/Ubuntu-Title.ttf +0 -0
  57. data/test/data/images/cmyk.jpg +0 -0
  58. data/test/data/images/fillbytes.jpg +0 -0
  59. data/test/data/images/gray.jpg +0 -0
  60. data/test/data/images/greyscale-1bit.png +0 -0
  61. data/test/data/images/greyscale-2bit.png +0 -0
  62. data/test/data/images/greyscale-4bit.png +0 -0
  63. data/test/data/images/greyscale-8bit.png +0 -0
  64. data/test/data/images/greyscale-alpha-8bit.png +0 -0
  65. data/test/data/images/greyscale-trns-8bit.png +0 -0
  66. data/test/data/images/greyscale-with-gamma1.0.png +0 -0
  67. data/test/data/images/greyscale-with-gamma1.5.png +0 -0
  68. data/test/data/images/indexed-1bit.png +0 -0
  69. data/test/data/images/indexed-2bit.png +0 -0
  70. data/test/data/images/indexed-4bit.png +0 -0
  71. data/test/data/images/indexed-8bit.png +0 -0
  72. data/test/data/images/indexed-alpha-4bit.png +0 -0
  73. data/test/data/images/indexed-alpha-8bit.png +0 -0
  74. data/test/data/images/rgb.jpg +0 -0
  75. data/test/data/images/truecolour-8bit.png +0 -0
  76. data/test/data/images/truecolour-alpha-8bit.png +0 -0
  77. data/test/data/images/truecolour-gama-chrm-8bit.png +0 -0
  78. data/test/data/images/truecolour-srgb-8bit.png +0 -0
  79. data/test/data/images/ycck.jpg +0 -0
  80. data/test/data/minimal.pdf +44 -0
  81. data/test/data/standard-security-handler/README +9 -0
  82. data/test/data/standard-security-handler/bothpwd-aes-128bit-V4.pdf +44 -0
  83. data/test/data/standard-security-handler/bothpwd-aes-256bit-V5.pdf +0 -0
  84. data/test/data/standard-security-handler/bothpwd-arc4-128bit-V2.pdf +43 -0
  85. data/test/data/standard-security-handler/bothpwd-arc4-128bit-V4.pdf +43 -0
  86. data/test/data/standard-security-handler/bothpwd-arc4-40bit-V1.pdf +0 -0
  87. data/test/data/standard-security-handler/nopwd-aes-128bit-V4.pdf +43 -0
  88. data/test/data/standard-security-handler/nopwd-aes-256bit-V5.pdf +0 -0
  89. data/test/data/standard-security-handler/nopwd-arc4-128bit-V2.pdf +43 -0
  90. data/test/data/standard-security-handler/nopwd-arc4-128bit-V4.pdf +43 -0
  91. data/test/data/standard-security-handler/nopwd-arc4-40bit-V1.pdf +43 -0
  92. data/test/data/standard-security-handler/ownerpwd-aes-128bit-V4.pdf +0 -0
  93. data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5.pdf +43 -0
  94. data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V2.pdf +43 -0
  95. data/test/data/standard-security-handler/ownerpwd-arc4-128bit-V4.pdf +43 -0
  96. data/test/data/standard-security-handler/ownerpwd-arc4-40bit-V1.pdf +43 -0
  97. data/test/data/standard-security-handler/userpwd-aes-128bit-V4.pdf +43 -0
  98. data/test/data/standard-security-handler/userpwd-aes-256bit-V5.pdf +43 -0
  99. data/test/data/standard-security-handler/userpwd-arc4-128bit-V2.pdf +0 -0
  100. data/test/data/standard-security-handler/userpwd-arc4-128bit-V4.pdf +0 -0
  101. data/test/data/standard-security-handler/userpwd-arc4-40bit-V1.pdf +43 -0
  102. data/test/hexapdf/common_tokenizer_tests.rb +236 -0
  103. data/test/hexapdf/content/common.rb +39 -0
  104. data/test/hexapdf/content/graphic_object/test_arc.rb +102 -0
  105. data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +90 -0
  106. data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
  107. data/test/hexapdf/content/graphic_object/test_solid_arc.rb +86 -0
  108. data/test/hexapdf/content/test_canvas.rb +1279 -0
  109. data/test/hexapdf/content/test_color_space.rb +176 -0
  110. data/test/hexapdf/content/test_graphics_state.rb +151 -0
  111. data/test/hexapdf/content/test_operator.rb +619 -0
  112. data/test/hexapdf/content/test_parser.rb +99 -0
  113. data/test/hexapdf/content/test_processor.rb +163 -0
  114. data/test/hexapdf/content/test_transformation_matrix.rb +64 -0
  115. data/test/hexapdf/document/test_files.rb +72 -0
  116. data/test/hexapdf/document/test_fonts.rb +60 -0
  117. data/test/hexapdf/document/test_images.rb +72 -0
  118. data/test/hexapdf/document/test_pages.rb +130 -0
  119. data/test/hexapdf/encryption/common.rb +87 -0
  120. data/test/hexapdf/encryption/test_aes.rb +129 -0
  121. data/test/hexapdf/encryption/test_arc4.rb +39 -0
  122. data/test/hexapdf/encryption/test_fast_aes.rb +17 -0
  123. data/test/hexapdf/encryption/test_fast_arc4.rb +12 -0
  124. data/test/hexapdf/encryption/test_identity.rb +21 -0
  125. data/test/hexapdf/encryption/test_ruby_aes.rb +23 -0
  126. data/test/hexapdf/encryption/test_ruby_arc4.rb +20 -0
  127. data/test/hexapdf/encryption/test_security_handler.rb +380 -0
  128. data/test/hexapdf/encryption/test_standard_security_handler.rb +322 -0
  129. data/test/hexapdf/filter/common.rb +53 -0
  130. data/test/hexapdf/filter/test_ascii85_decode.rb +59 -0
  131. data/test/hexapdf/filter/test_ascii_hex_decode.rb +38 -0
  132. data/test/hexapdf/filter/test_crypt.rb +21 -0
  133. data/test/hexapdf/filter/test_encryption.rb +24 -0
  134. data/test/hexapdf/filter/test_flate_decode.rb +44 -0
  135. data/test/hexapdf/filter/test_lzw_decode.rb +52 -0
  136. data/test/hexapdf/filter/test_predictor.rb +219 -0
  137. data/test/hexapdf/filter/test_run_length_decode.rb +32 -0
  138. data/test/hexapdf/font/cmap/test_parser.rb +102 -0
  139. data/test/hexapdf/font/cmap/test_writer.rb +66 -0
  140. data/test/hexapdf/font/encoding/test_base.rb +45 -0
  141. data/test/hexapdf/font/encoding/test_difference_encoding.rb +29 -0
  142. data/test/hexapdf/font/encoding/test_glyph_list.rb +59 -0
  143. data/test/hexapdf/font/encoding/test_zapf_dingbats_encoding.rb +16 -0
  144. data/test/hexapdf/font/test_cmap.rb +104 -0
  145. data/test/hexapdf/font/test_encoding.rb +27 -0
  146. data/test/hexapdf/font/test_invalid_glyph.rb +34 -0
  147. data/test/hexapdf/font/test_true_type_wrapper.rb +186 -0
  148. data/test/hexapdf/font/test_type1_wrapper.rb +107 -0
  149. data/test/hexapdf/font/true_type/common.rb +17 -0
  150. data/test/hexapdf/font/true_type/table/common.rb +27 -0
  151. data/test/hexapdf/font/true_type/table/test_cmap.rb +47 -0
  152. data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +141 -0
  153. data/test/hexapdf/font/true_type/table/test_directory.rb +30 -0
  154. data/test/hexapdf/font/true_type/table/test_glyf.rb +58 -0
  155. data/test/hexapdf/font/true_type/table/test_head.rb +56 -0
  156. data/test/hexapdf/font/true_type/table/test_hhea.rb +26 -0
  157. data/test/hexapdf/font/true_type/table/test_hmtx.rb +30 -0
  158. data/test/hexapdf/font/true_type/table/test_kern.rb +61 -0
  159. data/test/hexapdf/font/true_type/table/test_loca.rb +33 -0
  160. data/test/hexapdf/font/true_type/table/test_maxp.rb +50 -0
  161. data/test/hexapdf/font/true_type/table/test_name.rb +76 -0
  162. data/test/hexapdf/font/true_type/table/test_os2.rb +55 -0
  163. data/test/hexapdf/font/true_type/table/test_post.rb +78 -0
  164. data/test/hexapdf/font/true_type/test_builder.rb +42 -0
  165. data/test/hexapdf/font/true_type/test_font.rb +116 -0
  166. data/test/hexapdf/font/true_type/test_optimizer.rb +26 -0
  167. data/test/hexapdf/font/true_type/test_subsetter.rb +73 -0
  168. data/test/hexapdf/font/true_type/test_table.rb +48 -0
  169. data/test/hexapdf/font/type1/common.rb +6 -0
  170. data/test/hexapdf/font/type1/test_afm_parser.rb +65 -0
  171. data/test/hexapdf/font/type1/test_font.rb +104 -0
  172. data/test/hexapdf/font/type1/test_font_metrics.rb +22 -0
  173. data/test/hexapdf/font/type1/test_pfb_parser.rb +37 -0
  174. data/test/hexapdf/font_loader/test_from_configuration.rb +43 -0
  175. data/test/hexapdf/font_loader/test_from_file.rb +36 -0
  176. data/test/hexapdf/font_loader/test_standard14.rb +33 -0
  177. data/test/hexapdf/image_loader/test_jpeg.rb +93 -0
  178. data/test/hexapdf/image_loader/test_pdf.rb +47 -0
  179. data/test/hexapdf/image_loader/test_png.rb +259 -0
  180. data/test/hexapdf/layout/test_box.rb +154 -0
  181. data/test/hexapdf/layout/test_frame.rb +350 -0
  182. data/test/hexapdf/layout/test_image_box.rb +73 -0
  183. data/test/hexapdf/layout/test_inline_box.rb +71 -0
  184. data/test/hexapdf/layout/test_line.rb +206 -0
  185. data/test/hexapdf/layout/test_style.rb +790 -0
  186. data/test/hexapdf/layout/test_text_box.rb +140 -0
  187. data/test/hexapdf/layout/test_text_fragment.rb +375 -0
  188. data/test/hexapdf/layout/test_text_layouter.rb +758 -0
  189. data/test/hexapdf/layout/test_text_shaper.rb +62 -0
  190. data/test/hexapdf/layout/test_width_from_polygon.rb +109 -0
  191. data/test/hexapdf/task/test_dereference.rb +51 -0
  192. data/test/hexapdf/task/test_optimize.rb +162 -0
  193. data/test/hexapdf/test_composer.rb +258 -0
  194. data/test/hexapdf/test_configuration.rb +93 -0
  195. data/test/hexapdf/test_data_dir.rb +32 -0
  196. data/test/hexapdf/test_dictionary.rb +340 -0
  197. data/test/hexapdf/test_dictionary_fields.rb +269 -0
  198. data/test/hexapdf/test_document.rb +641 -0
  199. data/test/hexapdf/test_filter.rb +100 -0
  200. data/test/hexapdf/test_importer.rb +106 -0
  201. data/test/hexapdf/test_object.rb +258 -0
  202. data/test/hexapdf/test_parser.rb +645 -0
  203. data/test/hexapdf/test_pdf_array.rb +169 -0
  204. data/test/hexapdf/test_rectangle.rb +73 -0
  205. data/test/hexapdf/test_reference.rb +50 -0
  206. data/test/hexapdf/test_revision.rb +188 -0
  207. data/test/hexapdf/test_revisions.rb +196 -0
  208. data/test/hexapdf/test_serializer.rb +195 -0
  209. data/test/hexapdf/test_stream.rb +274 -0
  210. data/test/hexapdf/test_tokenizer.rb +80 -0
  211. data/test/hexapdf/test_type.rb +18 -0
  212. data/test/hexapdf/test_writer.rb +140 -0
  213. data/test/hexapdf/test_xref_section.rb +61 -0
  214. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +795 -0
  215. data/test/hexapdf/type/acro_form/test_button_field.rb +308 -0
  216. data/test/hexapdf/type/acro_form/test_choice_field.rb +220 -0
  217. data/test/hexapdf/type/acro_form/test_field.rb +259 -0
  218. data/test/hexapdf/type/acro_form/test_form.rb +357 -0
  219. data/test/hexapdf/type/acro_form/test_signature_field.rb +38 -0
  220. data/test/hexapdf/type/acro_form/test_text_field.rb +201 -0
  221. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +88 -0
  222. data/test/hexapdf/type/actions/test_launch.rb +24 -0
  223. data/test/hexapdf/type/actions/test_uri.rb +23 -0
  224. data/test/hexapdf/type/annotations/test_markup_annotation.rb +22 -0
  225. data/test/hexapdf/type/annotations/test_text.rb +34 -0
  226. data/test/hexapdf/type/annotations/test_widget.rb +225 -0
  227. data/test/hexapdf/type/test_annotation.rb +97 -0
  228. data/test/hexapdf/type/test_catalog.rb +48 -0
  229. data/test/hexapdf/type/test_cid_font.rb +61 -0
  230. data/test/hexapdf/type/test_file_specification.rb +141 -0
  231. data/test/hexapdf/type/test_font.rb +67 -0
  232. data/test/hexapdf/type/test_font_descriptor.rb +61 -0
  233. data/test/hexapdf/type/test_font_simple.rb +176 -0
  234. data/test/hexapdf/type/test_font_true_type.rb +31 -0
  235. data/test/hexapdf/type/test_font_type0.rb +120 -0
  236. data/test/hexapdf/type/test_font_type1.rb +142 -0
  237. data/test/hexapdf/type/test_font_type3.rb +26 -0
  238. data/test/hexapdf/type/test_form.rb +120 -0
  239. data/test/hexapdf/type/test_image.rb +261 -0
  240. data/test/hexapdf/type/test_info.rb +9 -0
  241. data/test/hexapdf/type/test_object_stream.rb +117 -0
  242. data/test/hexapdf/type/test_page.rb +598 -0
  243. data/test/hexapdf/type/test_page_tree_node.rb +315 -0
  244. data/test/hexapdf/type/test_resources.rb +209 -0
  245. data/test/hexapdf/type/test_trailer.rb +116 -0
  246. data/test/hexapdf/type/test_xref_stream.rb +143 -0
  247. data/test/hexapdf/utils/test_bit_field.rb +63 -0
  248. data/test/hexapdf/utils/test_bit_stream.rb +69 -0
  249. data/test/hexapdf/utils/test_graphics_helpers.rb +37 -0
  250. data/test/hexapdf/utils/test_lru_cache.rb +22 -0
  251. data/test/hexapdf/utils/test_object_hash.rb +120 -0
  252. data/test/hexapdf/utils/test_pdf_doc_encoding.rb +18 -0
  253. data/test/hexapdf/utils/test_sorted_tree_node.rb +239 -0
  254. data/test/test_helper.rb +58 -0
  255. metadata +263 -3
@@ -0,0 +1,795 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require_relative '../../content/common'
5
+ require 'hexapdf/document'
6
+ require 'hexapdf/type/acro_form/appearance_generator'
7
+
8
+ describe HexaPDF::Type::AcroForm::AppearanceGenerator do
9
+ before do
10
+ @doc = HexaPDF::Document.new
11
+ @page = @doc.pages.add
12
+ @form = @doc.acro_form(create: true)
13
+ end
14
+
15
+ describe "create_appearances" do
16
+ before do
17
+ @field = @doc.add({FT: :Btn}, type: :XXAcroFormField, subtype: :Btn)
18
+ @widget = @doc.wrap({Parent: @field, Type: :Annot, Subtype: :Widget})
19
+ @generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
20
+ end
21
+
22
+ it "fails for unsupported button fields" do
23
+ @field.flag(:push_button)
24
+ @generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
25
+ assert_raises(HexaPDF::Error) { @generator.create_appearances }
26
+ end
27
+
28
+ it "fails for unsupported field types" do
29
+ @field[:FT] = :Unknown
30
+ assert_raises(HexaPDF::Error) { @generator.create_appearances }
31
+ end
32
+ end
33
+
34
+ describe "background color and border" do
35
+ before do
36
+ @field = @doc.add({FT: :Btn}, type: :XXAcroFormField, subtype: :Btn)
37
+ @widget = @field.create_widget(@page, defaults: false, Rect: [0, 0, 10, 20])
38
+ @xform = @doc.add({Type: :XObject, Subtype: :Form, BBox: @widget[:Rect]})
39
+ @generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
40
+ end
41
+
42
+ def execute(circular = false)
43
+ @generator.send(:apply_background_and_border, @widget.border_style, @xform.canvas,
44
+ circular: circular)
45
+ end
46
+
47
+ it "applies no background color or border if none is set" do
48
+ execute
49
+ assert_operators(@xform.stream, [])
50
+ end
51
+
52
+ it "applies a background color if one set" do
53
+ @widget.background_color(0.5)
54
+ execute
55
+ execute(true)
56
+ assert_operators(@xform.stream,
57
+ [[:save_graphics_state],
58
+ [:set_device_gray_non_stroking_color, [0.5]],
59
+ [:append_rectangle, [0, 0, 10, 20]],
60
+ [:fill_path_non_zero], [:restore_graphics_state],
61
+
62
+ [:save_graphics_state],
63
+ [:set_device_gray_non_stroking_color, [0.5]],
64
+ [:move_to, [10.0, 10.0]],
65
+ [:curve_to, [10.0, 11.78411, 9.045085, 13.438072, 7.5, 14.330127]],
66
+ [:curve_to, [5.954915, 15.222182, 4.045085, 15.222182, 2.5, 14.330127]],
67
+ [:curve_to, [0.954915, 13.438072, 0.0, 11.78411, 0.0, 10.0]],
68
+ [:curve_to, [-0.0, 8.21589, 0.954915, 6.561928, 2.5, 5.669873]],
69
+ [:curve_to, [4.045085, 4.777818, 5.954915, 4.777818, 7.5, 5.669873]],
70
+ [:curve_to, [9.045085, 6.561928, 10.0, 8.21589, 10.0, 10.0]],
71
+ [:close_subpath], [:fill_path_non_zero], [:restore_graphics_state]])
72
+ end
73
+
74
+ it "sets the border color and width correctly" do
75
+ @widget.border_style(color: 0.5, width: 4)
76
+ execute
77
+ execute(true)
78
+ assert_operators(@xform.stream,
79
+ [[:save_graphics_state],
80
+ [:set_device_gray_stroking_color, [0.5]],
81
+ [:set_line_width, [4]],
82
+ [:append_rectangle, [2, 2, 6, 16]],
83
+ [:stroke_path], [:restore_graphics_state],
84
+
85
+ [:save_graphics_state],
86
+ [:set_device_gray_stroking_color, [0.5]],
87
+ [:set_line_width, [4]],
88
+ [:move_to, [8.0, 10.0]],
89
+ [:curve_to, [8.0, 11.070466, 7.427051, 12.062843, 6.5, 12.598076]],
90
+ [:curve_to, [5.572949, 13.133309, 4.427051, 13.133309, 3.5, 12.598076]],
91
+ [:curve_to, [2.572949, 12.062843, 2.0, 11.070466, 2.0, 10.0]],
92
+ [:curve_to, [2.0, 8.929534, 2.572949, 7.937157, 3.5, 7.401924]],
93
+ [:curve_to, [4.427051, 6.866691, 5.572949, 6.866691, 6.5, 7.401924]],
94
+ [:curve_to, [7.427051, 7.937157, 8.0, 8.929534, 8.0, 10.0]],
95
+ [:close_subpath], [:stroke_path], [:restore_graphics_state]])
96
+ end
97
+
98
+ it "handles the case of an underlined border" do
99
+ @widget.border_style(style: :underlined, width: 2)
100
+ execute
101
+ execute(true)
102
+ assert_operators(@xform.stream,
103
+ [[:save_graphics_state],
104
+ [:set_line_width, [2]],
105
+ [:move_to, [1, 1]], [:line_to, [9.0, 1]],
106
+ [:stroke_path], [:restore_graphics_state],
107
+
108
+ [:save_graphics_state],
109
+ [:set_line_width, [2]],
110
+ [:move_to, [1.0, 10.0]],
111
+ [:curve_to, [1.0, 8.572712, 1.763932, 7.249543, 3.0, 6.535898]],
112
+ [:curve_to, [4.236068, 5.822254, 5.763932, 5.822254, 7.0, 6.535898]],
113
+ [:curve_to, [8.236068, 7.249543, 9.0, 8.572712, 9.0, 10.0]],
114
+ [:stroke_path], [:restore_graphics_state]])
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
134
+ end
135
+
136
+ describe "draw_marker" do
137
+ before do
138
+ @field = @doc.add({FT: :Btn}, type: :XXAcroFormField, subtype: :Btn)
139
+ @widget = @field.create_widget(@page, defaults: false, Rect: [0, 0, 10, 20])
140
+ @xform = @doc.add({Type: :XObject, Subtype: :Form, BBox: @widget[:Rect]})
141
+ @generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
142
+ end
143
+
144
+ def execute
145
+ @generator.send(:draw_marker, @xform.canvas, @widget[:Rect], @widget.border_style.width,
146
+ @widget.marker_style)
147
+ end
148
+
149
+ it "handles the marker :circle specially for radio button widgets" do
150
+ @field.initialize_as_radio_button
151
+ @widget.marker_style(style: :circle, color: 0.5)
152
+ execute
153
+ assert_operators(@xform.stream,
154
+ [[:set_device_gray_non_stroking_color, [0.5]],
155
+ [:move_to, [7.0, 10.0]],
156
+ [:curve_to, [7.0, 10.713644, 6.618034, 11.375229, 6.0, 11.732051]],
157
+ [:curve_to, [5.381966, 12.088873, 4.618034, 12.088873, 4.0, 11.732051]],
158
+ [:curve_to, [3.381966, 11.375229, 3.0, 10.713644, 3.0, 10.0]],
159
+ [:curve_to, [3.0, 9.286356, 3.381966, 8.624771, 4.0, 8.267949]],
160
+ [:curve_to, [4.618034, 7.911127, 5.381966, 7.911127, 6.0, 8.267949]],
161
+ [:curve_to, [6.618034, 8.624771, 7.0, 9.286356, 7.0, 10.0]],
162
+ [:close_subpath],
163
+ [:fill_path_non_zero]])
164
+ end
165
+
166
+ it "handles the marker :cross specially" do
167
+ @widget.marker_style(style: :cross, color: 0.5)
168
+ execute
169
+ assert_operators(@xform.stream,
170
+ [[:set_device_gray_stroking_color, [0.5]],
171
+ [:move_to, [1, 1]], [:line_to, [9, 19]],
172
+ [:move_to, [1, 19]], [:line_to, [9, 1]],
173
+ [:stroke_path]])
174
+ end
175
+
176
+ describe "handles the normal markers by drawing them using the ZapfDingbats font" do
177
+ it "works with font auto-sizing" do
178
+ @widget.marker_style(style: :check, color: 0.5, size: 0)
179
+ execute
180
+ assert_operators(@xform.stream,
181
+ [[:set_font_and_size, [:F1, 8]],
182
+ [:set_device_gray_non_stroking_color, [0.5]],
183
+ [:begin_text],
184
+ [:set_text_matrix, [1, 0, 0, 1, 1.616, 7.236]],
185
+ [:show_text, ["4"]],
186
+ [:end_text]])
187
+ end
188
+
189
+ it "works with a fixed font size" do
190
+ @widget.marker_style(style: :check, color: 0.5, size: 5)
191
+ execute
192
+ assert_operators(@xform.stream,
193
+ [[:set_font_and_size, [:F1, 5]],
194
+ [:set_device_gray_non_stroking_color, [0.5]],
195
+ [:begin_text],
196
+ [:set_text_matrix, [1, 0, 0, 1, 2.885, 8.2725]],
197
+ [:show_text, ["4"]],
198
+ [:end_text]])
199
+ end
200
+ end
201
+ end
202
+
203
+ describe "button fields" do
204
+ before do
205
+ @field = @doc.add({FT: :Btn}, type: :XXAcroFormField, subtype: :Btn)
206
+ end
207
+
208
+ describe "check box" do
209
+ before do
210
+ @widget = @field.create_widget(@page, Rect: [0, 0, 0, 0])
211
+ @generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
212
+ @field.field_value = :Off
213
+ end
214
+
215
+ it "updates the widgets' /AS entry to point to the selected appearance" do
216
+ @generator.create_appearances
217
+ assert_equal(@field[:V], @widget[:AS])
218
+ end
219
+
220
+ it "set the print flag on the widgets" do
221
+ @generator.create_appearances
222
+ assert(@widget.flagged?(:print))
223
+ end
224
+
225
+ it "adjusts the /Rect if width is zero" do
226
+ @generator.create_appearances
227
+ assert_equal(12, @widget[:Rect].width)
228
+ end
229
+
230
+ it "adjusts the /Rect if height is zero" do
231
+ @generator.create_appearances
232
+ assert_equal(12, @widget[:Rect].height)
233
+ end
234
+
235
+ it "creates the needed appearance streams" do
236
+ @widget[:AP][:N].delete(:Off)
237
+ @generator.create_appearances
238
+ assert_equal(:XObject, @widget[:AP][:N][:Off].type)
239
+ assert_equal(:XObject, @widget[:AP][:N][:Yes].type)
240
+ end
241
+
242
+ it "creates the /Off appearance stream" do
243
+ @generator.create_appearances
244
+ assert_operators(@widget[:AP][:N][:Off].stream,
245
+ [[:save_graphics_state],
246
+ [:set_device_gray_non_stroking_color, [1.0]],
247
+ [:append_rectangle, [0, 0, 12, 12]],
248
+ [:fill_path_non_zero],
249
+ [:append_rectangle, [0.5, 0.5, 11, 11]],
250
+ [:stroke_path], [:restore_graphics_state]])
251
+ end
252
+
253
+ it "creates the /Yes appearance stream" do
254
+ @generator.create_appearances
255
+ assert_operators(@widget[:AP][:N][:Yes].stream,
256
+ [[:save_graphics_state],
257
+ [:set_device_gray_non_stroking_color, [1.0]],
258
+ [:append_rectangle, [0, 0, 12, 12]],
259
+ [:fill_path_non_zero],
260
+ [:append_rectangle, [0.5, 0.5, 11, 11]],
261
+ [:stroke_path], [:restore_graphics_state],
262
+
263
+ [:save_graphics_state],
264
+ [:set_font_and_size, [:F1, 10]],
265
+ [:begin_text],
266
+ [:set_text_matrix, [1, 0, 0, 1, 1.77, 2.545]],
267
+ [:show_text, ["4"]],
268
+ [:end_text],
269
+ [:restore_graphics_state]])
270
+ end
271
+
272
+ it "fails if the appearance dictionaries are not set up" do
273
+ @widget[:AP][:N].delete(:Yes)
274
+ assert_raises(HexaPDF::Error) { @generator.create_appearances }
275
+ end
276
+ end
277
+
278
+ describe "radio button" do
279
+ before do
280
+ @field.initialize_as_radio_button
281
+ @widget = @field.create_widget(@page, Rect: [0, 0, 0, 0], value: :radio)
282
+ @generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
283
+ end
284
+
285
+ it "updates the widgets' /AS entry to point to the selected appearance" do
286
+ @field.field_value = :radio
287
+ @generator.create_appearances
288
+ assert_equal(@field[:V], @widget[:AS])
289
+
290
+ @field.create_widget(@page, value: :other)
291
+ @field.field_value = :other
292
+ @generator.create_appearances
293
+ assert_equal(:Off, @widget[:AS])
294
+ end
295
+
296
+ it "set the print flag on the widgets" do
297
+ @generator.create_appearances
298
+ assert(@widget.flagged?(:print))
299
+ end
300
+
301
+ it "adjusts the /Rect if width is zero" do
302
+ @generator.create_appearances
303
+ assert_equal(12, @widget[:Rect].width)
304
+ end
305
+
306
+ it "adjusts the /Rect if height is zero" do
307
+ @generator.create_appearances
308
+ assert_equal(12, @widget[:Rect].height)
309
+ end
310
+
311
+ it "creates the needed appearance streams" do
312
+ @generator.create_appearances
313
+ assert_equal(:XObject, @widget[:AP][:N][:Off].type)
314
+ assert_equal(:XObject, @widget[:AP][:N][:radio].type)
315
+ end
316
+
317
+ it "creates the /Off appearance stream" do
318
+ @widget.marker_style(style: :cross)
319
+ @generator.create_appearances
320
+ assert_operators(@widget[:AP][:N][:Off].stream,
321
+ [[:save_graphics_state],
322
+ [:set_device_gray_non_stroking_color, [1.0]],
323
+ [:append_rectangle, [0, 0, 12, 12]],
324
+ [:fill_path_non_zero],
325
+ [:append_rectangle, [0.5, 0.5, 11, 11]],
326
+ [:stroke_path], [:restore_graphics_state]])
327
+ end
328
+
329
+ it "creates the appearance stream according to the set value" do
330
+ @widget.marker_style(style: :check)
331
+ @generator.create_appearances
332
+ assert_operators(@widget[:AP][:N][:radio].stream,
333
+ [[:save_graphics_state],
334
+ [:set_device_gray_non_stroking_color, [1.0]],
335
+ [:append_rectangle, [0, 0, 12, 12]],
336
+ [:fill_path_non_zero],
337
+ [:append_rectangle, [0.5, 0.5, 11, 11]],
338
+ [:stroke_path], [:restore_graphics_state],
339
+
340
+ [:save_graphics_state],
341
+ [:set_font_and_size, [:F1, 10]],
342
+ [:begin_text],
343
+ [:set_text_matrix, [1, 0, 0, 1, 1.77, 2.545]],
344
+ [:show_text, ["4"]],
345
+ [:end_text],
346
+ [:restore_graphics_state]])
347
+ end
348
+
349
+ it "fails if the appearance dictionaries are not set up" do
350
+ @widget[:AP][:N].delete(:radio)
351
+ assert_raises(HexaPDF::Error) { @generator.create_appearances }
352
+ end
353
+ end
354
+ end
355
+
356
+ describe "text fields" do
357
+ before do
358
+ @form.set_default_appearance_string
359
+ @field = @doc.add({FT: :Tx}, type: :XXAcroFormField, subtype: :Tx)
360
+ @widget = @field.create_widget(@page, Rect: [0, 0, 0, 0])
361
+ @generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
362
+ end
363
+
364
+ it "updates the widgets to use the :N appearance stream" do
365
+ @generator.create_appearances
366
+ assert_equal(:N, @widget[:AS])
367
+ end
368
+
369
+ it "set the print flag on the widgets" do
370
+ @generator.create_appearances
371
+ assert(@widget.flagged?(:print))
372
+ end
373
+
374
+ describe "it adjusts the :Rect when necessary" do
375
+ before do
376
+ @widget.border_style(width: 3)
377
+ end
378
+
379
+ it "uses a default width if the width is zero" do
380
+ @generator.create_appearances
381
+ assert_equal(@doc.config['acro_form.text_field.default_width'], @widget[:Rect].width)
382
+ end
383
+
384
+ it "uses the font size of the /DA if non-zero as basis for the height if it is zero" do
385
+ @field.set_default_appearance_string(font_size: 10)
386
+ @generator.create_appearances
387
+ assert_equal(15.25, @widget[:Rect].height)
388
+ end
389
+
390
+ it "uses a default font size as basis for the height if it and the set font size are zero" do
391
+ assert_equal(0, @field.parse_default_appearance_string[1])
392
+ @generator.create_appearances
393
+ assert_equal(15.25, @widget[:Rect].height)
394
+ end
395
+ end
396
+
397
+ it "adds an appropriate form XObject" do
398
+ @generator.create_appearances
399
+ form = @widget[:AP][:N]
400
+ assert_equal(:XObject, form.type)
401
+ assert_equal(:Form, form[:Subtype])
402
+ assert_equal([0, 0, @widget[:Rect].width, @widget[:Rect].height], form[:BBox])
403
+ assert_equal(@doc.acro_form.default_resources[:Font][:F1], form[:Resources][:Font][:F1])
404
+ end
405
+
406
+ it "re-uses the existing form XObject" do
407
+ @field[:V] = 'test'
408
+ @generator.create_appearances
409
+ form = @widget[:AP][:N]
410
+ form[:key] = :value
411
+ form.delete(:Subtype)
412
+ @widget[:AP][:N] = @doc.wrap(form, type: HexaPDF::Dictionary)
413
+
414
+ @field[:V] = 'test1'
415
+ @generator.create_appearances
416
+ assert_equal(form, @widget[:AP][:N])
417
+ refute(form.key?(:key))
418
+ assert_match(/test1/, form.contents)
419
+ end
420
+
421
+ describe "font size calculation" do
422
+ before do
423
+ @widget[:Rect].height = 20
424
+ @widget[:Rect].width = 100
425
+ @field.field_value = ''
426
+ end
427
+
428
+ it "uses the non-zero font size" do
429
+ @field.set_default_appearance_string(font_size: 10)
430
+ @generator.create_appearances
431
+ assert_operators(@widget[:AP][:N].stream,
432
+ [:set_font_and_size, [:F1, 10]],
433
+ range: 5)
434
+ end
435
+
436
+ it "calculates the font size based on the rectangle height and border width" do
437
+ @generator.create_appearances
438
+ assert_operators(@widget[:AP][:N].stream,
439
+ [:set_font_and_size, [:F1, 12.923875]],
440
+ range: 5)
441
+ @widget.border_style(width: 2, color: :transparent)
442
+ @generator.create_appearances
443
+ assert_operators(@widget[:AP][:N].stream,
444
+ [:set_font_and_size, [:F1, 11.487889]],
445
+ range: 5)
446
+ end
447
+
448
+ it " in case of mulitline auto-sizing" do
449
+ @field.initialize_as_multiline_text_field
450
+ @field[:V] = 'a'
451
+ @field.set_default_appearance_string(font_size: 0)
452
+ @generator.create_appearances
453
+ assert_operators(@widget[:AP][:N].stream,
454
+ [:set_font_and_size, [:F1, 12]],
455
+ range: 6)
456
+ end
457
+ end
458
+
459
+ describe "single line text fields" do
460
+ describe "quadding e.g. text alignment" do
461
+ before do
462
+ @field.field_value = 'Test'
463
+ @field.set_default_appearance_string(font_size: 10)
464
+ @widget[:Rect].height = 20
465
+ end
466
+
467
+ it "works for left aligned text" do
468
+ @field.text_alignment(:left)
469
+ @generator.create_appearances
470
+ assert_operators(@widget[:AP][:N].stream,
471
+ [:set_text_matrix, [1, 0, 0, 1, 2, 6.41]],
472
+ range: 7)
473
+ end
474
+
475
+ it "works for right aligned text" do
476
+ @field.text_alignment(:right)
477
+ @generator.create_appearances
478
+ assert_operators(@widget[:AP][:N].stream,
479
+ [:set_text_matrix, [1, 0, 0, 1, 78.55, 6.41]],
480
+ range: 7)
481
+ end
482
+
483
+ it "works for center aligned text" do
484
+ @field.text_alignment(:center)
485
+ @generator.create_appearances
486
+ assert_operators(@widget[:AP][:N].stream,
487
+ [:set_text_matrix, [1, 0, 0, 1, 40.275, 6.41]],
488
+ range: 7)
489
+ end
490
+
491
+ it "vertically aligns to the font descender if the text is too high" do
492
+ @widget[:Rect].height = 5
493
+ @generator.create_appearances
494
+ assert_operators(@widget[:AP][:N].stream,
495
+ [:set_text_matrix, [1, 0, 0, 1, 2, 3.07]],
496
+ range: 7)
497
+ end
498
+ end
499
+
500
+ it "creates the /N appearance stream according to the set string" do
501
+ @field.field_value = 'Text'
502
+ @generator.create_appearances
503
+ assert_operators(@widget[:AP][:N].stream,
504
+ [[:begin_marked_content, [:Tx]],
505
+ [:save_graphics_state],
506
+ [:append_rectangle, [1, 1, 98, 9.25]],
507
+ [:clip_path_non_zero],
508
+ [:end_path],
509
+ [:set_font_and_size, [:F1, 6.641436]],
510
+ [:begin_text],
511
+ [:set_text_matrix, [1, 0, 0, 1, 2, 3.240724]],
512
+ [:show_text, ["Text"]],
513
+ [:end_text],
514
+ [:restore_graphics_state],
515
+ [:end_marked_content]])
516
+ end
517
+ end
518
+
519
+ describe "multiline text fields" do
520
+ before do
521
+ @field.set_default_appearance_string(font_size: 10)
522
+ @field.initialize_as_multiline_text_field
523
+ @widget[:Rect].height = 30
524
+ @widget[:Rect].width = 100
525
+ end
526
+
527
+ describe "quadding e.g. text alignment" do
528
+ before do
529
+ @field[:V] = "Test\nValue"
530
+ end
531
+
532
+ it "works for left aligned text" do
533
+ @field.text_alignment(:left)
534
+ @generator.create_appearances
535
+ assert_operators(@widget[:AP][:N].stream,
536
+ [:set_text_matrix, [1, 0, 0, 1, 2, 16.195]],
537
+ range: 9)
538
+ end
539
+
540
+ it "works for right aligned text" do
541
+ @field.text_alignment(:right)
542
+ @generator.create_appearances
543
+ assert_operators(@widget[:AP][:N].stream,
544
+ [:set_text_matrix, [1, 0, 0, 1, 78.55, 16.195]],
545
+ range: 9)
546
+ end
547
+
548
+ it "works for center aligned text" do
549
+ @field.text_alignment(:center)
550
+ @generator.create_appearances
551
+ assert_operators(@widget[:AP][:N].stream,
552
+ [:set_text_matrix, [1, 0, 0, 1, 40.275, 16.195]],
553
+ range: 9)
554
+ end
555
+ end
556
+
557
+ it "creates the /N appearance stream according to the set string" do
558
+ @field.field_value = "Test\nValue"
559
+ @generator.create_appearances
560
+ assert_operators(@widget[:AP][:N].stream,
561
+ [[:begin_marked_content, [:Tx]],
562
+ [:save_graphics_state],
563
+ [:append_rectangle, [1, 1, 98, 28]],
564
+ [:clip_path_non_zero],
565
+ [:end_path],
566
+ [:save_graphics_state],
567
+ [:set_leading, [11.5625]],
568
+ [:set_font_and_size, [:F1, 10]],
569
+ [:begin_text],
570
+ [:set_text_matrix, [1, 0, 0, 1, 2, 16.195]],
571
+ [:show_text, ['Test']],
572
+ [:move_text_next_line],
573
+ [:show_text, ['Value']],
574
+ [:end_text],
575
+ [:restore_graphics_state],
576
+ [:restore_graphics_state],
577
+ [:end_marked_content]])
578
+
579
+ @field.field_value = "Test\nTest\nTest"
580
+ @field.set_default_appearance_string(font_size: 0)
581
+ @generator.create_appearances
582
+ assert_operators(@widget[:AP][:N].stream,
583
+ [[:begin_marked_content, [:Tx]],
584
+ [:save_graphics_state],
585
+ [:append_rectangle, [1, 1, 98, 28]],
586
+ [:clip_path_non_zero],
587
+ [:end_path],
588
+ [:save_graphics_state],
589
+ [:set_leading, [9.25]],
590
+ [:set_font_and_size, [:F1, 8]],
591
+ [:begin_text],
592
+ [:set_text_matrix, [1, 0, 0, 1, 2, 18.556]],
593
+ [:show_text, ['Test']],
594
+ [:move_text_next_line],
595
+ [:show_text, ['Test']],
596
+ [:move_text_next_line],
597
+ [:show_text, ['Test']],
598
+ [:end_text],
599
+ [:restore_graphics_state],
600
+ [:restore_graphics_state],
601
+ [:end_marked_content]],
602
+ )
603
+ end
604
+ end
605
+
606
+ describe "comb text fields" do
607
+ before do
608
+ @field.set_default_appearance_string(font_size: 10)
609
+ @field.initialize_as_comb_text_field
610
+ @field[:MaxLen] = 10
611
+ @widget[:Rect].height = 20
612
+ @widget[:Rect].width = 100
613
+ end
614
+
615
+ describe "quadding e.g. text alignment" do
616
+ before do
617
+ @field[:V] = 'Test'
618
+ end
619
+
620
+ it "works for left aligned text" do
621
+ @field.text_alignment(:left)
622
+ @generator.create_appearances
623
+ assert_operators(@widget[:AP][:N].stream,
624
+ [:set_text_matrix, [1, 0, 0, 1, 2.945, 6.41]],
625
+ range: 7)
626
+ end
627
+
628
+ it "works for right aligned text" do
629
+ @field.text_alignment(:right)
630
+ @generator.create_appearances
631
+ assert_operators(@widget[:AP][:N].stream,
632
+ [:set_text_matrix, [1, 0, 0, 1, 62.945, 6.41]],
633
+ range: 7)
634
+ end
635
+
636
+ it "works for center aligned text" do
637
+ @field.text_alignment(:center)
638
+ @generator.create_appearances
639
+ assert_operators(@widget[:AP][:N].stream,
640
+ [:set_text_matrix, [1, 0, 0, 1, 32.945, 6.41]],
641
+ range: 7)
642
+ end
643
+
644
+ it "handles centering like Adobe, e.g. shift left, when text cannot be completely centered" do
645
+ @field.field_value = 'Hello'
646
+ @field.text_alignment(:center)
647
+ @generator.create_appearances
648
+ assert_operators(@widget[:AP][:N].stream,
649
+ [:set_text_matrix, [1, 0, 0, 1, 22.39, 6.41]],
650
+ range: 7)
651
+ end
652
+ end
653
+
654
+ it "creates the /N appearance stream according to the set string" do
655
+ @field.field_value = 'Text'
656
+ @generator.create_appearances
657
+ assert_operators(@widget[:AP][:N].stream,
658
+ [[:begin_marked_content, [:Tx]],
659
+ [:save_graphics_state],
660
+ [:append_rectangle, [1, 1, 98, 18]],
661
+ [:clip_path_non_zero],
662
+ [:end_path],
663
+ [:set_font_and_size, [:F1, 10]],
664
+ [:begin_text],
665
+ [:set_text_matrix, [1, 0, 0, 1, 2.945, 6.41]],
666
+ [:show_text_with_positioning, [['T', -416.5, 'e', -472, 'x', -611, 't']]],
667
+ [:end_text],
668
+ [:restore_graphics_state],
669
+ [:end_marked_content]])
670
+ end
671
+
672
+ it "fails if the /MaxLen key is not set" do
673
+ @field.delete(:MaxLen)
674
+ @field[:V] = 't'
675
+ assert_raises(HexaPDF::Error) { @generator.create_appearances }
676
+ end
677
+ end
678
+
679
+ describe "choice fields" do
680
+ it "works for combo boxes by using the text appearance method" do
681
+ @form.set_default_appearance_string
682
+ field = @doc.add({FT: :Ch}, type: :XXAcroFormField, subtype: :Ch)
683
+ field.initialize_as_combo_box
684
+ field.flag(:edit)
685
+ field.field_value = 'Test'
686
+ widget = field.create_widget(@page, Rect: [0, 0, 0, 0])
687
+ generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(widget)
688
+ generator.create_appearances
689
+ assert_kind_of(HexaPDF::Type::Form, widget[:AP][:N])
690
+ end
691
+
692
+ describe "list boxes" do
693
+ before do
694
+ @field = @doc.add({FT: :Ch}, type: :XXAcroFormField, subtype: :Ch)
695
+ @field.initialize_as_list_box
696
+ @field.flag(:multi_select)
697
+ @field.option_items = ['a', 'b', 'c']
698
+ @widget = @field.create_widget(@page, Rect: [0, 0, 90, 36])
699
+ @generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
700
+ end
701
+
702
+ it "uses a fixed font size for list box items if auto-sizing is used" do
703
+ @field.set_default_appearance_string(font_size: 0)
704
+ @generator.create_appearances
705
+ assert_operators(@widget[:AP][:N].stream,
706
+ [:set_font_and_size, [:F1, 12]],
707
+ range: 8)
708
+ end
709
+
710
+ it "uses the set values instead of the ones from /I if in conflict" do
711
+ @field[:I] = [0, 1]
712
+ @field[:V] = ['b']
713
+ @generator.create_appearances
714
+ assert_operators(@widget[:AP][:N].stream,
715
+ [[:set_device_rgb_non_stroking_color, [0.6, 0.756863, 0.854902]],
716
+ [:append_rectangle, [1, 7.25, 88, 13.875]],
717
+ [:fill_path_non_zero]],
718
+ range: 5..7)
719
+ end
720
+
721
+ it "creates the /N appearance stream" do
722
+ @field[:I] = [1, 2]
723
+ @field[:V] = ['b', 'c']
724
+ @generator.create_appearances
725
+ assert_operators(@widget[:AP][:N].stream,
726
+ [[:begin_marked_content, [:Tx]],
727
+ [:save_graphics_state],
728
+ [:append_rectangle, [1, 1, 88, 34]],
729
+ [:clip_path_non_zero], [:end_path],
730
+ [:set_device_rgb_non_stroking_color, [0.6, 0.756863, 0.854902]],
731
+ [:append_rectangle, [1, 7.25, 88, 13.875]],
732
+ [:append_rectangle, [1, -6.625, 88, 13.875]],
733
+ [:fill_path_non_zero],
734
+ [:save_graphics_state],
735
+ [:set_leading, [13.875]],
736
+ [:set_font_and_size, [:F1, 12]],
737
+ [:set_device_gray_non_stroking_color, [0.0]],
738
+ [:begin_text],
739
+ [:set_text_matrix, [1, 0, 0, 1, 2, 23.609]],
740
+ [:show_text, ["a"]],
741
+ [:move_text_next_line],
742
+ [:show_text, ["b"]],
743
+ [:end_text],
744
+ [:restore_graphics_state], [:restore_graphics_state],
745
+ [:end_marked_content]])
746
+ end
747
+ end
748
+ end
749
+
750
+ describe "font resolution in case the referenced font is not usable" do
751
+ before do
752
+ @doc.config['acro_form.fallback_font'] = ['Times', {variant: :italic}]
753
+ @field[:V] = 'Test'
754
+ end
755
+
756
+ it "uses the fallback font if the font is not usable" do
757
+ def (@form.default_resources.font(:F1)).font_wrapper; nil; end
758
+ @generator.create_appearances
759
+ assert_equal(:'Times-Italic', @widget[:AP][:N][:Resources][:Font][:F2][:BaseFont])
760
+ end
761
+
762
+ it "uses the fallback font if the font is not found" do
763
+ @form.default_resources[:Font].delete(:F1)
764
+ @generator.create_appearances
765
+ assert_equal(:'Times-Italic', @widget[:AP][:N][:Resources][:Font][:F1][:BaseFont])
766
+ end
767
+
768
+ it "respects a simple fallback font name" do
769
+ @doc.config['acro_form.fallback_font'] = 'Times'
770
+ @form.default_resources[:Font].delete(:F1)
771
+ @generator.create_appearances
772
+ assert_equal(:'Times-Roman', @widget[:AP][:N][:Resources][:Font][:F1][:BaseFont])
773
+ end
774
+
775
+ it "respects a fallback font callable object" do
776
+ field = @field
777
+ @doc.config['acro_form.fallback_font'] = proc do |field_arg, font_arg|
778
+ assert_same(field.data, field_arg.data)
779
+ assert_nil(font_arg)
780
+ 'Times'
781
+ end
782
+ @form.default_resources[:Font].delete(:F1)
783
+ @generator.create_appearances
784
+ assert_equal(:'Times-Roman', @widget[:AP][:N][:Resources][:Font][:F1][:BaseFont])
785
+ end
786
+
787
+ it "fails if fallback fonts are disabled" do
788
+ @doc.config['acro_form.fallback_font'] = nil
789
+ @form.default_resources[:Font].delete(:F1)
790
+ msg = assert_raises(HexaPDF::Error) { @generator.create_appearances }
791
+ assert_match(/Font.*not usable/, msg.message)
792
+ end
793
+ end
794
+ end
795
+ end