hexapdf 0.11.9 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (270) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +157 -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 +22 -11
  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 +3 -2
  30. data/lib/hexapdf/cli/images.rb +1 -1
  31. data/lib/hexapdf/cli/info.rb +52 -3
  32. data/lib/hexapdf/cli/inspect.rb +31 -9
  33. data/lib/hexapdf/cli/merge.rb +2 -2
  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 +81 -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 +4 -4
  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 +5 -5
  55. data/lib/hexapdf/dictionary_fields.rb +2 -10
  56. data/lib/hexapdf/document.rb +45 -17
  57. data/lib/hexapdf/document/files.rb +1 -2
  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 +3 -14
  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 +2 -2
  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 +2 -1
  70. data/lib/hexapdf/encryption/standard_security_handler.rb +2 -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 +2 -5
  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 +9 -1
  86. data/lib/hexapdf/font/encoding/difference_encoding.rb +7 -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 +2 -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 +3 -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 +68 -52
  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 +4 -3
  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 +2 -2
  137. data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
  138. data/lib/hexapdf/layout/style.rb +24 -24
  139. data/lib/hexapdf/layout/text_box.rb +1 -1
  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 +4 -3
  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 +32 -27
  147. data/lib/hexapdf/parser.rb +69 -6
  148. data/lib/hexapdf/pdf_array.rb +10 -3
  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 +30 -22
  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 +7 -5
  158. data/lib/hexapdf/tokenizer.rb +5 -4
  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 +405 -0
  162. data/lib/hexapdf/type/acro_form/button_field.rb +305 -0
  163. data/lib/hexapdf/type/acro_form/choice_field.rb +220 -0
  164. data/lib/hexapdf/type/acro_form/field.rb +250 -17
  165. data/lib/hexapdf/type/acro_form/form.rb +159 -7
  166. data/lib/hexapdf/type/acro_form/text_field.rb +187 -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 +4 -3
  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 +238 -2
  180. data/lib/hexapdf/type/catalog.rb +23 -3
  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 +2 -2
  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 +4 -2
  187. data/lib/hexapdf/type/font_true_type.rb +7 -3
  188. data/lib/hexapdf/type/font_type0.rb +2 -2
  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 +12 -2
  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 +5 -3
  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 +36 -12
  199. data/lib/hexapdf/type/page_tree_node.rb +37 -16
  200. data/lib/hexapdf/type/resources.rb +17 -3
  201. data/lib/hexapdf/type/trailer.rb +4 -6
  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 +19 -16
  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/common_tokenizer_tests.rb +6 -1
  216. data/test/hexapdf/content/common.rb +2 -2
  217. data/test/hexapdf/content/graphic_object/test_arc.rb +4 -4
  218. data/test/hexapdf/content/test_canvas.rb +3 -3
  219. data/test/hexapdf/content/test_color_space.rb +71 -8
  220. data/test/hexapdf/content/test_operator.rb +22 -22
  221. data/test/hexapdf/content/test_parser.rb +14 -0
  222. data/test/hexapdf/document/test_fonts.rb +1 -1
  223. data/test/hexapdf/document/test_pages.rb +6 -6
  224. data/test/hexapdf/encryption/test_aes.rb +4 -4
  225. data/test/hexapdf/encryption/test_standard_security_handler.rb +11 -11
  226. data/test/hexapdf/filter/test_ascii85_decode.rb +1 -1
  227. data/test/hexapdf/filter/test_ascii_hex_decode.rb +1 -1
  228. data/test/hexapdf/font/encoding/test_base.rb +10 -0
  229. data/test/hexapdf/font/encoding/test_difference_encoding.rb +8 -0
  230. data/test/hexapdf/font/test_true_type_wrapper.rb +10 -7
  231. data/test/hexapdf/font/test_type1_wrapper.rb +33 -8
  232. data/test/hexapdf/layout/test_style.rb +1 -1
  233. data/test/hexapdf/layout/test_text_layouter.rb +3 -4
  234. data/test/hexapdf/test_configuration.rb +2 -2
  235. data/test/hexapdf/test_dictionary.rb +3 -1
  236. data/test/hexapdf/test_dictionary_fields.rb +2 -2
  237. data/test/hexapdf/test_document.rb +16 -4
  238. data/test/hexapdf/test_object.rb +44 -26
  239. data/test/hexapdf/test_parser.rb +125 -55
  240. data/test/hexapdf/test_pdf_array.rb +7 -0
  241. data/test/hexapdf/test_rectangle.rb +14 -0
  242. data/test/hexapdf/test_revision.rb +3 -0
  243. data/test/hexapdf/test_revisions.rb +35 -0
  244. data/test/hexapdf/test_writer.rb +2 -2
  245. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +521 -0
  246. data/test/hexapdf/type/acro_form/test_button_field.rb +281 -0
  247. data/test/hexapdf/type/acro_form/test_choice_field.rb +137 -0
  248. data/test/hexapdf/type/acro_form/test_field.rb +163 -6
  249. data/test/hexapdf/type/acro_form/test_form.rb +189 -22
  250. data/test/hexapdf/type/acro_form/test_text_field.rb +121 -0
  251. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +77 -0
  252. data/test/hexapdf/type/annotations/test_text.rb +1 -1
  253. data/test/hexapdf/type/annotations/test_widget.rb +199 -0
  254. data/test/hexapdf/type/test_annotation.rb +45 -0
  255. data/test/hexapdf/type/test_catalog.rb +18 -0
  256. data/test/hexapdf/type/test_font.rb +5 -0
  257. data/test/hexapdf/type/test_font_simple.rb +2 -1
  258. data/test/hexapdf/type/test_font_true_type.rb +6 -0
  259. data/test/hexapdf/type/test_font_type1.rb +8 -0
  260. data/test/hexapdf/type/test_form.rb +19 -1
  261. data/test/hexapdf/type/test_image.rb +7 -0
  262. data/test/hexapdf/type/test_page.rb +45 -7
  263. data/test/hexapdf/type/test_page_tree_node.rb +62 -12
  264. data/test/hexapdf/type/test_resources.rb +20 -0
  265. data/test/hexapdf/type/test_trailer.rb +4 -0
  266. data/test/hexapdf/utils/test_bit_field.rb +15 -1
  267. data/test/hexapdf/utils/test_sorted_tree_node.rb +10 -9
  268. data/test/test_helper.rb +1 -1
  269. metadata +34 -21
  270. data/lib/hexapdf/filter/dct_decode.rb +0 -60
@@ -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
 
@@ -158,4 +158,39 @@ describe HexaPDF::Revisions do
158
158
  doc = HexaPDF::Document.new(io: io)
159
159
  assert_equal(2, doc.revisions.count)
160
160
  end
161
+
162
+ it "uses the reconstructed revision if errors are found when loading from an IO" do
163
+ io = StringIO.new(<<~EOF)
164
+ %PDF-1.7
165
+ 1 0 obj
166
+ 10
167
+ endobj
168
+
169
+ xref
170
+ 0 2
171
+ 0000000000 65535 f
172
+ 0000000009 00000 n
173
+ trailer
174
+ << /Size 5 >>
175
+ startxref
176
+ 28
177
+ %%EOF
178
+
179
+ 2 0 obj
180
+ 300
181
+ endobj
182
+
183
+ xref
184
+ 2 1
185
+ 0000000301 00000 n
186
+ trailer
187
+ << /Size 3 /Prev 100>>
188
+ startxref
189
+ 139
190
+ %%EOF
191
+ EOF
192
+ doc = HexaPDF::Document.new(io: io)
193
+ assert_equal(2, doc.revisions.count)
194
+ assert_same(doc.revisions[0].trailer.value, doc.revisions[1].trailer.value)
195
+ end
161
196
  end
@@ -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.9)>>
43
+ <</Producer(HexaPDF version 0.13.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.9)>>
75
+ <</Producer(HexaPDF version 0.13.0)>>
76
76
  endobj
77
77
  2 0 obj
78
78
  <</Length 10>>stream
@@ -0,0 +1,521 @@
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
+ end
482
+
483
+ describe "choice fields" do
484
+ it "works for combo boxes by using the text appearance method" do
485
+ @form.set_default_appearance_string
486
+ field = @doc.add({FT: :Ch}, type: :XXAcroFormField, subtype: :Ch)
487
+ field.initialize_as_combo_box
488
+ widget = field.create_widget(@page, Rect: [0, 0, 0, 0])
489
+ generator = HexaPDF::Type::AcroForm::AppearanceGenerator.new(widget)
490
+ generator.create_appearances
491
+ assert_kind_of(HexaPDF::Type::Form, widget[:AP][:N])
492
+ end
493
+ end
494
+
495
+ describe "font resolution in case the referenced font is not usable" do
496
+ before do
497
+ @doc.config['acro_form.fallback_font'] = ['Times', {variant: :none}]
498
+ @field[:V] = 'Test'
499
+ end
500
+
501
+ it "uses the fallback font if the font is not usable" do
502
+ def (@form.default_resources.font(:F1)).font_wrapper; nil; end
503
+ @generator.create_appearances
504
+ assert_equal(:'Times-Roman', @widget[:AP][:N][:Resources][:Font][:F2][:BaseFont])
505
+ end
506
+
507
+ it "uses the fallback font if the font is not found" do
508
+ @form.default_resources[:Font].delete(:F1)
509
+ @generator.create_appearances
510
+ assert_equal(:'Times-Roman', @widget[:AP][:N][:Resources][:Font][:F1][:BaseFont])
511
+ end
512
+
513
+ it "fails if fallback fonts are disabled" do
514
+ @doc.config['acro_form.fallback_font'] = nil
515
+ @form.default_resources[:Font].delete(:F1)
516
+ msg = assert_raises(HexaPDF::Error) { @generator.create_appearances }
517
+ assert_match(/Font.*not usable/, msg.message)
518
+ end
519
+ end
520
+ end
521
+ end