hexapdf 0.46.0 → 1.6.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 (355) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +342 -16
  3. data/LICENSE +1 -1
  4. data/README.md +9 -8
  5. data/examples/009-text_layouter_alignment.rb +4 -0
  6. data/examples/010-text_layouter_inline_boxes.rb +4 -0
  7. data/examples/011-text_layouter_line_wrapping.rb +4 -0
  8. data/examples/012-text_layouter_styling.rb +9 -4
  9. data/examples/013-text_layouter_shapes.rb +5 -0
  10. data/examples/015-boxes.rb +3 -0
  11. data/examples/016-frame_automatic_box_placement.rb +3 -0
  12. data/examples/017-frame_text_flow.rb +3 -0
  13. data/examples/022-outline.rb +5 -1
  14. data/examples/{028-frame_mask_mode.rb → 028-composer_mask_mode.rb} +3 -3
  15. data/lib/hexapdf/cli/batch.rb +1 -1
  16. data/lib/hexapdf/cli/command.rb +65 -65
  17. data/lib/hexapdf/cli/debug_info.rb +98 -0
  18. data/lib/hexapdf/cli/files.rb +1 -1
  19. data/lib/hexapdf/cli/fonts.rb +1 -1
  20. data/lib/hexapdf/cli/form.rb +11 -6
  21. data/lib/hexapdf/cli/image2pdf.rb +1 -1
  22. data/lib/hexapdf/cli/images.rb +19 -4
  23. data/lib/hexapdf/cli/info.rb +1 -1
  24. data/lib/hexapdf/cli/inspect.rb +24 -8
  25. data/lib/hexapdf/cli/merge.rb +1 -1
  26. data/lib/hexapdf/cli/modify.rb +1 -2
  27. data/lib/hexapdf/cli/optimize.rb +6 -6
  28. data/lib/hexapdf/cli/split.rb +1 -1
  29. data/lib/hexapdf/cli/usage.rb +1 -1
  30. data/lib/hexapdf/cli/watermark.rb +1 -1
  31. data/lib/hexapdf/cli.rb +20 -2
  32. data/lib/hexapdf/composer.rb +22 -1
  33. data/lib/hexapdf/configuration.rb +56 -1
  34. data/lib/hexapdf/content/canvas.rb +1 -1
  35. data/lib/hexapdf/content/canvas_composer.rb +1 -1
  36. data/lib/hexapdf/content/color_space.rb +1 -1
  37. data/lib/hexapdf/content/graphic_object/arc.rb +1 -1
  38. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
  39. data/lib/hexapdf/content/graphic_object/geom2d.rb +1 -1
  40. data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
  41. data/lib/hexapdf/content/graphic_object.rb +1 -1
  42. data/lib/hexapdf/content/graphics_state.rb +2 -2
  43. data/lib/hexapdf/content/operator.rb +1 -1
  44. data/lib/hexapdf/content/parser.rb +22 -23
  45. data/lib/hexapdf/content/processor.rb +1 -1
  46. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  47. data/lib/hexapdf/content.rb +1 -1
  48. data/lib/hexapdf/data_dir.rb +1 -1
  49. data/lib/hexapdf/dictionary.rb +8 -2
  50. data/lib/hexapdf/dictionary_fields.rb +2 -2
  51. data/lib/hexapdf/digital_signature/cms_handler.rb +19 -2
  52. data/lib/hexapdf/digital_signature/handler.rb +1 -1
  53. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +1 -1
  54. data/lib/hexapdf/digital_signature/signature.rb +2 -2
  55. data/lib/hexapdf/digital_signature/signatures.rb +1 -1
  56. data/lib/hexapdf/digital_signature/signing/default_handler.rb +3 -3
  57. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +2 -2
  58. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +25 -5
  59. data/lib/hexapdf/digital_signature/signing.rb +1 -1
  60. data/lib/hexapdf/digital_signature/verification_result.rb +1 -1
  61. data/lib/hexapdf/digital_signature.rb +1 -1
  62. data/lib/hexapdf/document/annotations.rb +220 -0
  63. data/lib/hexapdf/document/destinations.rb +1 -1
  64. data/lib/hexapdf/document/files.rb +1 -1
  65. data/lib/hexapdf/document/fonts.rb +1 -1
  66. data/lib/hexapdf/document/images.rb +1 -1
  67. data/lib/hexapdf/document/layout.rb +95 -16
  68. data/lib/hexapdf/document/metadata.rb +11 -4
  69. data/lib/hexapdf/document/pages.rb +1 -1
  70. data/lib/hexapdf/document.rb +52 -9
  71. data/lib/hexapdf/encryption/aes.rb +1 -1
  72. data/lib/hexapdf/encryption/arc4.rb +3 -3
  73. data/lib/hexapdf/encryption/fast_aes.rb +1 -1
  74. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  75. data/lib/hexapdf/encryption/identity.rb +1 -1
  76. data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
  77. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  78. data/lib/hexapdf/encryption/security_handler.rb +4 -2
  79. data/lib/hexapdf/encryption/standard_security_handler.rb +40 -29
  80. data/lib/hexapdf/encryption.rb +1 -1
  81. data/lib/hexapdf/error.rb +12 -4
  82. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  83. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  84. data/lib/hexapdf/filter/crypt.rb +1 -1
  85. data/lib/hexapdf/filter/encryption.rb +1 -1
  86. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  87. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  88. data/lib/hexapdf/filter/pass_through.rb +1 -1
  89. data/lib/hexapdf/filter/predictor.rb +1 -1
  90. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  91. data/lib/hexapdf/filter.rb +1 -1
  92. data/lib/hexapdf/font/cmap/parser.rb +1 -1
  93. data/lib/hexapdf/font/cmap/writer.rb +59 -5
  94. data/lib/hexapdf/font/cmap.rb +18 -7
  95. data/lib/hexapdf/font/encoding/base.rb +28 -1
  96. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  97. data/lib/hexapdf/font/encoding/glyph_list.rb +1 -1
  98. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  99. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
  100. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  101. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  102. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +1 -1
  103. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  104. data/lib/hexapdf/font/encoding.rb +1 -1
  105. data/lib/hexapdf/font/invalid_glyph.rb +1 -1
  106. data/lib/hexapdf/font/true_type/builder.rb +1 -1
  107. data/lib/hexapdf/font/true_type/font.rb +1 -1
  108. data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
  109. data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
  110. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  111. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
  112. data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
  113. data/lib/hexapdf/font/true_type/table/glyf.rb +1 -1
  114. data/lib/hexapdf/font/true_type/table/head.rb +1 -1
  115. data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
  116. data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
  117. data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
  118. data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
  119. data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
  120. data/lib/hexapdf/font/true_type/table/name.rb +1 -1
  121. data/lib/hexapdf/font/true_type/table/os2.rb +1 -1
  122. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  123. data/lib/hexapdf/font/true_type/table.rb +7 -2
  124. data/lib/hexapdf/font/true_type.rb +1 -1
  125. data/lib/hexapdf/font/true_type_wrapper.rb +51 -16
  126. data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
  127. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  128. data/lib/hexapdf/font/type1/font.rb +1 -1
  129. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  130. data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
  131. data/lib/hexapdf/font/type1.rb +1 -1
  132. data/lib/hexapdf/font/type1_wrapper.rb +3 -4
  133. data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
  134. data/lib/hexapdf/font_loader/from_file.rb +1 -1
  135. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  136. data/lib/hexapdf/font_loader/variant_from_name.rb +1 -1
  137. data/lib/hexapdf/font_loader.rb +1 -1
  138. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  139. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  140. data/lib/hexapdf/image_loader/png.rb +1 -1
  141. data/lib/hexapdf/image_loader.rb +1 -1
  142. data/lib/hexapdf/importer.rb +2 -2
  143. data/lib/hexapdf/layout/box.rb +6 -1
  144. data/lib/hexapdf/layout/box_fitter.rb +1 -1
  145. data/lib/hexapdf/layout/column_box.rb +1 -1
  146. data/lib/hexapdf/layout/container_box.rb +64 -29
  147. data/lib/hexapdf/layout/frame.rb +1 -1
  148. data/lib/hexapdf/layout/image_box.rb +1 -1
  149. data/lib/hexapdf/layout/inline_box.rb +1 -1
  150. data/lib/hexapdf/layout/line.rb +1 -1
  151. data/lib/hexapdf/layout/list_box.rb +1 -1
  152. data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
  153. data/lib/hexapdf/layout/page_style.rb +1 -1
  154. data/lib/hexapdf/layout/style.rb +133 -22
  155. data/lib/hexapdf/layout/table_box.rb +86 -14
  156. data/lib/hexapdf/layout/text_box.rb +1 -1
  157. data/lib/hexapdf/layout/text_fragment.rb +13 -2
  158. data/lib/hexapdf/layout/text_layouter.rb +1 -1
  159. data/lib/hexapdf/layout/text_shaper.rb +1 -1
  160. data/lib/hexapdf/layout/width_from_polygon.rb +1 -1
  161. data/lib/hexapdf/layout.rb +1 -1
  162. data/lib/hexapdf/name_tree_node.rb +1 -1
  163. data/lib/hexapdf/number_tree_node.rb +1 -1
  164. data/lib/hexapdf/object.rb +4 -4
  165. data/lib/hexapdf/parser.rb +36 -7
  166. data/lib/hexapdf/pdf_array.rb +26 -4
  167. data/lib/hexapdf/rectangle.rb +1 -1
  168. data/lib/hexapdf/reference.rb +2 -2
  169. data/lib/hexapdf/revision.rb +7 -3
  170. data/lib/hexapdf/revisions.rb +1 -1
  171. data/lib/hexapdf/serializer.rb +8 -8
  172. data/lib/hexapdf/stream.rb +1 -1
  173. data/lib/hexapdf/task/dereference.rb +1 -1
  174. data/lib/hexapdf/task/merge_acro_form.rb +164 -0
  175. data/lib/hexapdf/task/optimize.rb +5 -5
  176. data/lib/hexapdf/task/pdfa.rb +1 -1
  177. data/lib/hexapdf/task.rb +2 -1
  178. data/lib/hexapdf/test_utils.rb +3 -2
  179. data/lib/hexapdf/tokenizer.rb +52 -44
  180. data/lib/hexapdf/type/acro_form/appearance_generator.rb +66 -13
  181. data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
  182. data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
  183. data/lib/hexapdf/type/acro_form/field.rb +6 -2
  184. data/lib/hexapdf/type/acro_form/form.rb +23 -32
  185. data/lib/hexapdf/type/acro_form/java_script_actions.rb +10 -3
  186. data/lib/hexapdf/type/acro_form/signature_field.rb +19 -8
  187. data/lib/hexapdf/type/acro_form/text_field.rb +10 -3
  188. data/lib/hexapdf/type/acro_form/variable_text_field.rb +13 -5
  189. data/lib/hexapdf/type/acro_form.rb +1 -1
  190. data/lib/hexapdf/type/action.rb +1 -1
  191. data/lib/hexapdf/type/actions/go_to.rb +2 -1
  192. data/lib/hexapdf/type/actions/go_to_r.rb +2 -1
  193. data/lib/hexapdf/type/actions/launch.rb +6 -2
  194. data/lib/hexapdf/type/actions/set_ocg_state.rb +1 -1
  195. data/lib/hexapdf/type/actions/uri.rb +1 -1
  196. data/lib/hexapdf/type/actions.rb +1 -1
  197. data/lib/hexapdf/type/annotation.rb +78 -3
  198. data/lib/hexapdf/type/annotations/appearance_generator.rb +426 -0
  199. data/lib/hexapdf/type/annotations/border_effect.rb +99 -0
  200. data/lib/hexapdf/type/annotations/border_styling.rb +160 -0
  201. data/lib/hexapdf/type/annotations/circle.rb +65 -0
  202. data/lib/hexapdf/type/annotations/interior_color.rb +84 -0
  203. data/lib/hexapdf/type/annotations/line.rb +334 -0
  204. data/lib/hexapdf/type/annotations/line_ending_styling.rb +208 -0
  205. data/lib/hexapdf/type/annotations/link.rb +1 -1
  206. data/lib/hexapdf/type/annotations/markup_annotation.rb +15 -3
  207. data/lib/hexapdf/type/annotations/polygon.rb +64 -0
  208. data/lib/hexapdf/type/annotations/polygon_polyline.rb +109 -0
  209. data/lib/hexapdf/type/annotations/polyline.rb +64 -0
  210. data/lib/hexapdf/type/annotations/square.rb +65 -0
  211. data/lib/hexapdf/type/annotations/square_circle.rb +77 -0
  212. data/lib/hexapdf/type/annotations/text.rb +1 -1
  213. data/lib/hexapdf/type/annotations/widget.rb +56 -118
  214. data/lib/hexapdf/type/annotations.rb +13 -1
  215. data/lib/hexapdf/type/catalog.rb +5 -2
  216. data/lib/hexapdf/type/cid_font.rb +6 -3
  217. data/lib/hexapdf/type/cmap.rb +58 -0
  218. data/lib/hexapdf/type/embedded_file.rb +1 -1
  219. data/lib/hexapdf/type/file_specification.rb +18 -15
  220. data/lib/hexapdf/type/font.rb +1 -1
  221. data/lib/hexapdf/type/font_descriptor.rb +5 -4
  222. data/lib/hexapdf/type/font_simple.rb +4 -2
  223. data/lib/hexapdf/type/font_true_type.rb +3 -1
  224. data/lib/hexapdf/type/font_type0.rb +2 -2
  225. data/lib/hexapdf/type/font_type1.rb +19 -1
  226. data/lib/hexapdf/type/font_type3.rb +1 -2
  227. data/lib/hexapdf/type/form.rb +8 -5
  228. data/lib/hexapdf/type/graphics_state_parameter.rb +8 -5
  229. data/lib/hexapdf/type/icon_fit.rb +1 -1
  230. data/lib/hexapdf/type/image.rb +9 -5
  231. data/lib/hexapdf/type/info.rb +3 -3
  232. data/lib/hexapdf/type/mark_information.rb +3 -3
  233. data/lib/hexapdf/type/marked_content_reference.rb +59 -0
  234. data/lib/hexapdf/type/measure.rb +57 -0
  235. data/lib/hexapdf/type/metadata.rb +1 -1
  236. data/lib/hexapdf/type/names.rb +1 -1
  237. data/lib/hexapdf/type/namespace.rb +57 -0
  238. data/lib/hexapdf/type/object_reference.rb +57 -0
  239. data/lib/hexapdf/type/object_stream.rb +1 -1
  240. data/lib/hexapdf/type/optional_content_configuration.rb +2 -2
  241. data/lib/hexapdf/type/optional_content_group.rb +1 -1
  242. data/lib/hexapdf/type/optional_content_membership.rb +2 -2
  243. data/lib/hexapdf/type/optional_content_properties.rb +1 -1
  244. data/lib/hexapdf/type/outline.rb +1 -1
  245. data/lib/hexapdf/type/outline_item.rb +1 -1
  246. data/lib/hexapdf/type/output_intent.rb +1 -1
  247. data/lib/hexapdf/type/page.rb +6 -4
  248. data/lib/hexapdf/type/page_label.rb +1 -1
  249. data/lib/hexapdf/type/page_tree_node.rb +1 -1
  250. data/lib/hexapdf/type/resources.rb +13 -9
  251. data/lib/hexapdf/type/struct_elem.rb +72 -0
  252. data/lib/hexapdf/type/struct_tree_root.rb +64 -0
  253. data/lib/hexapdf/type/trailer.rb +1 -1
  254. data/lib/hexapdf/type/viewer_preferences.rb +5 -4
  255. data/lib/hexapdf/type/xref_stream.rb +1 -1
  256. data/lib/hexapdf/type.rb +8 -1
  257. data/lib/hexapdf/utils/bit_field.rb +1 -1
  258. data/lib/hexapdf/utils/bit_stream.rb +1 -1
  259. data/lib/hexapdf/utils/graphics_helpers.rb +1 -1
  260. data/lib/hexapdf/utils/lru_cache.rb +1 -1
  261. data/lib/hexapdf/utils/math_helpers.rb +1 -1
  262. data/lib/hexapdf/utils/object_hash.rb +1 -1
  263. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  264. data/lib/hexapdf/utils/sorted_tree_node.rb +17 -4
  265. data/lib/hexapdf/utils.rb +1 -1
  266. data/lib/hexapdf/version.rb +2 -2
  267. data/lib/hexapdf/writer.rb +3 -2
  268. data/lib/hexapdf/xref_section.rb +25 -6
  269. data/lib/hexapdf.rb +1 -1
  270. data/test/data/standard-security-handler/bothpwd-aes-256bit-V5-R5.pdf +43 -0
  271. data/test/data/standard-security-handler/nopwd-aes-256bit-V5-R5.pdf +44 -0
  272. data/test/data/standard-security-handler/ownerpwd-aes-256bit-V5-R5.pdf +43 -0
  273. data/test/data/standard-security-handler/userpwd-aes-256bit-V5-R5.pdf +0 -0
  274. data/test/hexapdf/common_tokenizer_tests.rb +7 -7
  275. data/test/hexapdf/content/test_graphics_state.rb +2 -3
  276. data/test/hexapdf/content/test_operator.rb +4 -5
  277. data/test/hexapdf/digital_signature/common.rb +6 -1
  278. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +6 -1
  279. data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +12 -0
  280. data/test/hexapdf/digital_signature/test_cms_handler.rb +25 -15
  281. data/test/hexapdf/digital_signature/test_handler.rb +2 -3
  282. data/test/hexapdf/digital_signature/test_pkcs1_handler.rb +1 -2
  283. data/test/hexapdf/digital_signature/test_signature.rb +7 -0
  284. data/test/hexapdf/digital_signature/test_signatures.rb +12 -7
  285. data/test/hexapdf/document/test_annotations.rb +75 -0
  286. data/test/hexapdf/document/test_layout.rb +38 -10
  287. data/test/hexapdf/document/test_metadata.rb +13 -1
  288. data/test/hexapdf/encryption/common.rb +1 -1
  289. data/test/hexapdf/encryption/test_aes.rb +1 -1
  290. data/test/hexapdf/encryption/test_arc4.rb +2 -2
  291. data/test/hexapdf/encryption/test_security_handler.rb +8 -6
  292. data/test/hexapdf/encryption/test_standard_security_handler.rb +7 -3
  293. data/test/hexapdf/filter/test_ascii85_decode.rb +1 -1
  294. data/test/hexapdf/filter/test_ascii_hex_decode.rb +1 -1
  295. data/test/hexapdf/filter/test_flate_decode.rb +2 -3
  296. data/test/hexapdf/font/cmap/test_writer.rb +73 -16
  297. data/test/hexapdf/font/encoding/test_base.rb +20 -0
  298. data/test/hexapdf/font/encoding/test_glyph_list.rb +1 -1
  299. data/test/hexapdf/font/test_true_type_wrapper.rb +31 -5
  300. data/test/hexapdf/font/test_type1_wrapper.rb +8 -1
  301. data/test/hexapdf/font/true_type/test_table.rb +12 -0
  302. data/test/hexapdf/layout/test_box.rb +8 -2
  303. data/test/hexapdf/layout/test_container_box.rb +34 -6
  304. data/test/hexapdf/layout/test_list_box.rb +7 -7
  305. data/test/hexapdf/layout/test_page_style.rb +1 -1
  306. data/test/hexapdf/layout/test_style.rb +46 -12
  307. data/test/hexapdf/layout/test_table_box.rb +66 -16
  308. data/test/hexapdf/layout/test_text_box.rb +0 -6
  309. data/test/hexapdf/layout/test_text_fragment.rb +3 -3
  310. data/test/hexapdf/layout/test_text_layouter.rb +4 -2
  311. data/test/hexapdf/task/test_merge_acro_form.rb +104 -0
  312. data/test/hexapdf/task/test_optimize.rb +3 -1
  313. data/test/hexapdf/test_composer.rb +15 -0
  314. data/test/hexapdf/test_dictionary.rb +15 -0
  315. data/test/hexapdf/test_dictionary_fields.rb +1 -0
  316. data/test/hexapdf/test_document.rb +26 -8
  317. data/test/hexapdf/test_filter.rb +1 -1
  318. data/test/hexapdf/test_importer.rb +7 -0
  319. data/test/hexapdf/test_object.rb +1 -1
  320. data/test/hexapdf/test_parser.rb +87 -18
  321. data/test/hexapdf/test_pdf_array.rb +36 -3
  322. data/test/hexapdf/test_revision.rb +27 -6
  323. data/test/hexapdf/test_revisions.rb +1 -1
  324. data/test/hexapdf/test_serializer.rb +4 -4
  325. data/test/hexapdf/test_stream.rb +1 -2
  326. data/test/hexapdf/test_tokenizer.rb +1 -1
  327. data/test/hexapdf/test_writer.rb +22 -8
  328. data/test/hexapdf/test_xref_section.rb +15 -0
  329. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +118 -26
  330. data/test/hexapdf/type/acro_form/test_button_field.rb +7 -6
  331. data/test/hexapdf/type/acro_form/test_field.rb +10 -0
  332. data/test/hexapdf/type/acro_form/test_form.rb +32 -9
  333. data/test/hexapdf/type/acro_form/test_java_script_actions.rb +21 -0
  334. data/test/hexapdf/type/acro_form/test_signature_field.rb +3 -1
  335. data/test/hexapdf/type/acro_form/test_text_field.rb +7 -1
  336. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +14 -1
  337. data/test/hexapdf/type/actions/test_launch.rb +6 -2
  338. data/test/hexapdf/type/annotations/test_appearance_generator.rb +608 -0
  339. data/test/hexapdf/type/annotations/test_border_effect.rb +59 -0
  340. data/test/hexapdf/type/annotations/test_border_styling.rb +114 -0
  341. data/test/hexapdf/type/annotations/test_interior_color.rb +37 -0
  342. data/test/hexapdf/type/annotations/test_line.rb +144 -0
  343. data/test/hexapdf/type/annotations/test_line_ending_styling.rb +42 -0
  344. data/test/hexapdf/type/annotations/test_polygon_polyline.rb +29 -0
  345. data/test/hexapdf/type/annotations/test_widget.rb +47 -81
  346. data/test/hexapdf/type/test_annotation.rb +58 -0
  347. data/test/hexapdf/type/test_font_type1.rb +20 -1
  348. data/test/hexapdf/type/test_form.rb +7 -1
  349. data/test/hexapdf/type/test_image.rb +1 -1
  350. data/test/hexapdf/type/test_page.rb +7 -1
  351. data/test/hexapdf/type/test_page_tree_node.rb +2 -2
  352. data/test/hexapdf/type/test_resources.rb +3 -1
  353. data/test/hexapdf/utils/test_sorted_tree_node.rb +18 -7
  354. data/test/test_helper.rb +7 -0
  355. metadata +69 -9
@@ -0,0 +1,160 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2025 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'hexapdf/type/annotation'
38
+ require 'hexapdf/content'
39
+ require 'hexapdf/serializer'
40
+
41
+ module HexaPDF
42
+ module Type
43
+ module Annotations
44
+
45
+ # This module provides a convenience method for getting and setting the border style and is
46
+ # included in the annotations that need it.
47
+ #
48
+ # See: PDF2.0 s12.5.4
49
+ module BorderStyling
50
+
51
+ # Describes the border of an annotation.
52
+ #
53
+ # The +color+ property is either +nil+ if the border is transparent or else a device color
54
+ # object - see HexaPDF::Content::ColorSpace.
55
+ #
56
+ # The +style+ property can be one of the following:
57
+ #
58
+ # :solid:: Solid line.
59
+ # :beveled:: Embossed rectangle seemingly raised above the surface of the page.
60
+ # :inset:: Engraved rectangle receeding into the page.
61
+ # :underlined:: Underlined, i.e. only the bottom border is draw.
62
+ # Array: Dash array describing how to dash the line.
63
+ BorderStyle = Struct.new(:width, :color, :style, :horizontal_corner_radius,
64
+ :vertical_corner_radius)
65
+
66
+ # :call-seq:
67
+ # annot.border_style => border_style
68
+ # annot.border_style(color: 0, width: 1, style: :solid) => annot
69
+ #
70
+ # Returns a BorderStyle instance representing the border style of the annotation when no
71
+ # argument is given. Otherwise sets the border style of the annotation and returns self.
72
+ #
73
+ # When setting a border style, arguments that are not provided will use the default: a
74
+ # border with a solid, black, 1pt wide line. This also means that multiple invocations will
75
+ # reset *all* prior values.
76
+ #
77
+ # +color+:: The color of the border. See
78
+ # HexaPDF::Content::ColorSpace.device_color_from_specification for information on
79
+ # the allowed arguments.
80
+ #
81
+ # If the special value +:transparent+ is used when setting the color, a
82
+ # transparent is used. A transparent border will return a +nil+ value when getting
83
+ # the border color.
84
+ #
85
+ # +width+:: The width of the border. If set to 0, no border is shown.
86
+ #
87
+ # +style+:: Defines how the border is drawn. can be one of the following:
88
+ #
89
+ # +:solid+:: Draws a solid border.
90
+ # +:beveled+:: Draws a beveled border.
91
+ # +:inset+:: Draws an inset border.
92
+ # +:underlined+:: Draws only the bottom border.
93
+ # Array:: An array specifying a line dash pattern (see
94
+ # HexaPDF::Content::LineDashPattern)
95
+ def border_style(color: nil, width: nil, style: nil)
96
+ if color || width || style
97
+ color = if color == :transparent
98
+ []
99
+ else
100
+ Content::ColorSpace.device_color_from_specification(color || 0).components
101
+ end
102
+ width ||= 1
103
+ style ||= :solid
104
+
105
+ if self[:Subtype] == :Widget
106
+ (self[:MK] ||= {})[:BC] = color
107
+ else
108
+ self[:C] = color
109
+ end
110
+ bs = self[:BS] = {W: width}
111
+ case style
112
+ when :solid then bs[:S] = :S
113
+ when :beveled then bs[:S] = :B
114
+ when :inset then bs[:S] = :I
115
+ when :underlined then bs[:S] = :U
116
+ when Array
117
+ bs[:S] = :D
118
+ bs[:D] = style
119
+ else
120
+ raise ArgumentError, "Unknown value #{style} for style argument"
121
+ end
122
+ self
123
+ else
124
+ result = BorderStyle.new(1, nil, :solid, 0, 0)
125
+ bc = if self[:Subtype] == :Widget
126
+ (ac = self[:MK]) && (bc = ac[:BC])
127
+ else
128
+ self[:C]
129
+ end
130
+ if bc && !bc.empty?
131
+ result.color = Content::ColorSpace.prenormalized_device_color(bc.value)
132
+ end
133
+
134
+ if (bs = self[:BS])
135
+ result.width = bs[:W] if bs.key?(:W)
136
+ result.style = case bs[:S]
137
+ when :S then :solid
138
+ when :B then :beveled
139
+ when :I then :inset
140
+ when :U then :underlined
141
+ when :D then bs[:D].value
142
+ else :solid
143
+ end
144
+ elsif key?(:Border)
145
+ border = self[:Border]
146
+ result.horizontal_corner_radius = border[0]
147
+ result.vertical_corner_radius = border[1]
148
+ result.width = border[2]
149
+ result.style = border[3] if border[3]
150
+ end
151
+
152
+ result
153
+ end
154
+ end
155
+
156
+ end
157
+
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,65 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2025 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'hexapdf/type/annotations'
38
+
39
+ module HexaPDF
40
+ module Type
41
+ module Annotations
42
+
43
+ # A circle annotation displays an ellipse inside the annotation rectangle (the "circle" name
44
+ # defined by the PDF specification is a bit misleading).
45
+ #
46
+ # Also see SquareCircle for more information.
47
+ #
48
+ # Example:
49
+ #
50
+ # #>pdf-small
51
+ # doc.annotations.create_ellipse(doc.pages[0], 50, 50, a: 30, b: 20).
52
+ # border_style(color: "hp-blue", width: 2, style: [3, 1]).
53
+ # interior_color("hp-orange").
54
+ # regenerate_appearance
55
+ #
56
+ # See: PDF2.0 s12.5.6.8, HexaPDF::Type::Annotations::SquareCircle,
57
+ class Circle < SquareCircle
58
+
59
+ define_field :Subtype, type: Symbol, required: true, default: :Circle
60
+
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,84 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2025 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'hexapdf/type/annotations'
38
+
39
+ module HexaPDF
40
+ module Type
41
+ module Annotations
42
+
43
+ # This module provides a convenience method for getting and setting the interior color for
44
+ # various annotations.
45
+ #
46
+ # See: PDF2.0 s12.5
47
+ module InteriorColor
48
+
49
+ # :call-seq:
50
+ # line.interior_color => color or nil
51
+ # line.interior_color(*color) => line
52
+ #
53
+ # Returns the interior color or +nil+ (in case the interior color should be transparent)
54
+ # when no argument is given. Otherwise sets the interior color and returns self.
55
+ #
56
+ # How the interior color is used depends on the concrete annotation type. For line
57
+ # annotations, for example, it is the color to fill the line endings
58
+ #
59
+ # +color+:: The interior color. See
60
+ # HexaPDF::Content::ColorSpace.device_color_from_specification for information on
61
+ # the allowed arguments.
62
+ #
63
+ # If the special value +:transparent+ is used when setting the color, no color is
64
+ # used for filling.
65
+ def interior_color(*color)
66
+ if color.empty?
67
+ color = self[:IC]
68
+ color && !color.empty? ? Content::ColorSpace.prenormalized_device_color(color.value) : nil
69
+ else
70
+ color = if color.length == 1 && color.first == :transparent
71
+ []
72
+ else
73
+ Content::ColorSpace.device_color_from_specification(color).components
74
+ end
75
+ self[:IC] = color
76
+ self
77
+ end
78
+ end
79
+
80
+ end
81
+
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,334 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2025 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'hexapdf/type/annotation'
38
+
39
+ module HexaPDF
40
+ module Type
41
+ module Annotations
42
+
43
+ # A line annotation is a markup annotation that displays a single straight line.
44
+ #
45
+ # The style of the line annotation, like adding leader lines, changing the colors and so on,
46
+ # can be customized using the provided convenience methods and those from the included
47
+ # modules.
48
+ #
49
+ # Note that while BorderStyling#border_style allows special styling of the line (like
50
+ # :beveled), only a simple line dash pattern is supported by the line annotation.
51
+ #
52
+ # Example:
53
+ #
54
+ # #>pdf-small
55
+ # doc.annotations.create_line(doc.pages[0], start_point: [30, 20], end_point: [90, 60]).
56
+ # border_style(color: "hp-blue", width: 2, style: [3, 1]).
57
+ # leader_line_length(15).
58
+ # leader_line_extension_length(10).
59
+ # leader_line_offset(5).
60
+ # interior_color("hp-orange").
61
+ # line_ending_style(start_style: :circle, end_style: :open_arrow).
62
+ # captioned(true).
63
+ # contents("Caption").
64
+ # caption_position(:top).
65
+ # caption_offset(0, 5).
66
+ # regenerate_appearance
67
+ # canvas.line(30, 20, 90, 60).stroke
68
+ #
69
+ # See: PDF2.0 s12.5.6.7, HexaPDF::Type::MarkupAnnotation
70
+ class Line < MarkupAnnotation
71
+
72
+ include BorderStyling
73
+ include InteriorColor
74
+ include LineEndingStyling
75
+
76
+ define_field :Subtype, type: Symbol, required: true, default: :Line
77
+ define_field :L, type: PDFArray, required: true
78
+ define_field :BS, type: :Border
79
+ define_field :LE, type: PDFArray, default: [:None, :None], version: '1.4'
80
+ define_field :IC, type: PDFArray, version: '1.4'
81
+ define_field :LL, type: Numeric, default: 0, version: '1.6'
82
+ define_field :LLE, type: Numeric, default: 0, version: '1.6'
83
+ define_field :Cap, type: Boolean, default: false, version: '1.6'
84
+ define_field :IT, type: Symbol, version: '1.6',
85
+ allowed_values: [:LineArrow, :LineDimension]
86
+ define_field :LLO, type: Numeric, version: '1.7'
87
+ define_field :CP, type: Symbol, default: :Inline, version: '1.7',
88
+ allowed_values: [:Inline, :Top]
89
+ define_field :Measure, type: Dictionary, version: '1.7'
90
+ define_field :CO, type: PDFArray, default: [0, 0], version: '1.7'
91
+
92
+ # :call-seq:
93
+ # line.line => [x0, y0, x1, y1]
94
+ # line.line(x0, y0, x1, y1) => line
95
+ #
96
+ # Returns the start point and end point of the line as an array of four numbers [x0, y0, x1,
97
+ # y1] when no argument is given. Otherwise sets the start and end point of the line and
98
+ # returns self.
99
+ #
100
+ # This is the only required setting for a line annotation. Note, however, that without
101
+ # setting an appropriate color through #border_style the line will be transparent.
102
+ #
103
+ # Example:
104
+ #
105
+ # #>pdf-small
106
+ # doc.annotations.
107
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
108
+ # regenerate_appearance
109
+ def line(x0 = nil, y0 = nil, x1 = nil, y1 = nil)
110
+ if x0.nil? && y0.nil? && x1.nil? && y1.nil?
111
+ self[:L].to_ary
112
+ elsif !x0 || !y0 || !x1 || !y1
113
+ raise ArgumentError, "All four arguments x0, y0, x1, y1 must be provided"
114
+ else
115
+ self[:L] = [x0, y0, x1, y1]
116
+ self
117
+ end
118
+ end
119
+
120
+ # :call-seq:
121
+ # line.leader_line_length => leader_line_length
122
+ # line.leader_line_length(length) => line
123
+ #
124
+ # Returns the leader line length when no argument is given. Otherwise sets the leader line
125
+ # length and returns self.
126
+ #
127
+ # Leader lines extend from the line's end points perpendicular to the line. If the length
128
+ # value is positive, the leader lines appear in the clockwise direction, otherwise in the
129
+ # opposite direction.
130
+ #
131
+ # Note: The "line's end points" mean the actually drawn line and not the one specified with
132
+ # #line as those two are different when leader lines are involved.
133
+ #
134
+ # A value of zero means that no leader lines are used.
135
+ #
136
+ # Example:
137
+ #
138
+ # #>pdf-small
139
+ # doc.annotations.
140
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
141
+ # leader_line_length(15).
142
+ # regenerate_appearance
143
+ # canvas.stroke_color("hp-orange").line(20, 20, 80, 60).stroke
144
+ #
145
+ # Also see: #leader_line_extension_length, #leader_line_offset
146
+ def leader_line_length(length = nil)
147
+ length ? (self[:LL] = length; self) : self[:LL]
148
+ end
149
+
150
+ # :call-seq:
151
+ # line.leader_line_extension_length => leader_line_extension_length
152
+ # line.leader_line_extension_length(length) => line
153
+ #
154
+ # Returns the leader line extension length when no argument is given. Otherwise sets the
155
+ # leader line extension length and returns self.
156
+ #
157
+ # Leader line extensions extend from the line into the opposite direction of the leader
158
+ # lines.
159
+ #
160
+ # The argument +length+ must be non-negative.
161
+ #
162
+ # If the leader line extension length is set to a positive value, the leader line length
163
+ # also needs to be specified.
164
+ #
165
+ # Example:
166
+ #
167
+ # #>pdf-small
168
+ # doc.annotations.
169
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
170
+ # leader_line_length(15).
171
+ # leader_line_extension_length(5).
172
+ # regenerate_appearance
173
+ # canvas.stroke_color("hp-orange").line(20, 20, 80, 60).stroke
174
+ #
175
+ # Also see: #leader_line_length, #leader_line_offset
176
+ def leader_line_extension_length(length = nil)
177
+ if length
178
+ raise ArgumentError, "length must be non-negative" if length < 0
179
+ self[:LLE] = length
180
+ self
181
+ else
182
+ self[:LLE]
183
+ end
184
+ end
185
+
186
+ # :call-seq:
187
+ # line.leader_line_offset => leader_line_offset
188
+ # line.leader_line_offset(number) => line
189
+ #
190
+ # Returns the leader line offset when no argument is given. Otherwise sets the leader line
191
+ # offset and returns self.
192
+ #
193
+ # The leader line offset is a non-negative number that describes the offset of the leader
194
+ # lines from the endpoints of the line.
195
+ #
196
+ # Example:
197
+ #
198
+ # #>pdf-small
199
+ # doc.annotations.
200
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
201
+ # leader_line_length(15).
202
+ # leader_line_offset(5).
203
+ # regenerate_appearance
204
+ # canvas.stroke_color("hp-orange").line(20, 20, 80, 60).stroke
205
+ #
206
+ # Also see: #leader_line_length, #leader_line_extension_length
207
+ def leader_line_offset(offset = nil)
208
+ offset ? (self[:LLO] = offset; self) : self[:LLO] || 0
209
+ end
210
+
211
+ # :call-seq:
212
+ # line.captioned => true or false
213
+ # line.captioned(value) => line
214
+ #
215
+ # Returns +true+ (if the line has a visible caption) or +false+ (no visible caption) when no
216
+ # argument is given. Otherwise sets whether a caption should be visible and returns self.
217
+ #
218
+ # If a caption should be shown, the text specified by the /Contents or /RC entries is shown
219
+ # in the appearance of the line.
220
+ #
221
+ # Example:
222
+ #
223
+ # #>pdf-small-hide
224
+ # doc.annotations.
225
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
226
+ # contents("Inline text").
227
+ # captioned(true).
228
+ # regenerate_appearance
229
+ # Also see: #caption_position, #caption_offset
230
+ def captioned(value = nil)
231
+ value ? (self[:Cap] = value; self) : self[:Cap]
232
+ end
233
+
234
+ # Maps HexaPDF names to PDF names.
235
+ CAPTION_POSITION_MAP = { # :nodoc:
236
+ Inline: :Inline, inline: :Inline,
237
+ Top: :Top, top: :Top,
238
+ }.freeze
239
+ CAPTION_POSITION_REVERSE_MAP = CAPTION_POSITION_MAP.invert # :nodoc:
240
+
241
+ # :call-seq:
242
+ # line.caption_position => caption_position
243
+ # line.caption_position(value) => line
244
+ #
245
+ # Returns the caption position when no argument is given. Otherwise sets the caption
246
+ # position and returns self.
247
+ #
248
+ # Possible caption positions are (the first one is the HexaPDF name, the second the PDF
249
+ # name):
250
+ #
251
+ # :inline or :Inline::
252
+ # The caption is centered inside the line (default).
253
+ #
254
+ # #>pdf-small-hide
255
+ # doc.annotations.
256
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
257
+ # contents("Inline text").
258
+ # captioned(true).
259
+ # caption_position(:inline).
260
+ # regenerate_appearance
261
+ #
262
+ # :top or :Top::
263
+ # The caption is on the top of the line.
264
+ #
265
+ # #>pdf-small-hide
266
+ # doc.annotations.
267
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
268
+ # contents("Top text").
269
+ # captioned(true).
270
+ # caption_position(:top).
271
+ # regenerate_appearance
272
+ #
273
+ # Also see: #captioned, #caption_offset
274
+ def caption_position(value = nil)
275
+ if value
276
+ value = CAPTION_POSITION_MAP.fetch(value) do
277
+ raise ArgumentError, "Invalid caption position: #{value.inspect}"
278
+ end
279
+ self[:CP] = value
280
+ self
281
+ else
282
+ CAPTION_POSITION_REVERSE_MAP[self[:CP]]
283
+ end
284
+ end
285
+
286
+ # :call-seq:
287
+ # line.caption_offset => caption_offset
288
+ # line.caption_offset(x, y) => line
289
+ #
290
+ # Returns the caption offset when no argument is given. Otherwise sets the caption offset
291
+ # and returns self.
292
+ #
293
+ # The caption offset is an array of two numbers that specify the horizontal and vertical
294
+ # offsets of the caption from its normal position. A positive horizontal offset means moving
295
+ # the caption to the right. A positive vertical offset means shifting the caption up.
296
+ #
297
+ # Example:
298
+ #
299
+ # #>pdf-small-hide
300
+ # doc.annotations.
301
+ # create_line(doc.pages[0], start_point: [20, 20], end_point: [80, 60]).
302
+ # contents("Top text").
303
+ # captioned(true).
304
+ # caption_position(:top).
305
+ # caption_offset(20, 10).
306
+ # regenerate_appearance
307
+ #
308
+ # Also see: #captioned, #caption_position
309
+ def caption_offset(x = nil, y = nil)
310
+ x || y ? (self[:CO] = [x || 0, y || 0]; self) : self[:CO].to_ary
311
+ end
312
+
313
+ private
314
+
315
+ def perform_validation #:nodoc:
316
+ super
317
+ if self[:LLE] < 0
318
+ yield('/LLE must be a non-negative number', true)
319
+ self[:LLE] = -self[:LLE]
320
+ end
321
+ if key?(:LLO) && self[:LLO] < 0
322
+ yield('/LLO must be a non-negative number', true)
323
+ self[:LLO] = -self[:LLO]
324
+ end
325
+ if self[:LLE] > 0 && self[:LL] == 0
326
+ yield("/LL required to be non-zero if /LLE is set")
327
+ end
328
+ end
329
+
330
+ end
331
+
332
+ end
333
+ end
334
+ end