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
@@ -35,7 +35,6 @@
35
35
  #++
36
36
 
37
37
  require 'io/console'
38
- require 'ostruct'
39
38
  require 'cmdparse'
40
39
  require 'hexapdf/document'
41
40
  require 'hexapdf/font/true_type'
@@ -53,7 +52,7 @@ module HexaPDF
53
52
  module Extensions #:nodoc:
54
53
  def help_banner #:nodoc:
55
54
  "hexapdf #{HexaPDF::VERSION} - Versatile PDF Manipulation Tool\n" \
56
- "Copyright (c) 2014-2024 Thomas Leitner; licensed under the AGPLv3\n\n" \
55
+ "Copyright (c) 2014-2025 Thomas Leitner; licensed under the AGPLv3\n\n" \
57
56
  "#{format(usage, indent: 7)}\n\n"
58
57
  end
59
58
 
@@ -68,21 +67,22 @@ module HexaPDF
68
67
 
69
68
  def initialize(*args, **kwargs, &block) #:nodoc:
70
69
  super
71
- @out_options = OpenStruct.new
72
- @out_options.compact = true
73
- @out_options.compress_pages = false
74
- @out_options.object_streams = :preserve
75
- @out_options.xref_streams = :preserve
76
- @out_options.streams = :preserve
77
- @out_options.optimize_fonts = false
78
- @out_options.prune_page_resources = false
79
-
80
- @out_options.encryption = :preserve
81
- @out_options.enc_user_pwd = @out_options.enc_owner_pwd = nil
82
- @out_options.enc_key_length = 128
83
- @out_options.enc_algorithm = :aes
84
- @out_options.enc_force_v4 = false
85
- @out_options.enc_permissions = []
70
+ @out_options = {
71
+ compact: true,
72
+ compress_pages: false,
73
+ object_streams: :preserve,
74
+ xref_streams: :preserve,
75
+ streams: :preserve,
76
+ optimize_fonts: false,
77
+ prune_page_resources: false,
78
+ encryption: :preserve,
79
+ enc_user_pwd: nil,
80
+ enc_owner_pwd: nil,
81
+ enc_key_length: 128,
82
+ enc_algorithm: :aes,
83
+ enc_force_v4: false,
84
+ enc_permissions: [],
85
+ }
86
86
  end
87
87
 
88
88
  protected
@@ -163,7 +163,7 @@ module HexaPDF
163
163
  if command_parser.verbosity_info?
164
164
  puts "Creating output document #{out_file}"
165
165
  end
166
- doc.write(out_file, validate: false, incremental: incremental)
166
+ doc.write(out_file, validate: false, compact: false, incremental: incremental)
167
167
  end
168
168
  end
169
169
 
@@ -183,35 +183,35 @@ module HexaPDF
183
183
  options.separator("")
184
184
  options.separator("Optimization options:")
185
185
  options.on("--[no-]compact", "Delete unnecessary PDF objects (default: " \
186
- "#{@out_options.compact})") do |c|
187
- @out_options.compact = c
186
+ "#{@out_options[:compact]})") do |c|
187
+ @out_options[:compact] = c
188
188
  end
189
189
  options.on("--object-streams MODE", [:generate, :preserve, :delete],
190
190
  "Handling of object streams (either generate, preserve or delete; " \
191
- "default: #{@out_options.object_streams})") do |os|
192
- @out_options.object_streams = os
191
+ "default: #{@out_options[:object_streams]})") do |os|
192
+ @out_options[:object_streams] = os
193
193
  end
194
194
  options.on("--xref-streams MODE", [:generate, :preserve, :delete],
195
195
  "Handling of cross-reference streams (either generate, preserve or delete; " \
196
- "default: #{@out_options.xref_streams})") do |x|
197
- @out_options.xref_streams = x
196
+ "default: #{@out_options[:xref_streams]})") do |x|
197
+ @out_options[:xref_streams] = x
198
198
  end
199
199
  options.on("--streams MODE", [:compress, :preserve, :uncompress],
200
200
  "Handling of stream data (either compress, preserve or uncompress; default: " \
201
- "#{@out_options.streams})") do |streams|
202
- @out_options.streams = streams
201
+ "#{@out_options[:streams]})") do |streams|
202
+ @out_options[:streams] = streams
203
203
  end
204
204
  options.on("--[no-]compress-pages", "Recompress page content streams (may take a long " \
205
- "time; default: #{@out_options.compress_pages})") do |c|
206
- @out_options.compress_pages = c
205
+ "time; default: #{@out_options[:compress_pages]})") do |c|
206
+ @out_options[:compress_pages] = c
207
207
  end
208
208
  options.on("--[no-]prune-page-resources", "Prunes unused objects from the page resources " \
209
- "(may take a long time; default: #{@out_options.prune_page_resources})") do |c|
210
- @out_options.prune_page_resources = c
209
+ "(may take a long time; default: #{@out_options[:prune_page_resources]})") do |c|
210
+ @out_options[:prune_page_resources] = c
211
211
  end
212
212
  options.on("--[no-]optimize-fonts", "Optimize embedded font files; " \
213
- "default: #{@out_options.optimize_fonts})") do |o|
214
- @out_options.optimize_fonts = o
213
+ "default: #{@out_options[:optimize_fonts]})") do |o|
214
+ @out_options[:optimize_fonts] = o
215
215
  end
216
216
  end
217
217
 
@@ -222,37 +222,37 @@ module HexaPDF
222
222
  options.separator("")
223
223
  options.separator("Encryption options:")
224
224
  options.on("--decrypt", "Remove any encryption") do
225
- @out_options.encryption = :remove
225
+ @out_options[:encryption] = :remove
226
226
  end
227
227
  options.on("--encrypt", "Encrypt the output file") do
228
- @out_options.encryption = :add
228
+ @out_options[:encryption] = :add
229
229
  end
230
230
  options.on("--owner-password PASSWORD", String, "The owner password to be set on the " \
231
231
  "output file (use - for reading from standard input)") do |pwd|
232
- @out_options.encryption = :add
233
- @out_options.enc_owner_pwd = (pwd == '-' ? read_password("Owner password") : pwd)
232
+ @out_options[:encryption] = :add
233
+ @out_options[:enc_owner_pwd] = (pwd == '-' ? read_password("Owner password") : pwd)
234
234
  end
235
235
  options.on("--user-password PASSWORD", String, "The user password to be set on the " \
236
236
  "output file (use - for reading from standard input)") do |pwd|
237
- @out_options.encryption = :add
238
- @out_options.enc_user_pwd = (pwd == '-' ? read_password("User password") : pwd)
237
+ @out_options[:encryption] = :add
238
+ @out_options[:enc_user_pwd] = (pwd == '-' ? read_password("User password") : pwd)
239
239
  end
240
240
  options.on("--algorithm ALGORITHM", [:aes, :arc4],
241
241
  "The encryption algorithm: aes or arc4 (default: " \
242
- "#{@out_options.enc_algorithm})") do |a|
243
- @out_options.encryption = :add
244
- @out_options.enc_algorithm = a
242
+ "#{@out_options[:enc_algorithm]})") do |a|
243
+ @out_options[:encryption] = :add
244
+ @out_options[:enc_algorithm] = a
245
245
  end
246
246
  options.on("--key-length BITS", Integer,
247
247
  "The encryption key length in bits (default: " \
248
- "#{@out_options.enc_key_length})") do |i|
249
- @out_options.encryption = :add
250
- @out_options.enc_key_length = i
248
+ "#{@out_options[:enc_key_length]})") do |i|
249
+ @out_options[:encryption] = :add
250
+ @out_options[:enc_key_length] = i
251
251
  end
252
252
  options.on("--force-V4",
253
253
  "Force use of encryption version 4 if key length=128 and algorithm=arc4") do
254
- @out_options.encryption = :add
255
- @out_options.enc_force_v4 = true
254
+ @out_options[:encryption] = :add
255
+ @out_options[:enc_force_v4] = true
256
256
  end
257
257
  syms = HexaPDF::Encryption::StandardSecurityHandler::Permissions::SYMBOL_TO_PERMISSION.keys
258
258
  options.on("--permissions PERMS", Array,
@@ -264,8 +264,8 @@ module HexaPDF
264
264
  end
265
265
  perm.to_sym
266
266
  end
267
- @out_options.encryption = :add
268
- @out_options.enc_permissions = perms
267
+ @out_options[:encryption] = :add
268
+ @out_options[:enc_permissions] = perms
269
269
  end
270
270
  end
271
271
 
@@ -273,12 +273,12 @@ module HexaPDF
273
273
  #
274
274
  # See: #define_optimization_options
275
275
  def apply_optimization_options(doc)
276
- doc.task(:optimize, compact: @out_options.compact,
277
- object_streams: @out_options.object_streams,
278
- xref_streams: @out_options.xref_streams,
279
- compress_pages: @out_options.compress_pages,
280
- prune_page_resources: @out_options.prune_page_resources)
281
- if @out_options.streams != :preserve || @out_options.optimize_fonts
276
+ doc.task(:optimize, compact: @out_options[:compact],
277
+ object_streams: @out_options[:object_streams],
278
+ xref_streams: @out_options[:xref_streams],
279
+ compress_pages: @out_options[:compress_pages],
280
+ prune_page_resources: @out_options[:prune_page_resources])
281
+ if @out_options[:streams] != :preserve || @out_options[:optimize_fonts]
282
282
  doc.each do |obj|
283
283
  optimize_stream(obj)
284
284
  optimize_font(obj)
@@ -292,15 +292,15 @@ module HexaPDF
292
292
 
293
293
  # Applies the chosen stream mode to the given object.
294
294
  def optimize_stream(obj)
295
- return if @out_options.streams == :preserve || !obj.respond_to?(:set_filter) ||
295
+ return if @out_options[:streams] == :preserve || !obj.respond_to?(:set_filter) ||
296
296
  Array(obj[:Filter]).any? {|f| IGNORED_FILTERS[f] }
297
297
 
298
- obj.set_filter(@out_options.streams == :compress ? :FlateDecode : nil)
298
+ obj.set_filter(@out_options[:streams] == :compress ? :FlateDecode : nil)
299
299
  end
300
300
 
301
301
  # Optimize the object if it is a font object.
302
302
  def optimize_font(obj)
303
- return unless @out_options.optimize_fonts && obj.kind_of?(HexaPDF::Type::Font) &&
303
+ return unless @out_options[:optimize_fonts] && obj.kind_of?(HexaPDF::Type::Font) &&
304
304
  (obj[:Subtype] == :TrueType ||
305
305
  (obj[:Subtype] == :Type0 && obj.descendant_font[:Subtype] == :CIDFontType2)) &&
306
306
  obj.embedded?
@@ -319,14 +319,14 @@ module HexaPDF
319
319
  #
320
320
  # See: #define_encryption_options
321
321
  def apply_encryption_options(doc)
322
- case @out_options.encryption
322
+ case @out_options[:encryption]
323
323
  when :add
324
- doc.encrypt(algorithm: @out_options.enc_algorithm,
325
- key_length: @out_options.enc_key_length,
326
- force_v4: @out_options.enc_force_v4,
327
- permissions: @out_options.enc_permissions,
328
- owner_password: @out_options.enc_owner_pwd,
329
- user_password: @out_options.enc_user_pwd)
324
+ doc.encrypt(algorithm: @out_options[:enc_algorithm],
325
+ key_length: @out_options[:enc_key_length],
326
+ force_v4: @out_options[:enc_force_v4],
327
+ permissions: @out_options[:enc_permissions],
328
+ owner_password: @out_options[:enc_owner_pwd],
329
+ user_password: @out_options[:enc_user_pwd])
330
330
  when :remove
331
331
  doc.encrypt(name: nil)
332
332
  end
@@ -0,0 +1,98 @@
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/cli/command'
38
+
39
+ module HexaPDF
40
+ module CLI
41
+
42
+ # Creates debugging information for adding to an issue.
43
+ class DebugInfo < Command
44
+
45
+ def initialize #:nodoc:
46
+ super('debug-info', takes_commands: false)
47
+ short_desc("Create debug information for a PDF file")
48
+ long_desc(<<~EOF)
49
+ Creates debug information for a possibly malformed PDF file that can be attached to an
50
+ issue.
51
+
52
+ Two files are created: anonymized-FILE where all strings are replaced with zeroes and
53
+ debug_info.txt with additional debug information.
54
+ EOF
55
+
56
+ options.on("--password PASSWORD", "-p", String,
57
+ "The password for decryption. Use - for reading from standard input.") do |pwd|
58
+ @password = (pwd == '-' ? read_password : pwd)
59
+ end
60
+
61
+ @password = nil
62
+ end
63
+
64
+ def execute(file) #:nodoc:
65
+ output_name = "anonymized-#{file}"
66
+ puts "Creating anonymized file '#{output_name}'"
67
+ data = File.binread(file)
68
+ data.gsub!(/(>>\s*stream\s*)(.*?)(\s*endstream)/m) {|m| "#{$1}#{'0' * $2.length}#{$3}" }
69
+ data.gsub!(/([^<]<)([0-9A-Fa-f#{Tokenizer::WHITESPACE}]*?)>/m) {|m| "#{$1}#{'0' * $2.length}>" }
70
+ data.gsub!(/\((.*?)\)/m) {|m| "(#{'0' * $1.length})" }
71
+ File.binwrite(output_name, data)
72
+
73
+ debug_info = +''
74
+ puts "Collecting debug information in debug_info.txt"
75
+ begin
76
+ output = capture_output { HexaPDF::CLI::Application.new.parse(['info', '--check', file]) }
77
+ debug_info << "Output:\n"<< output
78
+ rescue
79
+ debug_info << "Error collecting info: #{$!.message}\n"
80
+ end
81
+ File.write('debug_info.txt', debug_info)
82
+ end
83
+
84
+ private
85
+
86
+ def capture_output
87
+ stdout, stderr = $stdout, $stderr
88
+ $stdout = $stderr = StringIO.new
89
+ yield
90
+ $stdout.string
91
+ ensure
92
+ $stdout, $stderr = stdout, stderr
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+ end
@@ -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
@@ -161,8 +161,8 @@ module HexaPDF
161
161
  (field.alternate_field_name ? " (#{field.alternate_field_name})" : '')
162
162
  concrete_field_type = field.concrete_field_type
163
163
  nice_field_type = concrete_field_type.to_s.split('_').map(&:capitalize).join(' ')
164
- size = "(#{widget[:Rect].width.round(3)}x#{widget[:Rect].height.round(3)})"
165
- position = "(#{widget[:Rect].left}, #{widget[:Rect].bottom})"
164
+ size = "#{widget[:Rect].width.round(3)}x#{widget[:Rect].height.round(3)}"
165
+ position = "x=#{widget[:Rect].left}, y=#{widget[:Rect].bottom}"
166
166
  field_value = if !field.field_value || concrete_field_type != :signature_field
167
167
  field.field_value.inspect
168
168
  else
@@ -172,10 +172,15 @@ module HexaPDF
172
172
  temp
173
173
  end
174
174
 
175
+ if concrete_field_type == :radio_button
176
+ rb_name = ((widget.appearance_dict&.normal_appearance&.value&.keys || []) - [:Off]).first
177
+ rb_name = " (#{rb_name.inspect})"
178
+ end
179
+
175
180
  flags = field_flags(field)
176
- puts " #{field_name}" << (flags.empty? ? '' : " (#{flags.join(', ')})")
181
+ puts " #{field_name}#{rb_name}" << (flags.empty? ? '' : " (#{flags.join(', ')})")
177
182
  if command_parser.verbosity_info?
178
- printf(" └─ %-22s | %-20s\n", nice_field_type, "#{size} #{position}")
183
+ printf(" └─ %-22s | %-20s\n", nice_field_type, "#{position}, #{size} ")
179
184
  end
180
185
  puts " └─ #{field_value}"
181
186
  if command_parser.verbosity_info?
@@ -285,7 +290,7 @@ module HexaPDF
285
290
  page.each_annotation do |annotation|
286
291
  next unless annotation[:Subtype] == :Widget
287
292
  field = annotation.form_field
288
- next if field.concrete_field_type == :push_button
293
+ next if !field.concrete_field_type || field.concrete_field_type == :push_button
289
294
  if with_seen || !seen[field.full_field_name]
290
295
  yield(page, page_index, field, annotation)
291
296
  seen[field.full_field_name] = true
@@ -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
@@ -35,6 +35,7 @@
35
35
  #++
36
36
 
37
37
  require 'set'
38
+ require 'fileutils'
38
39
  require 'hexapdf/cli/command'
39
40
 
40
41
  module HexaPDF
@@ -132,7 +133,7 @@ module HexaPDF
132
133
  printf("%5s %5s %9s %6s %6s %5s %4s %3s %5s %5s %6s %5s %8s\n",
133
134
  "index", "page", "oid", "width", "height", "color", "comp", "bpc",
134
135
  "x-ppi", "y-ppi", "size", "type", "writable")
135
- puts("-" * 77)
136
+ puts("-" * 84)
136
137
  each_image(doc) do |image, index, pindex, (x_ppi, y_ppi)|
137
138
  info = image.info
138
139
  size = human_readable_file_size(image[:Length] + image[:SMask]&.[](:Length).to_i)
@@ -145,20 +146,34 @@ module HexaPDF
145
146
 
146
147
  # Extracts the images with the given indices.
147
148
  def extract_images(doc)
149
+ FileUtils.mkdir_p(File.dirname("#{@prefix}filename"))
150
+ prefix = File.directory?(@prefix) ? @prefix : "#{@prefix}-"
151
+
148
152
  done = Set.new
153
+ count = total = 0
149
154
  each_image(doc) do |image, index, _|
150
155
  next unless (@indices.include?(index) || @indices.include?(0)) && !done.include?(index)
156
+ total += 1
151
157
  info = image.info
152
158
  if info.writable
153
- path = "#{@prefix}-#{index}.#{image.info.extension}"
159
+ count += 1
160
+ path = "#{prefix}#{index}.#{image.info.extension}"
154
161
  maybe_raise_on_existing_file(path)
155
- puts "Extracting #{path}..." if command_parser.verbosity_info?
162
+ if command_parser.verbosity_info?
163
+ puts "Extracting image #{index} (#{image.width}x#{image.height}, " \
164
+ "#{info.color_space}, #{info.type}) to #{path}..."
165
+ end
156
166
  image.write(path)
157
167
  done << index
168
+ if info.color_space == :cmyk && info.type == :jpeg
169
+ $stderr.puts "Note (image #{path}): JPEG uses CMYK colorspace and may " \
170
+ "need color post-processing"
171
+ end
158
172
  elsif command_parser.verbosity_warning?
159
173
  $stderr.puts "Warning (image #{index}): PDF image format not supported for writing"
160
174
  end
161
175
  end
176
+ puts "Created #{count} image files (out of #{total} selected)" if command_parser.verbosity_info?
162
177
  end
163
178
 
164
179
  # Iterates over all images.
@@ -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
@@ -188,12 +188,20 @@ module HexaPDF
188
188
  end
189
189
  serialize(obj.value, recursive: true) if obj
190
190
 
191
- when 's', 'stream', 'raw', 'raw-stream'
191
+ when 's', 'stream', 'raw', 'raw-stream', 'sd'
192
192
  if (obj = pdf_object_from_string_reference(data.shift) rescue $stderr.puts($!.message)) &&
193
193
  obj.kind_of?(HexaPDF::Stream)
194
- source = (command.start_with?('raw') ? obj.stream_source : obj.stream_decoder)
195
- while source.alive? && (stream_data = source.resume)
196
- $stdout.write(stream_data)
194
+ if command == 'sd'
195
+ if obj.respond_to?(:process_contents)
196
+ obj.process_contents(ContentProcessor.new)
197
+ else
198
+ $stderr.puts("Error: The object is not a Form XObject or page")
199
+ end
200
+ else
201
+ source = (command.start_with?('raw') ? obj.stream_source : obj.stream_decoder)
202
+ while source.alive? && (stream_data = source.resume)
203
+ $stdout.write(stream_data)
204
+ end
197
205
  end
198
206
  elsif command_parser.verbosity_info?
199
207
  $stderr.puts("Note: Object has no stream data")
@@ -285,6 +293,10 @@ module HexaPDF
285
293
  IO.copy_stream(@doc.revisions.parser.io, $stdout, length, 0)
286
294
  else
287
295
  puts "Document has #{@doc.revisions.count} revision#{@doc.revisions.count == 1 ? '' : 's'}"
296
+ if @doc.revisions.parser.reconstructed? && @doc.revisions.count == 1 &&
297
+ @doc.revisions.current == @doc.revisions.parser.reconstructed_revision
298
+ puts "Document cross-reference table has been reconstructed"
299
+ end
288
300
  revision_information do |rev, index, count, signature, end_offset|
289
301
  type = if rev.trailer[:XRefStm]
290
302
  "xref table + stream"
@@ -395,16 +407,19 @@ module HexaPDF
395
407
  end
396
408
  io = @doc.revisions.parser.io
397
409
 
398
- startxrefs = @doc.revisions.map {|rev| rev.trailer[:Prev] }
399
410
  io.seek(0, IO::SEEK_END)
400
- startxrefs.push(@doc.revisions.parser.startxref_offset, io.pos).shift
411
+ startxrefs = @doc.revisions.map {|rev| rev.trailer[:Prev].to_i } <<
412
+ @doc.revisions.parser.startxref_offset <<
413
+ io.pos
414
+ startxrefs.sort!
415
+ startxrefs.shift
401
416
 
402
417
  @doc.revisions.each_with_index.map do |rev, index|
403
418
  end_index = 0
404
419
  sig = signatures[rev]
405
420
  if sig
406
421
  end_index = sig[:ByteRange][-2] + sig[:ByteRange][-1]
407
- else
422
+ elsif rev != @doc.revisions.parser.reconstructed_revision
408
423
  io.seek(startxrefs[index], IO::SEEK_SET)
409
424
  buffer = ''.b
410
425
  while io.pos < startxrefs[index + 1]
@@ -424,6 +439,7 @@ module HexaPDF
424
439
  ["OID[,GEN] | o[bject] OID[,GEN]", "Print object"],
425
440
  ["r[ecursive] OID[,GEN]", "Print object recursively"],
426
441
  ["s[tream] OID[,GEN]", "Print filtered stream"],
442
+ ["sd OID[,GEN]", "Print the decoded stream of a Form XObject or page"],
427
443
  ["raw[-stream] OID[,GEN]", "Print raw stream"],
428
444
  ["rev[ision] [NUMBER]", "Print or extract revision"],
429
445
  ["x[ref] OID[,GEN]", "Print the cross-reference entry"],
@@ -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
@@ -34,7 +34,6 @@
34
34
  # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
35
  #++
36
36
 
37
- require 'ostruct'
38
37
  require 'hexapdf/cli/command'
39
38
 
40
39
  module HexaPDF
@@ -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
@@ -53,11 +53,11 @@ module HexaPDF
53
53
  EOF
54
54
 
55
55
  @password = nil
56
- @out_options.compact = true
57
- @out_options.xref_streams = :generate
58
- @out_options.object_streams = :generate
59
- @out_options.streams = :compress
60
- @out_options.optimize_fonts = true
56
+ @out_options[:compact] = true
57
+ @out_options[:xref_streams] = :generate
58
+ @out_options[:object_streams] = :generate
59
+ @out_options[:streams] = :compress
60
+ @out_options[:optimize_fonts] = true
61
61
 
62
62
  options.on("--password PASSWORD", "-p", String,
63
63
  "The password for decryption. Use - for reading from standard input.") do |pwd|
@@ -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