hexapdf 0.11.9 → 0.13.0

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 (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