hexapdf 0.11.5 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (255) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +115 -0
  3. data/LICENSE +1 -1
  4. data/examples/001-hello_world.rb +1 -1
  5. data/examples/002-graphics.rb +1 -1
  6. data/examples/003-arcs.rb +1 -1
  7. data/examples/004-optimizing.rb +1 -1
  8. data/examples/005-merging.rb +1 -1
  9. data/examples/006-standard_pdf_fonts.rb +1 -1
  10. data/examples/007-truetype.rb +1 -1
  11. data/examples/008-show_char_bboxes.rb +1 -1
  12. data/examples/009-text_layouter_alignment.rb +1 -1
  13. data/examples/010-text_layouter_inline_boxes.rb +1 -1
  14. data/examples/011-text_layouter_line_wrapping.rb +1 -1
  15. data/examples/012-text_layouter_styling.rb +1 -1
  16. data/examples/013-text_layouter_shapes.rb +1 -1
  17. data/examples/014-text_in_polygon.rb +1 -1
  18. data/examples/015-boxes.rb +1 -1
  19. data/examples/016-frame_automatic_box_placement.rb +1 -1
  20. data/examples/017-frame_text_flow.rb +1 -1
  21. data/examples/018-composer.rb +1 -1
  22. data/examples/019-acro_form.rb +51 -0
  23. data/lib/hexapdf.rb +1 -1
  24. data/lib/hexapdf/cli.rb +3 -1
  25. data/lib/hexapdf/cli/batch.rb +1 -1
  26. data/lib/hexapdf/cli/command.rb +18 -9
  27. data/lib/hexapdf/cli/files.rb +1 -1
  28. data/lib/hexapdf/cli/form.rb +240 -0
  29. data/lib/hexapdf/cli/image2pdf.rb +1 -1
  30. data/lib/hexapdf/cli/images.rb +1 -1
  31. data/lib/hexapdf/cli/info.rb +1 -1
  32. data/lib/hexapdf/cli/inspect.rb +1 -1
  33. data/lib/hexapdf/cli/merge.rb +1 -1
  34. data/lib/hexapdf/cli/modify.rb +1 -1
  35. data/lib/hexapdf/cli/optimize.rb +1 -1
  36. data/lib/hexapdf/cli/split.rb +1 -1
  37. data/lib/hexapdf/cli/watermark.rb +1 -1
  38. data/lib/hexapdf/composer.rb +2 -2
  39. data/lib/hexapdf/configuration.rb +66 -11
  40. data/lib/hexapdf/content.rb +3 -1
  41. data/lib/hexapdf/content/canvas.rb +5 -18
  42. data/lib/hexapdf/content/color_space.rb +111 -32
  43. data/lib/hexapdf/content/graphic_object.rb +1 -1
  44. data/lib/hexapdf/content/graphic_object/arc.rb +1 -1
  45. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
  46. data/lib/hexapdf/content/graphic_object/geom2d.rb +1 -1
  47. data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
  48. data/lib/hexapdf/content/graphics_state.rb +1 -1
  49. data/lib/hexapdf/content/operator.rb +9 -9
  50. data/lib/hexapdf/content/parser.rb +18 -5
  51. data/lib/hexapdf/content/processor.rb +1 -1
  52. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  53. data/lib/hexapdf/data_dir.rb +1 -1
  54. data/lib/hexapdf/dictionary.rb +1 -1
  55. data/lib/hexapdf/dictionary_fields.rb +1 -1
  56. data/lib/hexapdf/document.rb +14 -5
  57. data/lib/hexapdf/document/files.rb +1 -1
  58. data/lib/hexapdf/document/fonts.rb +1 -1
  59. data/lib/hexapdf/document/images.rb +1 -1
  60. data/lib/hexapdf/document/pages.rb +5 -12
  61. data/lib/hexapdf/encryption.rb +1 -1
  62. data/lib/hexapdf/encryption/aes.rb +1 -1
  63. data/lib/hexapdf/encryption/arc4.rb +1 -1
  64. data/lib/hexapdf/encryption/fast_aes.rb +1 -1
  65. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  66. data/lib/hexapdf/encryption/identity.rb +1 -1
  67. data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
  68. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  69. data/lib/hexapdf/encryption/security_handler.rb +7 -1
  70. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
  71. data/lib/hexapdf/error.rb +1 -1
  72. data/lib/hexapdf/filter.rb +3 -3
  73. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  74. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  75. data/lib/hexapdf/filter/encryption.rb +1 -1
  76. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  77. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  78. data/lib/hexapdf/filter/{jpx_decode.rb → pass_through.rb} +5 -5
  79. data/lib/hexapdf/filter/predictor.rb +1 -1
  80. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  81. data/lib/hexapdf/font/cmap.rb +1 -1
  82. data/lib/hexapdf/font/cmap/parser.rb +1 -1
  83. data/lib/hexapdf/font/cmap/writer.rb +1 -1
  84. data/lib/hexapdf/font/encoding.rb +1 -1
  85. data/lib/hexapdf/font/encoding/base.rb +1 -1
  86. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  87. data/lib/hexapdf/font/encoding/glyph_list.rb +1 -1
  88. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  89. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
  90. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  91. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  92. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +1 -1
  93. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  94. data/lib/hexapdf/font/invalid_glyph.rb +1 -1
  95. data/lib/hexapdf/font/true_type.rb +1 -1
  96. data/lib/hexapdf/font/true_type/builder.rb +1 -1
  97. data/lib/hexapdf/font/true_type/font.rb +1 -1
  98. data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
  99. data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
  100. data/lib/hexapdf/font/true_type/table.rb +1 -1
  101. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  102. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
  103. data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
  104. data/lib/hexapdf/font/true_type/table/glyf.rb +1 -1
  105. data/lib/hexapdf/font/true_type/table/head.rb +1 -1
  106. data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
  107. data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
  108. data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
  109. data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
  110. data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
  111. data/lib/hexapdf/font/true_type/table/name.rb +1 -1
  112. data/lib/hexapdf/font/true_type/table/os2.rb +1 -1
  113. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  114. data/lib/hexapdf/font/true_type_wrapper.rb +54 -51
  115. data/lib/hexapdf/font/type1.rb +1 -1
  116. data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
  117. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  118. data/lib/hexapdf/font/type1/font.rb +1 -1
  119. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  120. data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
  121. data/lib/hexapdf/font/type1_wrapper.rb +67 -51
  122. data/lib/hexapdf/font_loader.rb +1 -1
  123. data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
  124. data/lib/hexapdf/font_loader/from_file.rb +1 -1
  125. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  126. data/lib/hexapdf/image_loader.rb +1 -1
  127. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  128. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  129. data/lib/hexapdf/image_loader/png.rb +1 -1
  130. data/lib/hexapdf/importer.rb +2 -4
  131. data/lib/hexapdf/layout.rb +1 -1
  132. data/lib/hexapdf/layout/box.rb +1 -1
  133. data/lib/hexapdf/layout/frame.rb +1 -1
  134. data/lib/hexapdf/layout/image_box.rb +1 -1
  135. data/lib/hexapdf/layout/inline_box.rb +1 -1
  136. data/lib/hexapdf/layout/line.rb +1 -1
  137. data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
  138. data/lib/hexapdf/layout/style.rb +1 -1
  139. data/lib/hexapdf/layout/text_box.rb +9 -4
  140. data/lib/hexapdf/layout/text_fragment.rb +1 -1
  141. data/lib/hexapdf/layout/text_layouter.rb +1 -1
  142. data/lib/hexapdf/layout/text_shaper.rb +1 -1
  143. data/lib/hexapdf/layout/width_from_polygon.rb +1 -1
  144. data/lib/hexapdf/name_tree_node.rb +1 -1
  145. data/lib/hexapdf/number_tree_node.rb +1 -1
  146. data/lib/hexapdf/object.rb +2 -2
  147. data/lib/hexapdf/parser.rb +4 -3
  148. data/lib/hexapdf/pdf_array.rb +1 -1
  149. data/lib/hexapdf/rectangle.rb +31 -1
  150. data/lib/hexapdf/reference.rb +1 -1
  151. data/lib/hexapdf/revision.rb +2 -1
  152. data/lib/hexapdf/revisions.rb +1 -1
  153. data/lib/hexapdf/serializer.rb +2 -2
  154. data/lib/hexapdf/stream.rb +1 -1
  155. data/lib/hexapdf/task.rb +1 -1
  156. data/lib/hexapdf/task/dereference.rb +1 -1
  157. data/lib/hexapdf/task/optimize.rb +11 -4
  158. data/lib/hexapdf/tokenizer.rb +1 -1
  159. data/lib/hexapdf/type.rb +1 -1
  160. data/lib/hexapdf/type/acro_form.rb +7 -1
  161. data/lib/hexapdf/type/acro_form/appearance_generator.rb +401 -0
  162. data/lib/hexapdf/type/acro_form/button_field.rb +300 -0
  163. data/lib/hexapdf/type/acro_form/choice_field.rb +220 -0
  164. data/lib/hexapdf/type/acro_form/field.rb +220 -17
  165. data/lib/hexapdf/type/acro_form/form.rb +157 -7
  166. data/lib/hexapdf/type/acro_form/text_field.rb +186 -0
  167. data/lib/hexapdf/type/acro_form/variable_text_field.rb +122 -0
  168. data/lib/hexapdf/type/action.rb +1 -1
  169. data/lib/hexapdf/type/actions.rb +1 -1
  170. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  171. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  172. data/lib/hexapdf/type/actions/launch.rb +1 -1
  173. data/lib/hexapdf/type/actions/uri.rb +1 -1
  174. data/lib/hexapdf/type/annotation.rb +73 -3
  175. data/lib/hexapdf/type/annotations.rb +1 -1
  176. data/lib/hexapdf/type/annotations/link.rb +2 -2
  177. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  178. data/lib/hexapdf/type/annotations/text.rb +1 -1
  179. data/lib/hexapdf/type/annotations/widget.rb +239 -2
  180. data/lib/hexapdf/type/catalog.rb +21 -1
  181. data/lib/hexapdf/type/cid_font.rb +1 -1
  182. data/lib/hexapdf/type/embedded_file.rb +1 -1
  183. data/lib/hexapdf/type/file_specification.rb +1 -1
  184. data/lib/hexapdf/type/font.rb +18 -1
  185. data/lib/hexapdf/type/font_descriptor.rb +2 -2
  186. data/lib/hexapdf/type/font_simple.rb +1 -1
  187. data/lib/hexapdf/type/font_true_type.rb +1 -1
  188. data/lib/hexapdf/type/font_type0.rb +1 -1
  189. data/lib/hexapdf/type/font_type1.rb +16 -1
  190. data/lib/hexapdf/type/font_type3.rb +1 -1
  191. data/lib/hexapdf/type/form.rb +1 -1
  192. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  193. data/lib/hexapdf/type/icon_fit.rb +1 -1
  194. data/lib/hexapdf/type/image.rb +3 -1
  195. data/lib/hexapdf/type/info.rb +1 -1
  196. data/lib/hexapdf/type/names.rb +1 -1
  197. data/lib/hexapdf/type/object_stream.rb +1 -1
  198. data/lib/hexapdf/type/page.rb +1 -1
  199. data/lib/hexapdf/type/page_tree_node.rb +8 -11
  200. data/lib/hexapdf/type/resources.rb +16 -3
  201. data/lib/hexapdf/type/trailer.rb +2 -3
  202. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  203. data/lib/hexapdf/type/xref_stream.rb +1 -1
  204. data/lib/hexapdf/utils/bit_field.rb +38 -24
  205. data/lib/hexapdf/utils/bit_stream.rb +1 -1
  206. data/lib/hexapdf/utils/graphics_helpers.rb +1 -1
  207. data/lib/hexapdf/utils/lru_cache.rb +1 -1
  208. data/lib/hexapdf/utils/math_helpers.rb +1 -1
  209. data/lib/hexapdf/utils/object_hash.rb +1 -1
  210. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  211. data/lib/hexapdf/utils/sorted_tree_node.rb +1 -1
  212. data/lib/hexapdf/version.rb +2 -2
  213. data/lib/hexapdf/writer.rb +1 -1
  214. data/lib/hexapdf/xref_section.rb +1 -1
  215. data/test/hexapdf/content/common.rb +2 -2
  216. data/test/hexapdf/content/test_color_space.rb +71 -8
  217. data/test/hexapdf/content/test_operator.rb +22 -22
  218. data/test/hexapdf/content/test_parser.rb +14 -0
  219. data/test/hexapdf/document/test_fonts.rb +1 -1
  220. data/test/hexapdf/document/test_pages.rb +6 -6
  221. data/test/hexapdf/encryption/test_security_handler.rb +4 -0
  222. data/test/hexapdf/font/test_true_type_wrapper.rb +10 -7
  223. data/test/hexapdf/font/test_type1_wrapper.rb +32 -8
  224. data/test/hexapdf/layout/test_text_box.rb +20 -2
  225. data/test/hexapdf/task/test_optimize.rb +24 -8
  226. data/test/hexapdf/test_document.rb +12 -0
  227. data/test/hexapdf/test_parser.rb +10 -0
  228. data/test/hexapdf/test_rectangle.rb +14 -0
  229. data/test/hexapdf/test_revision.rb +3 -0
  230. data/test/hexapdf/test_serializer.rb +3 -3
  231. data/test/hexapdf/test_writer.rb +2 -2
  232. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +515 -0
  233. data/test/hexapdf/type/acro_form/test_button_field.rb +276 -0
  234. data/test/hexapdf/type/acro_form/test_choice_field.rb +137 -0
  235. data/test/hexapdf/type/acro_form/test_field.rb +124 -6
  236. data/test/hexapdf/type/acro_form/test_form.rb +189 -22
  237. data/test/hexapdf/type/acro_form/test_text_field.rb +119 -0
  238. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +77 -0
  239. data/test/hexapdf/type/annotations/test_text.rb +1 -1
  240. data/test/hexapdf/type/annotations/test_widget.rb +199 -0
  241. data/test/hexapdf/type/test_annotation.rb +45 -0
  242. data/test/hexapdf/type/test_catalog.rb +18 -0
  243. data/test/hexapdf/type/test_font.rb +5 -0
  244. data/test/hexapdf/type/test_font_type1.rb +8 -0
  245. data/test/hexapdf/type/test_image.rb +7 -0
  246. data/test/hexapdf/type/test_page_tree_node.rb +20 -12
  247. data/test/hexapdf/type/test_resources.rb +20 -0
  248. data/test/hexapdf/type/test_trailer.rb +4 -0
  249. data/test/hexapdf/utils/test_bit_field.rb +13 -1
  250. data/test/test_helper.rb +1 -1
  251. metadata +36 -20
  252. data/CONTRIBUTERS +0 -5
  253. data/VERSION +0 -1
  254. data/lib/hexapdf/filter/dct_decode.rb +0 -60
  255. data/man/man1/hexapdf.1 +0 -0
@@ -31,11 +31,11 @@ describe HexaPDF::Layout::TextBox do
31
31
  end
32
32
 
33
33
  it "respects the set width and height" do
34
- box = create_box([@inline_box] * 5, width: 40, height: 50, style: {padding: 10})
34
+ box = create_box([@inline_box], width: 40, height: 50, style: {padding: 10})
35
35
  assert(box.fit(100, 100, @frame))
36
36
  assert_equal(40, box.width)
37
37
  assert_equal(50, box.height)
38
- assert_equal([20, 20, 10], box.instance_variable_get(:@result).lines.map(&:width))
38
+ assert_equal([10], box.instance_variable_get(:@result).lines.map(&:width))
39
39
  end
40
40
 
41
41
  it "fits into the frame's outline" do
@@ -52,6 +52,16 @@ describe HexaPDF::Layout::TextBox do
52
52
  assert_equal(50, box.width)
53
53
  assert_equal(20, box.height)
54
54
  end
55
+
56
+ it "can't fit the text box if the set width is bigger than the available width" do
57
+ box = create_box([@inline_box], width: 101)
58
+ refute(box.fit(100, 100, @frame))
59
+ end
60
+
61
+ it "can't fit the text box if the set height is bigger than the available height" do
62
+ box = create_box([@inline_box], height: 101)
63
+ refute(box.fit(100, 100, @frame))
64
+ end
55
65
  end
56
66
 
57
67
  describe "split" do
@@ -70,6 +80,14 @@ describe HexaPDF::Layout::TextBox do
70
80
  assert_equal([nil, box], box.split(5, 20, @frame))
71
81
  end
72
82
 
83
+ it "works if the whole text box doesn't fits" do
84
+ box = create_box([@inline_box], width: 102)
85
+ assert_equal([nil, box], box.split(100, 100, @frame))
86
+
87
+ box = create_box([@inline_box], height: 102)
88
+ assert_equal([nil, box], box.split(100, 100, @frame))
89
+ end
90
+
73
91
  it "splits the box if necessary" do
74
92
  box = create_box([@inline_box] * 10)
75
93
  boxes = box.split(50, 10, @frame)
@@ -7,13 +7,15 @@ require 'hexapdf/task/optimize'
7
7
  describe HexaPDF::Task::Optimize do
8
8
  class TestType < HexaPDF::Dictionary
9
9
 
10
+ define_type :Test
10
11
  define_field :Optional, type: Symbol, default: :Optional
11
12
 
12
13
  end
13
14
 
14
15
  before do
16
+ HexaPDF::GlobalConfiguration['object.type_map'][:Test] = TestType
15
17
  @doc = HexaPDF::Document.new
16
- @obj1 = @doc.add(@doc.wrap({Optional: :Optional}, type: TestType))
18
+ @obj1 = @doc.add({Type: :Test, Optional: :Optional})
17
19
  @doc.trailer[:Test] = @doc.wrap(@obj1)
18
20
  @doc.revisions.add
19
21
  @obj2 = @doc.add({Type: :UsedEntry})
@@ -22,6 +24,10 @@ describe HexaPDF::Task::Optimize do
22
24
  @obj1[:Test] = @doc.wrap(@obj4, type: TestType)
23
25
  end
24
26
 
27
+ after do
28
+ HexaPDF::GlobalConfiguration['object.type_map'].delete(:Test)
29
+ end
30
+
25
31
  def assert_objstms_generated
26
32
  assert(@doc.revisions.all? {|rev| rev.any? {|obj| obj.type == :ObjStm } })
27
33
  assert(@doc.revisions.all? {|rev| rev.any? {|obj| obj.type == :XRef } })
@@ -40,7 +46,7 @@ describe HexaPDF::Task::Optimize do
40
46
  end
41
47
 
42
48
  def assert_default_deleted
43
- refute(@obj1.value.key?(:Optional))
49
+ refute(@doc.object(1).key?(:Optional))
44
50
  end
45
51
 
46
52
  describe "compact" do
@@ -84,21 +90,29 @@ describe HexaPDF::Task::Optimize do
84
90
  end
85
91
 
86
92
  describe "object_streams" do
87
- it "generates object streams" do
93
+ def reload_document_with_objstm_from_io
94
+ io = StringIO.new
88
95
  objstm = @doc.add({Type: :ObjStm})
89
- xref = @doc.add({Type: :XRef})
96
+ @doc.add({Type: :XRef})
97
+ objstm.add_object(@doc.add({Type: :Test}))
98
+ @doc.write(io)
99
+ io.rewind
100
+ @doc = HexaPDF::Document.new(io: io)
101
+ end
102
+
103
+ it "generates object streams" do
90
104
  210.times { @doc.add(5) }
105
+ objstm = @doc.add({Type: :ObjStm})
106
+ reload_document_with_objstm_from_io
91
107
  @doc.task(:optimize, object_streams: :generate)
92
108
  assert_objstms_generated
93
109
  assert_default_deleted
94
110
  assert_nil(@doc.object(objstm).value)
95
- assert(3, @doc.revisions.current.find_all {|obj| obj.type == :ObjStm }.size)
96
- assert([xref], @doc.revisions.current.find_all {|obj| obj.type == :XRef })
111
+ assert_equal(2, @doc.revisions.current.find_all {|obj| obj.type == :ObjStm }.size)
97
112
  end
98
113
 
99
114
  it "deletes object and xref streams" do
100
- @doc.add({Type: :ObjStm})
101
- @doc.add({Type: :XRef})
115
+ reload_document_with_objstm_from_io
102
116
  @doc.task(:optimize, object_streams: :delete, xref_streams: :delete)
103
117
  assert_no_objstms
104
118
  assert_no_xrefstms
@@ -107,9 +121,11 @@ describe HexaPDF::Task::Optimize do
107
121
 
108
122
  it "deletes object and generates xref streams" do
109
123
  @doc.add({Type: :ObjStm})
124
+ xref = @doc.add({Type: :XRef})
110
125
  @doc.task(:optimize, object_streams: :delete, xref_streams: :generate)
111
126
  assert_no_objstms
112
127
  assert_xrefstms_generated
128
+ assert_equal([xref], @doc.revisions.current.find_all {|obj| obj.type == :XRef })
113
129
  assert_default_deleted
114
130
  end
115
131
  end
@@ -584,6 +584,18 @@ describe HexaPDF::Document do
584
584
  end
585
585
  end
586
586
 
587
+ describe "acro_form" do
588
+ it "returns the main AcroForm object" do
589
+ assert_nil(@doc.acro_form)
590
+ @doc.catalog[:AcroForm] = 5
591
+ assert_equal(5, @doc.acro_form)
592
+ end
593
+
594
+ it "create the AcroForm object if instructed" do
595
+ assert_equal(:XXAcroForm, @doc.acro_form(create: true).type)
596
+ end
597
+ end
598
+
587
599
  describe "listener interface" do
588
600
  it "allows registering and dispatching messages" do
589
601
  args = []
@@ -231,6 +231,10 @@ describe HexaPDF::Parser do
231
231
  create_parser("startxref\n5")
232
232
  exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
233
233
  assert_match(/end-of-file marker not found/, exp.message)
234
+
235
+ create_parser("")
236
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
237
+ assert_match(/end-of-file marker not found/, exp.message)
234
238
  end
235
239
 
236
240
  it "fails if the startxref keyword is missing" do
@@ -251,6 +255,12 @@ describe HexaPDF::Parser do
251
255
  assert_match(/file header/, exp.message)
252
256
  end
253
257
 
258
+ it "fails if the header is missing" do
259
+ create_parser("no header")
260
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.file_header_version }
261
+ assert_match(/file header/, exp.message)
262
+ end
263
+
254
264
  it "ignores junk at the beginning of the file and correctly calculates offset" do
255
265
  create_parser("junk" * 200 + "\n%PDF-1.4\n")
256
266
  assert_equal('1.4', @parser.file_header_version)
@@ -36,6 +36,20 @@ describe HexaPDF::Rectangle do
36
36
  assert_equal(4, rect.height)
37
37
  end
38
38
 
39
+ it "allows setting all fields of the rectangle" do
40
+ rect = HexaPDF::Rectangle.new([2, 1, 0, 5])
41
+ rect.left = 5
42
+ rect.right = 1
43
+ rect.bottom = 2
44
+ rect.top = 3
45
+ assert_equal([5, 2, 1, 3], rect.value)
46
+
47
+ rect.width = 10
48
+ assert_equal(15, rect.right)
49
+ rect.height = 10
50
+ assert_equal(12, rect.top)
51
+ end
52
+
39
53
  it "allows comparison to arrays" do
40
54
  rect = HexaPDF::Rectangle.new([0, 1, 2, 5])
41
55
  assert(rect == [0, 1, 2, 5])
@@ -114,12 +114,14 @@ describe HexaPDF::Revision do
114
114
  @rev.delete(@ref, mark_as_free: false)
115
115
  refute(@rev.object?(@ref))
116
116
  assert(@obj.null?)
117
+ assert_raises(HexaPDF::Error) { @obj.document }
117
118
  end
118
119
 
119
120
  it "deletes objects specified by object number" do
120
121
  @rev.delete(@ref.oid, mark_as_free: false)
121
122
  refute(@rev.object?(@ref.oid))
122
123
  assert(@obj.null?)
124
+ assert_raises(HexaPDF::Error) { @obj.document }
123
125
  end
124
126
 
125
127
  it "marks the object as PDF null object when using mark_as_free=true" do
@@ -127,6 +129,7 @@ describe HexaPDF::Revision do
127
129
  @rev.delete(@ref)
128
130
  assert(@rev.object(@ref).null?)
129
131
  assert(@obj.null?)
132
+ assert_raises(HexaPDF::Error) { @obj.document }
130
133
  end
131
134
  end
132
135
 
@@ -75,18 +75,18 @@ describe HexaPDF::Serializer do
75
75
  assert_serialized('/lime#20Green', 'lime Green'.intern)
76
76
  assert_serialized('/paired#28#29parentheses', 'paired()parentheses'.intern)
77
77
  assert_serialized('/The_Key_of_F#23_Minor', 'The_Key_of_F#_Minor'.intern)
78
- assert_serialized('/', ''.intern)
78
+ assert_serialized('/ ', ''.intern)
79
79
  assert_serialized('/H#c3#b6#c3#9fgang', "Hößgang".intern)
80
80
  assert_serialized('/H#e8lp', "H\xE8lp".force_encoding('BINARY').intern)
81
81
  end
82
82
 
83
83
  it "serializes arrays" do
84
- assert_serialized("[-12 2.4321/Name true(345)true]", [-12, 2.4321, :Name, true, '345', true])
84
+ assert_serialized("[-12/ 2.4321/Name true(345)true]", [-12, :"", 2.4321, :Name, true, '345', true])
85
85
  assert_serialized("[]", [])
86
86
  end
87
87
 
88
88
  it "serializes hashes" do
89
- assert_serialized("<</hallo 5/other true/name[5]>>", hallo: 5, other: true, name: [5])
89
+ assert_serialized("<</hallo 5/ true/other true/name[5]>>", hallo: 5, "": true, other: true, name: [5])
90
90
  assert_serialized("<<>>", {})
91
91
  end
92
92
 
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
40
40
  219
41
41
  %%EOF
42
42
  3 0 obj
43
- <</Producer(HexaPDF version 0.11.5)>>
43
+ <</Producer(HexaPDF version 0.12.0)>>
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.11.5)>>
75
+ <</Producer(HexaPDF version 0.12.0)>>
76
76
  endobj
77
77
  2 0 obj
78
78
  <</Length 10>>stream
@@ -0,0 +1,515 @@
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 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
+ it "fails for unsupported field types" do
37
+ @field[:FT] = :Unknown
38
+ assert_raises(HexaPDF::Error) { @generator.create_appearances }
39
+ end
40
+ end
41
+
42
+ describe "background color and border" do
43
+ before do
44
+ @field = @doc.add({FT: :Btn}, type: :XXAcroFormField, subtype: :Btn)
45
+ @widget = @field.create_widget(@page, defaults: false, Rect: [0, 0, 10, 20])
46
+ @xform = @doc.add({Type: :XObject, Subtype: :Form, BBox: @widget[:Rect]})
47
+ @generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
48
+ end
49
+
50
+ def execute(circular = false)
51
+ @generator.send(:apply_background_and_border, @widget.border_style, @xform.canvas,
52
+ circular: circular)
53
+ end
54
+
55
+ it "applies no background color or border if none is set" do
56
+ execute
57
+ assert_operators(@xform.stream, [])
58
+ end
59
+
60
+ it "applies a background color if one set" do
61
+ @widget.background_color(0.5)
62
+ execute
63
+ execute(true)
64
+ assert_operators(@xform.stream,
65
+ [[:save_graphics_state],
66
+ [:set_device_gray_non_stroking_color, [0.5]],
67
+ [:append_rectangle, [0, 0, 10, 20]],
68
+ [:fill_path_non_zero], [:restore_graphics_state],
69
+
70
+ [:save_graphics_state],
71
+ [:set_device_gray_non_stroking_color, [0.5]],
72
+ [:move_to, [10.0, 10.0]],
73
+ [:curve_to, [10.0, 11.78411, 9.045085, 13.438072, 7.5, 14.330127]],
74
+ [:curve_to, [5.954915, 15.222182, 4.045085, 15.222182, 2.5, 14.330127]],
75
+ [:curve_to, [0.954915, 13.438072, 0.0, 11.78411, 0.0, 10.0]],
76
+ [:curve_to, [-0.0, 8.21589, 0.954915, 6.561928, 2.5, 5.669873]],
77
+ [:curve_to, [4.045085, 4.777818, 5.954915, 4.777818, 7.5, 5.669873]],
78
+ [:curve_to, [9.045085, 6.561928, 10.0, 8.21589, 10.0, 10.0]],
79
+ [:close_subpath], [:fill_path_non_zero], [:restore_graphics_state]])
80
+ end
81
+
82
+ it "sets the border color and width correctly" do
83
+ @widget.border_style(color: 0.5, width: 4)
84
+ execute
85
+ execute(true)
86
+ assert_operators(@xform.stream,
87
+ [[:save_graphics_state],
88
+ [:set_device_gray_stroking_color, [0.5]],
89
+ [:set_line_width, [4]],
90
+ [:append_rectangle, [2, 2, 6, 16]],
91
+ [:stroke_path], [:restore_graphics_state],
92
+
93
+ [:save_graphics_state],
94
+ [:set_device_gray_stroking_color, [0.5]],
95
+ [:set_line_width, [4]],
96
+ [:move_to, [8.0, 10.0]],
97
+ [:curve_to, [8.0, 11.070466, 7.427051, 12.062843, 6.5, 12.598076]],
98
+ [:curve_to, [5.572949, 13.133309, 4.427051, 13.133309, 3.5, 12.598076]],
99
+ [:curve_to, [2.572949, 12.062843, 2.0, 11.070466, 2.0, 10.0]],
100
+ [:curve_to, [2.0, 8.929534, 2.572949, 7.937157, 3.5, 7.401924]],
101
+ [:curve_to, [4.427051, 6.866691, 5.572949, 6.866691, 6.5, 7.401924]],
102
+ [:curve_to, [7.427051, 7.937157, 8.0, 8.929534, 8.0, 10.0]],
103
+ [:close_subpath], [:stroke_path], [:restore_graphics_state]])
104
+ end
105
+
106
+ it "handles the case of an underlined border" do
107
+ @widget.border_style(style: :underlined, width: 2)
108
+ execute
109
+ execute(true)
110
+ assert_operators(@xform.stream,
111
+ [[:save_graphics_state],
112
+ [:set_line_width, [2]],
113
+ [:move_to, [1, 1]], [:line_to, [9.0, 1]],
114
+ [:stroke_path], [:restore_graphics_state],
115
+
116
+ [:save_graphics_state],
117
+ [:set_line_width, [2]],
118
+ [:move_to, [1.0, 10.0]],
119
+ [:curve_to, [1.0, 8.572712, 1.763932, 7.249543, 3.0, 6.535898]],
120
+ [:curve_to, [4.236068, 5.822254, 5.763932, 5.822254, 7.0, 6.535898]],
121
+ [:curve_to, [8.236068, 7.249543, 9.0, 8.572712, 9.0, 10.0]],
122
+ [:stroke_path], [:restore_graphics_state]])
123
+ end
124
+ end
125
+
126
+ describe "draw_marker" do
127
+ before do
128
+ @field = @doc.add({FT: :Btn}, type: :XXAcroFormField, subtype: :Btn)
129
+ @widget = @field.create_widget(@page, defaults: false, Rect: [0, 0, 10, 20])
130
+ @xform = @doc.add({Type: :XObject, Subtype: :Form, BBox: @widget[:Rect]})
131
+ @generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
132
+ end
133
+
134
+ def execute
135
+ @generator.send(:draw_marker, @xform.canvas, @widget[:Rect], @widget.border_style.width,
136
+ @widget.marker_style)
137
+ end
138
+
139
+ it "handles the marker :circle specially for radio button widgets" do
140
+ @field.initialize_as_radio_button
141
+ @widget.marker_style(style: :circle, color: 0.5)
142
+ execute
143
+ assert_operators(@xform.stream,
144
+ [[:set_device_gray_non_stroking_color, [0.5]],
145
+ [:move_to, [7.0, 10.0]],
146
+ [:curve_to, [7.0, 10.713644, 6.618034, 11.375229, 6.0, 11.732051]],
147
+ [:curve_to, [5.381966, 12.088873, 4.618034, 12.088873, 4.0, 11.732051]],
148
+ [:curve_to, [3.381966, 11.375229, 3.0, 10.713644, 3.0, 10.0]],
149
+ [:curve_to, [3.0, 9.286356, 3.381966, 8.624771, 4.0, 8.267949]],
150
+ [:curve_to, [4.618034, 7.911127, 5.381966, 7.911127, 6.0, 8.267949]],
151
+ [:curve_to, [6.618034, 8.624771, 7.0, 9.286356, 7.0, 10.0]],
152
+ [:close_subpath],
153
+ [:fill_path_non_zero]])
154
+ end
155
+
156
+ it "handles the marker :cross specially" do
157
+ @widget.marker_style(style: :cross, color: 0.5)
158
+ execute
159
+ assert_operators(@xform.stream,
160
+ [[:set_device_gray_stroking_color, [0.5]],
161
+ [:move_to, [1, 1]], [:line_to, [9, 19]],
162
+ [:move_to, [1, 19]], [:line_to, [9, 1]],
163
+ [:stroke_path]])
164
+ end
165
+
166
+ describe "handles the normal markers by drawing them using the ZapfDingbats font" do
167
+ it "works with font auto-sizing" do
168
+ @widget.marker_style(style: :check, color: 0.5, size: 0)
169
+ execute
170
+ assert_operators(@xform.stream,
171
+ [[:set_font_and_size, [:F1, 8]],
172
+ [:set_device_gray_non_stroking_color, [0.5]],
173
+ [:begin_text],
174
+ [:set_text_matrix, [1, 0, 0, 1, 1.616, 7.236]],
175
+ [:show_text, ["4"]],
176
+ [:end_text]])
177
+ end
178
+
179
+ it "works with a fixed font size" do
180
+ @widget.marker_style(style: :check, color: 0.5, size: 5)
181
+ execute
182
+ assert_operators(@xform.stream,
183
+ [[:set_font_and_size, [:F1, 5]],
184
+ [:set_device_gray_non_stroking_color, [0.5]],
185
+ [:begin_text],
186
+ [:set_text_matrix, [1, 0, 0, 1, 2.885, 8.2725]],
187
+ [:show_text, ["4"]],
188
+ [:end_text]])
189
+ end
190
+ end
191
+ end
192
+
193
+ describe "button fields" do
194
+ before do
195
+ @field = @doc.add({FT: :Btn}, type: :XXAcroFormField, subtype: :Btn)
196
+ end
197
+
198
+ describe "check box" do
199
+ before do
200
+ @widget = @field.create_widget(@page, Rect: [0, 0, 0, 0])
201
+ @generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
202
+ @field.field_value = :Off
203
+ end
204
+
205
+ it "updates the widgets' /AS entry to point to the selected appearance" do
206
+ @generator.create_appearances
207
+ assert_equal(@field[:V], @widget[:AS])
208
+ end
209
+
210
+ it "set the print flag on the widgets" do
211
+ @generator.create_appearances
212
+ assert(@widget.flagged?(:print))
213
+ end
214
+
215
+ it "adjusts the /Rect if width is zero" do
216
+ @generator.create_appearances
217
+ assert_equal(12, @widget[:Rect].width)
218
+ end
219
+
220
+ it "adjusts the /Rect if height is zero" do
221
+ @generator.create_appearances
222
+ assert_equal(12, @widget[:Rect].height)
223
+ end
224
+
225
+ it "creates the needed appearance streams" do
226
+ @generator.create_appearances
227
+ assert_equal(:XObject, @widget[:AP][:N][:Off].type)
228
+ assert_equal(:XObject, @widget[:AP][:N][:Yes].type)
229
+ end
230
+
231
+ it "creates the /Off appearance stream" do
232
+ @generator.create_appearances
233
+ assert_operators(@widget[:AP][:N][:Off].stream,
234
+ [[:save_graphics_state],
235
+ [:set_device_gray_non_stroking_color, [1.0]],
236
+ [:append_rectangle, [0, 0, 12, 12]],
237
+ [:fill_path_non_zero],
238
+ [:append_rectangle, [0.5, 0.5, 11, 11]],
239
+ [:stroke_path], [:restore_graphics_state]])
240
+ end
241
+
242
+ it "creates the /Yes appearance stream" do
243
+ @generator.create_appearances
244
+ assert_operators(@widget[:AP][:N][:Yes].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
+
252
+ [:save_graphics_state],
253
+ [:set_font_and_size, [:F1, 10]],
254
+ [:begin_text],
255
+ [:set_text_matrix, [1, 0, 0, 1, 1.77, 2.545]],
256
+ [:show_text, ["4"]],
257
+ [:end_text],
258
+ [:restore_graphics_state]])
259
+ end
260
+
261
+ it "fails if the appearance dictionaries are not set up" do
262
+ @widget[:AP][:N].delete(:Off)
263
+ assert_raises(HexaPDF::Error) { @generator.create_appearances }
264
+ end
265
+ end
266
+
267
+ describe "radio button" do
268
+ before do
269
+ @field.initialize_as_radio_button
270
+ @widget = @field.create_widget(@page, Rect: [0, 0, 0, 0], value: :radio)
271
+ @generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
272
+ end
273
+
274
+ it "updates the widgets' /AS entry to point to the selected appearance" do
275
+ @field.field_value = :radio
276
+ @generator.create_appearances
277
+ assert_equal(@field[:V], @widget[:AS])
278
+
279
+ @field.create_widget(@page, value: :other)
280
+ @field.field_value = :other
281
+ @generator.create_appearances
282
+ assert_equal(:Off, @widget[:AS])
283
+ end
284
+
285
+ it "set the print flag on the widgets" do
286
+ @generator.create_appearances
287
+ assert(@widget.flagged?(:print))
288
+ end
289
+
290
+ it "adjusts the /Rect if width is zero" do
291
+ @generator.create_appearances
292
+ assert_equal(12, @widget[:Rect].width)
293
+ end
294
+
295
+ it "adjusts the /Rect if height is zero" do
296
+ @generator.create_appearances
297
+ assert_equal(12, @widget[:Rect].height)
298
+ end
299
+
300
+ it "creates the needed appearance streams" do
301
+ @generator.create_appearances
302
+ assert_equal(:XObject, @widget[:AP][:N][:Off].type)
303
+ assert_equal(:XObject, @widget[:AP][:N][:radio].type)
304
+ end
305
+
306
+ it "creates the /Off appearance stream" do
307
+ @widget.marker_style(style: :cross)
308
+ @generator.create_appearances
309
+ assert_operators(@widget[:AP][:N][:Off].stream,
310
+ [[:save_graphics_state],
311
+ [:set_device_gray_non_stroking_color, [1.0]],
312
+ [:append_rectangle, [0, 0, 12, 12]],
313
+ [:fill_path_non_zero],
314
+ [:append_rectangle, [0.5, 0.5, 11, 11]],
315
+ [:stroke_path], [:restore_graphics_state]])
316
+ end
317
+
318
+ it "creates the appearance stream according to the set value" do
319
+ @widget.marker_style(style: :check)
320
+ @generator.create_appearances
321
+ assert_operators(@widget[:AP][:N][:radio].stream,
322
+ [[:save_graphics_state],
323
+ [:set_device_gray_non_stroking_color, [1.0]],
324
+ [:append_rectangle, [0, 0, 12, 12]],
325
+ [:fill_path_non_zero],
326
+ [:append_rectangle, [0.5, 0.5, 11, 11]],
327
+ [:stroke_path], [:restore_graphics_state],
328
+
329
+ [:save_graphics_state],
330
+ [:set_font_and_size, [:F1, 10]],
331
+ [:begin_text],
332
+ [:set_text_matrix, [1, 0, 0, 1, 1.77, 2.545]],
333
+ [:show_text, ["4"]],
334
+ [:end_text],
335
+ [:restore_graphics_state]])
336
+ end
337
+
338
+ it "fails if the appearance dictionaries are not set up" do
339
+ @widget[:AP][:N].delete(:radio)
340
+ assert_raises(HexaPDF::Error) { @generator.create_appearances }
341
+ end
342
+ end
343
+ end
344
+
345
+ describe "text field" do
346
+ before do
347
+ @form.set_default_appearance_string
348
+ @field = @doc.add({FT: :Tx}, type: :XXAcroFormField, subtype: :Tx)
349
+ @widget = @field.create_widget(@page, Rect: [0, 0, 0, 0])
350
+ @generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(@widget)
351
+ end
352
+
353
+ it "updates the widgets to use the :N appearance stream" do
354
+ @generator.create_appearances
355
+ assert_equal(:N, @widget[:AS])
356
+ end
357
+
358
+ it "set the print flag on the widgets" do
359
+ @generator.create_appearances
360
+ assert(@widget.flagged?(:print))
361
+ end
362
+
363
+ describe "it adjusts the :Rect when necessary" do
364
+ before do
365
+ @widget.border_style(width: 3)
366
+ end
367
+
368
+ it "uses a default width if the width is zero" do
369
+ @generator.create_appearances
370
+ assert_equal(@doc.config['acro_form.text_field.default_width'], @widget[:Rect].width)
371
+ end
372
+
373
+ it "uses the font size of the /DA if non-zero as basis for the height if it is zero" do
374
+ @field.set_default_appearance_string(font_size: 10)
375
+ @generator.create_appearances
376
+ assert_equal(15.25, @widget[:Rect].height)
377
+ end
378
+
379
+ it "uses a default font size as basis for the height if it and the set font size are zero" do
380
+ assert_equal(0, @field.parse_default_appearance_string[1])
381
+ @generator.create_appearances
382
+ assert_equal(15.25, @widget[:Rect].height)
383
+ end
384
+ end
385
+
386
+ it "adds an appropriate form XObject" do
387
+ @generator.create_appearances
388
+ form = @widget[:AP][:N]
389
+ assert_equal(:XObject, form.type)
390
+ assert_equal(:Form, form[:Subtype])
391
+ assert_equal([0, 0, @widget[:Rect].width, @widget[:Rect].height], form[:BBox])
392
+ assert_equal(@doc.acro_form.default_resources[:Font][:F1], form[:Resources][:Font][:F1])
393
+ end
394
+
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
402
+
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
410
+
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
422
+ end
423
+
424
+ describe "quadding e.g. text alignment" do
425
+ before do
426
+ @field.field_value = 'Test'
427
+ @field.set_default_appearance_string(font_size: 10)
428
+ @widget[:Rect].height = 20
429
+ end
430
+
431
+ it "works for left aligned text" do
432
+ @field.text_alignment(:left)
433
+ @generator.create_appearances
434
+ assert_operators(@widget[:AP][:N].stream,
435
+ [:set_text_matrix, [1, 0, 0, 1, 2, 6.41]],
436
+ range: 7)
437
+ end
438
+
439
+ it "works for right aligned text" do
440
+ @field.text_alignment(:right)
441
+ @generator.create_appearances
442
+ assert_operators(@widget[:AP][:N].stream,
443
+ [:set_text_matrix, [1, 0, 0, 1, 78.55, 6.41]],
444
+ range: 7)
445
+ end
446
+
447
+ it "works for center aligned text" do
448
+ @field.text_alignment(:center)
449
+ @generator.create_appearances
450
+ assert_operators(@widget[:AP][:N].stream,
451
+ [:set_text_matrix, [1, 0, 0, 1, 40.275, 6.41]],
452
+ range: 7)
453
+ end
454
+
455
+ it "vertically aligns to the font descender if the text is too high" do
456
+ @widget[:Rect].height = 5
457
+ @generator.create_appearances
458
+ assert_operators(@widget[:AP][:N].stream,
459
+ [:set_text_matrix, [1, 0, 0, 1, 2, 3.07]],
460
+ range: 7)
461
+ end
462
+ end
463
+
464
+ it "creates the /N appearance stream according to the set string" do
465
+ @field.field_value = 'Text'
466
+ @generator.create_appearances
467
+ assert_operators(@widget[:AP][:N].stream,
468
+ [[:begin_marked_content, [:Tx]],
469
+ [:save_graphics_state],
470
+ [:append_rectangle, [1, 1, 98, 9.25]],
471
+ [:clip_path_non_zero],
472
+ [:end_path],
473
+ [:set_font_and_size, [:F1, 6.641436]],
474
+ [:begin_text],
475
+ [:set_text_matrix, [1, 0, 0, 1, 2, 3.240724]],
476
+ [:show_text, ["Text"]],
477
+ [:end_text],
478
+ [:restore_graphics_state],
479
+ [:end_marked_content]])
480
+ end
481
+
482
+ end
483
+
484
+ describe "choice fields" do
485
+ it "works for combo boxes by using the text appearance method" do
486
+ @form.set_default_appearance_string
487
+ field = @doc.add({FT: :Ch}, type: :XXAcroFormField, subtype: :Ch)
488
+ field.initialize_as_combo_box
489
+ widget = field.create_widget(@page, Rect: [0, 0, 0, 0])
490
+ generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(widget)
491
+ generator.create_appearances
492
+ assert_kind_of(HexaPDF::Type::Form, widget[:AP][:N])
493
+ end
494
+ end
495
+
496
+ describe "font resolution in case the referenced font is not usable" do
497
+ before do
498
+ def (@form.default_resources.font(:F1)).font_wrapper; nil; end
499
+ @field.field_value = 'Test'
500
+ end
501
+
502
+ it "uses the fallback font if configured" do
503
+ @doc.config['acro_form.fallback_font'] = ['Times', variant: :none]
504
+ @generator.create_appearances
505
+ assert_equal(:'Times-Roman', @widget[:AP][:N][:Resources][:Font][:F2][:BaseFont])
506
+ end
507
+
508
+ it "fails if fallback fonts are disabled" do
509
+ @doc.config['acro_form.fallback_font'] = nil
510
+ msg = assert_raises(HexaPDF::Error) { @generator.create_appearances }
511
+ assert_match(/Font.*not usable/, msg.message)
512
+ end
513
+ end
514
+ end
515
+ end