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
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2024 Thomas Leitner
7
+ # Copyright (C) 2014-2025 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -150,14 +150,15 @@ module HexaPDF
150
150
  end
151
151
 
152
152
  # :call-seq:
153
- # quad.set(value)
154
- # quad.set(array)
155
- # quad.set(quad)
153
+ # quad.set(value) -> quad
154
+ # quad.set(array) -> quad
155
+ # quad.set(hash) -> quad
156
+ # quad.set(quad) -> quad
156
157
  #
157
- # Sets all values of the quad.
158
+ # Sets all values of the quad and returns it.
158
159
  #
159
- # * If a single value is provided that is neither a Quad nor an array, it is handled as if
160
- # an array with one value was given.
160
+ # * If a single value is provided that is neither a Quad nor an array nor a hash, it is
161
+ # handled as if an array with one value was given.
161
162
  #
162
163
  # * If a Quad is provided, its values are used.
163
164
  #
@@ -170,6 +171,9 @@ module HexaPDF
170
171
  # third value.
171
172
  # * Four or more values: Top is set to the first, right to the second, bottom to the third
172
173
  # and left to the fourth value.
174
+ #
175
+ # * If a hash is provided, the keys +:top+, +:bottom+, +:left+ and +:right+ are used to set
176
+ # the respective value. All unspecified keys that have not been set before are set to 0.
173
177
  def set(obj)
174
178
  case obj
175
179
  when Quad
@@ -182,9 +186,15 @@ module HexaPDF
182
186
  @bottom = obj[2] || obj[0]
183
187
  @left = obj[3] || obj[1] || obj[0]
184
188
  @right = obj[1] || obj[0]
189
+ when Hash
190
+ @top = obj[:top] || @top || 0
191
+ @bottom = obj[:bottom] || @bottom || 0
192
+ @left = obj[:left] || @left || 0
193
+ @right = obj[:right] || @right || 0
185
194
  else
186
195
  @top = @bottom = @left = @right = obj
187
196
  end
197
+ self
188
198
  end
189
199
 
190
200
  # Returns +true+ if the quad effectively contains only one value.
@@ -265,7 +275,9 @@ module HexaPDF
265
275
  miter_limit(10).
266
276
  line_cap_style(line_cap_style(:top))
267
277
 
268
- canvas.rectangle(x, y, w, h).clip_path.end_path
278
+ if width.top > w || width.top > h
279
+ canvas.rectangle(x, y, w, h).clip_path.end_path
280
+ end
269
281
  if style.top == :solid
270
282
  canvas.line_dash_pattern(0).
271
283
  rectangle(x + offset, y + offset, w - 2 * offset, h - 2 * offset).stroke
@@ -396,6 +408,9 @@ module HexaPDF
396
408
  # bottom-left corner of the box during the drawing operations.
397
409
  class Layers
398
410
 
411
+ # The array holding all raw layer definitions.
412
+ attr_reader :layers
413
+
399
414
  # Creates a new Layers object popuplated with the given +layers+.
400
415
  def initialize(layers = nil)
401
416
  @layers = []
@@ -601,11 +616,36 @@ module HexaPDF
601
616
  # style.update(**properties) -> style
602
617
  #
603
618
  # Updates the style's properties using the key-value pairs specified by the +properties+ hash.
619
+ #
620
+ # Also see: #merge
604
621
  def update(**properties)
605
622
  properties.each {|key, value| send(key, value) }
606
623
  self
607
624
  end
608
625
 
626
+ # Yields all set properties.
627
+ def each_property # :yield: property, value
628
+ return to_enum(__method__) unless block_given?
629
+ instance_variables.each do |iv|
630
+ (val = PROPERTIES[iv]) && yield(val, instance_variable_get(iv))
631
+ end
632
+ end
633
+
634
+ # :call-seq:
635
+ # style.merge(other_style) -> style
636
+ #
637
+ # Merges the set properties of the +other_style+ object into this one.
638
+ #
639
+ # Note that merging is done on a per-property basis. So if a complex property is set on
640
+ # +other_style+ and also on +self+, the +other_style+ value completely overwrites the one from
641
+ # +self+.
642
+ #
643
+ # Also see: #update
644
+ def merge(other)
645
+ other.each_property {|property, value| send(property, value) }
646
+ self
647
+ end
648
+
609
649
  ##
610
650
  # :method: font
611
651
  # :call-seq:
@@ -613,8 +653,9 @@ module HexaPDF
613
653
  #
614
654
  # The font to be used, must be set to a valid font wrapper object before it can be used.
615
655
  #
616
- # HexaPDF::Composer handles this property specially in that it resolves a set string or array
617
- # to a font wrapper object before doing anything else with the style object.
656
+ # HexaPDF::Document::Layout handles this property - together with #font_bold and #font_italic
657
+ # - specially in that it resolves a set string or array to a font wrapper object before doing
658
+ # anything else with the style object.
618
659
  #
619
660
  # This is the only style property without a default value!
620
661
  #
@@ -631,6 +672,48 @@ module HexaPDF
631
672
  # composer.text("Courier Bold", font: "Courier bold")
632
673
  # composer.text("Courier Bold also", font: ["Courier", variant: :bold])
633
674
 
675
+ ##
676
+ # :method: font_bold
677
+ # :call-seq:
678
+ # font_bold(bold = false)
679
+ #
680
+ # Specifies whether the bold variant of the font is used.
681
+ #
682
+ # Note that this property only has affect if #font is not already set to a font wrapper
683
+ # object and if it is set explicitly (i.e. #font_bold? returns +true+).
684
+ #
685
+ # See #font, #font_italic
686
+ #
687
+ # Examples:
688
+ #
689
+ # #>pdf-composer100
690
+ # composer.text("Helvetica bold", font: "Helvetica", font_bold: true)
691
+ #
692
+ # helvetica_bold = composer.document.fonts.add("Helvetica", variant: :bold)
693
+ # composer.text("Helvetica bold", font: helvetica_bold, font_bold: false)
694
+ # composer.text("Helvetica", font: ["Helvetica", {variant: :bold}], font_bold: false)
695
+
696
+ ##
697
+ # :method: font_italic
698
+ # :call-seq:
699
+ # font_italic(bold = false)
700
+ #
701
+ # Specifies whether the italic variant of the font is used.
702
+ #
703
+ # Note that this property only has affect if #font is not already set to a font wrapper
704
+ # object and if it is set explicitly (i.e. #font_italic? returns +true+).
705
+ #
706
+ # See #font, #font_bold.
707
+ #
708
+ # Examples:
709
+ #
710
+ # #>pdf-composer100
711
+ # composer.text("Helvetica italic", font: "Helvetica", font_italic: true)
712
+ #
713
+ # helvetica_bold = composer.document.fonts.add("Helvetica", variant: :italic)
714
+ # composer.text("Helvetica italic", font: helvetica_bold, font_italic: false)
715
+ # composer.text("Helvetica", font: ["Helvetica", {variant: :italic}], font_italic: false)
716
+
634
717
  ##
635
718
  # :method: font_size
636
719
  # :call-seq:
@@ -1019,7 +1102,7 @@ module HexaPDF
1019
1102
  #
1020
1103
  # This method can set the line spacing in two ways:
1021
1104
  #
1022
- # * Using two positional arguments +type+ and +value+.
1105
+ # * Using the positional, mandatory argument +type+ and the optional +value+.
1023
1106
  # * Or a hash with the keys +type+ and +value+.
1024
1107
  #
1025
1108
  # Note that the last line has no additional spacing after it by default. Set #last_line_gap
@@ -1420,8 +1503,33 @@ module HexaPDF
1420
1503
  # composer.text("This is some longer text that does not appear in two lines.",
1421
1504
  # height: 15, overflow: :truncate)
1422
1505
 
1423
- [
1506
+ ##
1507
+ # :method: box_options
1508
+ # :call-seq:
1509
+ # box_options(**options)
1510
+ #
1511
+ # Contains initialization arguments for the box instance that is created with this
1512
+ # style. Together with the other style properties this allows the complete specification of a
1513
+ # box instance just via a Style instance.
1514
+ #
1515
+ # Note that this property is only used by the HexaPDF::Document::Layout methods when a box
1516
+ # instance is created. If a box instance is created directly, this property has no effect.
1517
+ #
1518
+ # Examples:
1519
+ #
1520
+ # #>pdf-composer100
1521
+ # composer.style(:my_list, box_options: {marker_type: :decimal, item_spacing: 15})
1522
+ # composer.list(style: :my_list) do |list|
1523
+ # list.text("This is some text.")
1524
+ # list.text("This is some other text.")
1525
+ # end
1526
+
1527
+
1528
+ # :nodoc:
1529
+ PROPERTIES = [
1424
1530
  [:font, "raise HexaPDF::Error, 'No font set'"],
1531
+ [:font_bold, false],
1532
+ [:font_italic, false],
1425
1533
  [:font_size, 10],
1426
1534
  [:line_height, nil],
1427
1535
  [:character_spacing, 0],
@@ -1432,10 +1540,10 @@ module HexaPDF
1432
1540
  [:text_rendering_mode, "Content::TextRenderingMode::FILL",
1433
1541
  {setter: "Content::TextRenderingMode.normalize(value)"}],
1434
1542
  [:subscript, false,
1435
- {setter: "value; superscript(false) if superscript",
1543
+ {setter: "value; superscript(false) if value && superscript? && superscript",
1436
1544
  valid_values: [true, false]}],
1437
1545
  [:superscript, false,
1438
- {setter: "value; subscript(false) if subscript",
1546
+ {setter: "value; subscript(false) if value && subscript? && subscript",
1439
1547
  valid_values: [true, false]}],
1440
1548
  [:underline, false, {valid_values: [true, false]}],
1441
1549
  [:strikeout, false, {valid_values: [true, false]}],
@@ -1455,15 +1563,17 @@ module HexaPDF
1455
1563
  [:text_valign, :top, {valid_values: [:top, :center, :bottom]}],
1456
1564
  [:text_indent, 0],
1457
1565
  [:line_spacing, "LineSpacing.new(type: :single)",
1458
- {setter: "LineSpacing.new(**(value.kind_of?(Symbol) || value.kind_of?(Numeric) ? " \
1459
- "{type: value, value: extra_arg} : value))",
1566
+ {setter: "LineSpacing.new(**(value.kind_of?(Symbol) || value.kind_of?(Numeric) || " \
1567
+ "value.kind_of?(LineSpacing) ? {type: value, value: extra_arg} : value))",
1460
1568
  extra_args: ", extra_arg = nil"}],
1461
1569
  [:last_line_gap, false, {valid_values: [true, false]}],
1462
1570
  [:fill_horizontal, nil],
1463
1571
  [:background_color, nil],
1464
1572
  [:background_alpha, 1],
1465
- [:padding, "Quad.new(0)", {setter: "Quad.new(value)"}],
1466
- [:margin, "Quad.new(0)", {setter: "Quad.new(value)"}],
1573
+ [:padding, "Quad.new(0)",
1574
+ {setter: "value.kind_of?(Hash) && @name ? @name.set(value) : Quad.new(value)"}],
1575
+ [:margin, "Quad.new(0)",
1576
+ {setter: "value.kind_of?(Hash) && @name ? @name.set(value) : Quad.new(value)"}],
1467
1577
  [:border, "Border.new", {setter: "Border.new(**value)"}],
1468
1578
  [:overlays, "Layers.new", {setter: "Layers.new(value)"}],
1469
1579
  [:underlays, "Layers.new", {setter: "Layers.new(value)"}],
@@ -1473,6 +1583,7 @@ module HexaPDF
1473
1583
  [:mask_mode, :default, {valid_values: [:default, :none, :box, :fill_horizontal,
1474
1584
  :fill_frame_horizontal, :fill_vertical, :fill]}],
1475
1585
  [:overflow, :error],
1586
+ [:box_options, {}],
1476
1587
  ].each do |name, default, options = {}|
1477
1588
  default = default.inspect unless default.kind_of?(String)
1478
1589
  setter = options.delete(:setter) || "value"
@@ -1498,7 +1609,7 @@ module HexaPDF
1498
1609
  end
1499
1610
  EOF
1500
1611
  alias_method("#{name}=", name)
1501
- end
1612
+ end.each_with_object({}) {|arr, hash| hash[:"@#{arr.first}"] = arr.first }
1502
1613
 
1503
1614
  ##
1504
1615
  # :method: text_segmentation_algorithm
@@ -1545,9 +1656,9 @@ module HexaPDF
1545
1656
 
1546
1657
  # The calculated text rise, taking superscript and subscript into account.
1547
1658
  def calculated_text_rise
1548
- if superscript
1659
+ if superscript? && superscript
1549
1660
  text_rise + font_size * 0.33
1550
- elsif subscript
1661
+ elsif subscript? && subscript
1551
1662
  text_rise - font_size * 0.20
1552
1663
  else
1553
1664
  text_rise
@@ -1556,7 +1667,7 @@ module HexaPDF
1556
1667
 
1557
1668
  # The calculated font size, taking superscript and subscript into account.
1558
1669
  def calculated_font_size
1559
- (superscript || subscript ? 0.583 : 1) * font_size
1670
+ ((superscript? && superscript) || (subscript? && subscript) ? 0.583 : 1) * font_size
1560
1671
  end
1561
1672
 
1562
1673
  # Returns the correct offset from the baseline for the underline.
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2024 Thomas Leitner
7
+ # Copyright (C) 2014-2025 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -85,6 +85,13 @@ module HexaPDF
85
85
  # #>pdf-composer
86
86
  # composer.table([['A', 'B'], ['C', 'D']])
87
87
  #
88
+ # Each cell can hold zero or more boxes:
89
+ #
90
+ # #>pdf-composer
91
+ # cells = [[[layout.text('A'), layout.image(machu_picchu, height: 40)], layout.text('B')],
92
+ # [nil, layout.text('D')]]
93
+ # composer.table(cells)
94
+ #
88
95
  # The style of the cells can be customized, e.g. to avoid drawing borders:
89
96
  #
90
97
  # #>pdf-composer
@@ -104,7 +111,7 @@ module HexaPDF
104
111
  # It is also possible to use row and column spans:
105
112
  #
106
113
  # #>pdf-composer
107
- # cells = [[{content: layout.text('A'), col_span: 2}, {content: layout.text('B'), row_span: 2}],
114
+ # cells = [[{content: layout.text('A'), col_span: 2}, {content: layout.text("B\nB\nB"), row_span: 2}],
108
115
  # [{content: layout.text('C'), col_span: 2, row_span: 2}],
109
116
  # [layout.text('D')]]
110
117
  # composer.table(cells)
@@ -119,6 +126,15 @@ module HexaPDF
119
126
  # [layout.text('E'), layout.text('F')]]
120
127
  # composer.column(height: 90) {|col| col.table(cells, header: header, footer: footer) }
121
128
  #
129
+ # While the width of a cell is determined by the #column_widths array, the height is
130
+ # automatically determined during fitting of the content. However, it is also possible to use a
131
+ # fixed height (only if the actual content is smaller or equal than it):
132
+ #
133
+ # #>pdf-composer
134
+ # cells = [[{content: layout.text('A'), min_height: 5}, layout.text('B')],
135
+ # [{content: layout.text('C'), min_height: 40}, layout.text('D')]]
136
+ # composer.table(cells)
137
+ #
122
138
  # The cells can be styled using a callable object for more complex styling:
123
139
  #
124
140
  # #>pdf-composer
@@ -184,13 +200,14 @@ module HexaPDF
184
200
  attr_accessor :children
185
201
 
186
202
  # Creates a new Cell instance.
187
- def initialize(row:, column:, children: nil, row_span: nil, col_span: nil, **kwargs)
203
+ def initialize(row:, column:, children: nil, min_height: nil, row_span: nil, col_span: nil, **kwargs)
188
204
  super(**kwargs, width: 0, height: 0)
189
205
  @children = children
190
206
  @row = row
191
207
  @column = column
192
208
  @row_span = row_span || 1
193
209
  @col_span = col_span || 1
210
+ @min_height = min_height
194
211
  style.border.width.set(1) unless style.border?
195
212
  style.border.draw_on_bounds = true
196
213
  style.padding.set(5) unless style.padding?
@@ -250,6 +267,11 @@ module HexaPDF
250
267
  @fit_results = []
251
268
  fit_result.success!
252
269
  end
270
+
271
+ if @min_height && @height < @min_height
272
+ @height = @preferred_height = @min_height
273
+ fit_result.failure! if available_height < @height
274
+ end
253
275
  end
254
276
 
255
277
  # Draws the content of the cell.
@@ -291,6 +313,8 @@ module HexaPDF
291
313
  #
292
314
  # +:col_span+:: An integer specifying the number of columsn this cell should span.
293
315
  #
316
+ # +:min_height+:: A number specifying the minimum height of the table cell.
317
+ #
294
318
  # +:properties+:: A hash of properties (see Box#properties) to be set on the cell itself.
295
319
  #
296
320
  # All other key-value pairs are taken to be cell styling information (like
@@ -382,7 +406,14 @@ module HexaPDF
382
406
  def fit_rows(start_row, available_height, column_info, frame)
383
407
  height = available_height
384
408
  last_fitted_row_index = -1
409
+ row_heights = {}
410
+ zero_height_rows = {}
411
+ row_spans = []
412
+
385
413
  @cells[start_row..-1].each.with_index(start_row) do |columns, row_index|
414
+ # 1. Fit all columns of the row and record the max height of all non-row-span cells. If
415
+ # a row has zero height (usually because it only has row-span cells), record that
416
+ # information. Additionally store all cells with row-spans.
386
417
  row_fit = true
387
418
  row_height = 0
388
419
  columns.each_with_index do |cell, col_index|
@@ -396,27 +427,67 @@ module HexaPDF
396
427
  row_fit = false
397
428
  break
398
429
  end
399
- cell.left = column_info[cell.column].first
400
- cell.top = height - available_height
401
- row_height = cell.preferred_height if row_height < cell.preferred_height
430
+ if row_height < cell.preferred_height && cell.row_span == 1
431
+ row_height = cell.preferred_height
432
+ end
433
+ row_spans << cell if cell.row_span > 1
402
434
  end
403
435
 
404
- if row_fit
405
- seen = {}
406
- columns.each do |cell|
407
- next if seen[cell]
408
- cell.update_height(cell.row == row_index ? row_height : cell.height + row_height)
409
- seen[cell] = true
410
- end
436
+ zero_height_rows[row_index] = true if row_height == 0
411
437
 
438
+ if row_fit
439
+ # 2. If all cells of the row fit, we subtract the recorded row height of the
440
+ # non-row-span cells from the available height for the next pass.
412
441
  last_fitted_row_index = row_index
442
+ row_heights[row_index] = row_height
413
443
  available_height -= row_height
444
+
445
+ # 3. We look at all row-span cells that end at the current row index. If the row-span
446
+ # cell is larger than the sum of the row heights, we proportionally enlarge the
447
+ # stored height of each spanned row and subtract the difference from the available
448
+ # height for the next pass. If the row span contains initially zero-height rows,
449
+ # only those rows are enlarged. Row-span cells themselves are not updated at this
450
+ # point!
451
+ row_spans.each do |cell|
452
+ upper_row_index = cell.row + cell.row_span - 1
453
+ next unless upper_row_index == row_index
454
+
455
+ rows = cell.row.upto(upper_row_index)
456
+ row_span_height = rows.sum {|ri| row_heights[ri] }
457
+ if row_span_height < cell.preferred_height
458
+ zero_height_rows_in_span = rows.select {|ri| zero_height_rows[ri] }
459
+ rows = zero_height_rows_in_span if zero_height_rows_in_span.size > 0
460
+ adjustment = (cell.preferred_height - row_span_height) / rows.size.to_f
461
+ rows.each {|ri| row_heights[ri] += adjustment }
462
+ available_height -= cell.preferred_height - row_span_height
463
+ end
464
+ end
414
465
  else
415
466
  last_fitted_row_index = columns.min_by(&:row).row - 1 if height != available_height
416
467
  break
417
468
  end
418
469
  end
419
470
 
471
+ if last_fitted_row_index >= 0
472
+ # 4. Once all possible rows have been fitted and the heights of the rows are fixed, the
473
+ # final height and top-left corner of each cell needs to be set.
474
+ running_height = 0
475
+ @cells[start_row..last_fitted_row_index].each.with_index(start_row) do |columns, row_index|
476
+ columns.each_with_index do |cell, col_index|
477
+ next if cell.row != row_index || cell.column != col_index
478
+ cell.left = column_info[cell.column].first
479
+ cell.top = running_height
480
+ if cell.row_span == 1
481
+ cell.update_height(row_heights[row_index])
482
+ else
483
+ new_height = cell.row.upto(cell.row + cell.row_span - 1).sum {|ri| row_heights[ri] }
484
+ cell.update_height(new_height)
485
+ end
486
+ end
487
+ running_height += row_heights[row_index]
488
+ end
489
+ end
490
+
420
491
  [height - available_height, last_fitted_row_index < start_row ? -1 : last_fitted_row_index]
421
492
  end
422
493
 
@@ -459,11 +530,12 @@ module HexaPDF
459
530
  children = content.delete(:content)
460
531
  row_span = content.delete(:row_span)
461
532
  col_span = content.delete(:col_span)
533
+ min_height = content.delete(:min_height)
462
534
  properties = content.delete(:properties)
463
535
  style = content
464
536
  end
465
537
  cell = Cell.new(children: children, row: row_index, column: col_index,
466
- row_span: row_span, col_span: col_span)
538
+ row_span: row_span, col_span: col_span, min_height: min_height)
467
539
  cell_style_block&.call(cell)
468
540
  cell.style.update(**style) if style
469
541
  cell.properties.update(properties) if properties
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2024 Thomas Leitner
7
+ # Copyright (C) 2014-2025 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2024 Thomas Leitner
7
+ # Copyright (C) 2014-2025 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -55,6 +55,10 @@ module HexaPDF
55
55
  # The rectangle with the bottom-left corner (#x_min, #y_min) and the top-right corner (#x_max,
56
56
  # #y_max) describes the minimum bounding box of the whole text fragment and is usually *not*
57
57
  # equal to the box (0, 0)-(#width, #height).
58
+ #
59
+ # *Note*: This class should not be used directly but via
60
+ # HexaPDF::Document::Layout#text_fragments. This way the whole document layout infrastructure
61
+ # like font fallback and such is automatically used.
58
62
  class TextFragment
59
63
 
60
64
  using NumericRefinements
@@ -64,6 +68,8 @@ module HexaPDF
64
68
  # The needed style of the text fragment is specified by the +style+ argument (see
65
69
  # Style::create for details). Note that the resulting style object needs at least the font
66
70
  # set.
71
+ #
72
+ # For internal use, see the note under TextFragment for details.
67
73
  def self.create(text, style)
68
74
  style = Style.create(style)
69
75
  fragment = new(style.font.decode_utf8(text), style)
@@ -90,6 +96,8 @@ module HexaPDF
90
96
  # The needed style of the text fragments is specified by the +style+ argument (see
91
97
  # Style::create for details). Note that the resulting style object needs at least the font
92
98
  # set.
99
+ #
100
+ # For internal use, see the note under TextFragment for details.
93
101
  def self.create_with_fallback_glyphs(text, style)
94
102
  return [create(text, style)] if !block_given? || text.empty?
95
103
 
@@ -155,6 +163,8 @@ module HexaPDF
155
163
  #
156
164
  # The argument +style+ can either be a Style object or a hash of style properties, see
157
165
  # Style::create for details.
166
+ #
167
+ # For internal use, see the note under TextFragment for details.
158
168
  def initialize(items, style, properties: nil)
159
169
  @items = items
160
170
  @style = Style.create(style)
@@ -235,6 +245,7 @@ module HexaPDF
235
245
  end
236
246
  end
237
247
 
248
+ in_text_object = (canvas.graphics_object == :text)
238
249
  canvas.begin_text
239
250
  tlm = canvas.graphics_state.tlm
240
251
  tx = x - tlm.e
@@ -248,7 +259,7 @@ module HexaPDF
248
259
  elsif ty.abs < PRECISION
249
260
  canvas.move_text_cursor(offset: [tx, 0], absolute: false)
250
261
  else
251
- canvas.move_text_cursor(offset: [x, y])
262
+ canvas.move_text_cursor(offset: [x, y], absolute: in_text_object)
252
263
  end
253
264
  canvas.show_glyphs_only(items)
254
265
 
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2024 Thomas Leitner
7
+ # Copyright (C) 2014-2025 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2024 Thomas Leitner
7
+ # Copyright (C) 2014-2025 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2024 Thomas Leitner
7
+ # Copyright (C) 2014-2025 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2024 Thomas Leitner
7
+ # Copyright (C) 2014-2025 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2024 Thomas Leitner
7
+ # Copyright (C) 2014-2025 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2024 Thomas Leitner
7
+ # Copyright (C) 2014-2025 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -4,7 +4,7 @@
4
4
  # This file is part of HexaPDF.
5
5
  #
6
6
  # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
- # Copyright (C) 2014-2024 Thomas Leitner
7
+ # Copyright (C) 2014-2025 Thomas Leitner
8
8
  #
9
9
  # HexaPDF is free software: you can redistribute it and/or modify it
10
10
  # under the terms of the GNU Affero General Public License version 3 as
@@ -305,8 +305,8 @@ module HexaPDF
305
305
  result
306
306
  rescue HexaPDF::Error
307
307
  raise
308
- rescue StandardError
309
- yield("Error: Unexpected value encountered", false, self) if block_given?
308
+ rescue StandardError => e
309
+ yield("Unexpected error encountered: #{e.message}", false, self) if block_given?
310
310
  false
311
311
  end
312
312
 
@@ -372,7 +372,7 @@ module HexaPDF
372
372
 
373
373
  # Computes the hash value based on the object and generation numbers.
374
374
  def hash
375
- oid.hash ^ gen.hash
375
+ [oid, gen].hash
376
376
  end
377
377
 
378
378
  def inspect #:nodoc: