hexapdf 0.11.9 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +157 -0
  3. data/LICENSE +1 -1
  4. data/examples/001-hello_world.rb +1 -1
  5. data/examples/002-graphics.rb +1 -1
  6. data/examples/003-arcs.rb +1 -1
  7. data/examples/004-optimizing.rb +1 -1
  8. data/examples/005-merging.rb +1 -1
  9. data/examples/006-standard_pdf_fonts.rb +1 -1
  10. data/examples/007-truetype.rb +1 -1
  11. data/examples/008-show_char_bboxes.rb +1 -1
  12. data/examples/009-text_layouter_alignment.rb +1 -1
  13. data/examples/010-text_layouter_inline_boxes.rb +1 -1
  14. data/examples/011-text_layouter_line_wrapping.rb +1 -1
  15. data/examples/012-text_layouter_styling.rb +1 -1
  16. data/examples/013-text_layouter_shapes.rb +1 -1
  17. data/examples/014-text_in_polygon.rb +1 -1
  18. data/examples/015-boxes.rb +1 -1
  19. data/examples/016-frame_automatic_box_placement.rb +1 -1
  20. data/examples/017-frame_text_flow.rb +1 -1
  21. data/examples/018-composer.rb +1 -1
  22. data/examples/019-acro_form.rb +51 -0
  23. data/lib/hexapdf.rb +1 -1
  24. data/lib/hexapdf/cli.rb +3 -1
  25. data/lib/hexapdf/cli/batch.rb +1 -1
  26. data/lib/hexapdf/cli/command.rb +22 -11
  27. data/lib/hexapdf/cli/files.rb +1 -1
  28. data/lib/hexapdf/cli/form.rb +240 -0
  29. data/lib/hexapdf/cli/image2pdf.rb +3 -2
  30. data/lib/hexapdf/cli/images.rb +1 -1
  31. data/lib/hexapdf/cli/info.rb +52 -3
  32. data/lib/hexapdf/cli/inspect.rb +31 -9
  33. data/lib/hexapdf/cli/merge.rb +2 -2
  34. data/lib/hexapdf/cli/modify.rb +1 -1
  35. data/lib/hexapdf/cli/optimize.rb +1 -1
  36. data/lib/hexapdf/cli/split.rb +1 -1
  37. data/lib/hexapdf/cli/watermark.rb +1 -1
  38. data/lib/hexapdf/composer.rb +2 -2
  39. data/lib/hexapdf/configuration.rb +81 -11
  40. data/lib/hexapdf/content.rb +3 -1
  41. data/lib/hexapdf/content/canvas.rb +5 -18
  42. data/lib/hexapdf/content/color_space.rb +111 -32
  43. data/lib/hexapdf/content/graphic_object.rb +1 -1
  44. data/lib/hexapdf/content/graphic_object/arc.rb +4 -4
  45. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
  46. data/lib/hexapdf/content/graphic_object/geom2d.rb +1 -1
  47. data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
  48. data/lib/hexapdf/content/graphics_state.rb +1 -1
  49. data/lib/hexapdf/content/operator.rb +9 -9
  50. data/lib/hexapdf/content/parser.rb +18 -5
  51. data/lib/hexapdf/content/processor.rb +1 -1
  52. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  53. data/lib/hexapdf/data_dir.rb +1 -1
  54. data/lib/hexapdf/dictionary.rb +5 -5
  55. data/lib/hexapdf/dictionary_fields.rb +2 -10
  56. data/lib/hexapdf/document.rb +45 -17
  57. data/lib/hexapdf/document/files.rb +1 -2
  58. data/lib/hexapdf/document/fonts.rb +1 -1
  59. data/lib/hexapdf/document/images.rb +1 -1
  60. data/lib/hexapdf/document/pages.rb +3 -14
  61. data/lib/hexapdf/encryption.rb +1 -1
  62. data/lib/hexapdf/encryption/aes.rb +1 -1
  63. data/lib/hexapdf/encryption/arc4.rb +1 -1
  64. data/lib/hexapdf/encryption/fast_aes.rb +1 -1
  65. data/lib/hexapdf/encryption/fast_arc4.rb +2 -2
  66. data/lib/hexapdf/encryption/identity.rb +1 -1
  67. data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
  68. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  69. data/lib/hexapdf/encryption/security_handler.rb +2 -1
  70. data/lib/hexapdf/encryption/standard_security_handler.rb +2 -1
  71. data/lib/hexapdf/error.rb +1 -1
  72. data/lib/hexapdf/filter.rb +3 -3
  73. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  74. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  75. data/lib/hexapdf/filter/encryption.rb +1 -1
  76. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  77. data/lib/hexapdf/filter/lzw_decode.rb +1 -1
  78. data/lib/hexapdf/filter/{jpx_decode.rb → pass_through.rb} +5 -5
  79. data/lib/hexapdf/filter/predictor.rb +1 -1
  80. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  81. data/lib/hexapdf/font/cmap.rb +2 -5
  82. data/lib/hexapdf/font/cmap/parser.rb +1 -1
  83. data/lib/hexapdf/font/cmap/writer.rb +1 -1
  84. data/lib/hexapdf/font/encoding.rb +1 -1
  85. data/lib/hexapdf/font/encoding/base.rb +9 -1
  86. data/lib/hexapdf/font/encoding/difference_encoding.rb +7 -1
  87. data/lib/hexapdf/font/encoding/glyph_list.rb +1 -1
  88. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  89. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
  90. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  91. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  92. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +1 -1
  93. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  94. data/lib/hexapdf/font/invalid_glyph.rb +1 -1
  95. data/lib/hexapdf/font/true_type.rb +1 -1
  96. data/lib/hexapdf/font/true_type/builder.rb +1 -1
  97. data/lib/hexapdf/font/true_type/font.rb +1 -1
  98. data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
  99. data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
  100. data/lib/hexapdf/font/true_type/table.rb +1 -1
  101. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  102. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
  103. data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
  104. data/lib/hexapdf/font/true_type/table/glyf.rb +1 -1
  105. data/lib/hexapdf/font/true_type/table/head.rb +2 -1
  106. data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
  107. data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
  108. data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
  109. data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
  110. data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
  111. data/lib/hexapdf/font/true_type/table/name.rb +1 -1
  112. data/lib/hexapdf/font/true_type/table/os2.rb +3 -1
  113. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  114. data/lib/hexapdf/font/true_type_wrapper.rb +54 -51
  115. data/lib/hexapdf/font/type1.rb +1 -1
  116. data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
  117. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  118. data/lib/hexapdf/font/type1/font.rb +1 -1
  119. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  120. data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
  121. data/lib/hexapdf/font/type1_wrapper.rb +68 -52
  122. data/lib/hexapdf/font_loader.rb +1 -1
  123. data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
  124. data/lib/hexapdf/font_loader/from_file.rb +1 -1
  125. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  126. data/lib/hexapdf/image_loader.rb +1 -1
  127. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  128. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  129. data/lib/hexapdf/image_loader/png.rb +4 -3
  130. data/lib/hexapdf/importer.rb +2 -4
  131. data/lib/hexapdf/layout.rb +1 -1
  132. data/lib/hexapdf/layout/box.rb +1 -1
  133. data/lib/hexapdf/layout/frame.rb +1 -1
  134. data/lib/hexapdf/layout/image_box.rb +1 -1
  135. data/lib/hexapdf/layout/inline_box.rb +1 -1
  136. data/lib/hexapdf/layout/line.rb +2 -2
  137. data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
  138. data/lib/hexapdf/layout/style.rb +24 -24
  139. data/lib/hexapdf/layout/text_box.rb +1 -1
  140. data/lib/hexapdf/layout/text_fragment.rb +1 -1
  141. data/lib/hexapdf/layout/text_layouter.rb +1 -1
  142. data/lib/hexapdf/layout/text_shaper.rb +4 -3
  143. data/lib/hexapdf/layout/width_from_polygon.rb +1 -1
  144. data/lib/hexapdf/name_tree_node.rb +1 -1
  145. data/lib/hexapdf/number_tree_node.rb +1 -1
  146. data/lib/hexapdf/object.rb +32 -27
  147. data/lib/hexapdf/parser.rb +69 -6
  148. data/lib/hexapdf/pdf_array.rb +10 -3
  149. data/lib/hexapdf/rectangle.rb +31 -1
  150. data/lib/hexapdf/reference.rb +1 -1
  151. data/lib/hexapdf/revision.rb +2 -1
  152. data/lib/hexapdf/revisions.rb +30 -22
  153. data/lib/hexapdf/serializer.rb +2 -2
  154. data/lib/hexapdf/stream.rb +1 -1
  155. data/lib/hexapdf/task.rb +1 -1
  156. data/lib/hexapdf/task/dereference.rb +1 -1
  157. data/lib/hexapdf/task/optimize.rb +7 -5
  158. data/lib/hexapdf/tokenizer.rb +5 -4
  159. data/lib/hexapdf/type.rb +1 -1
  160. data/lib/hexapdf/type/acro_form.rb +7 -1
  161. data/lib/hexapdf/type/acro_form/appearance_generator.rb +405 -0
  162. data/lib/hexapdf/type/acro_form/button_field.rb +305 -0
  163. data/lib/hexapdf/type/acro_form/choice_field.rb +220 -0
  164. data/lib/hexapdf/type/acro_form/field.rb +250 -17
  165. data/lib/hexapdf/type/acro_form/form.rb +159 -7
  166. data/lib/hexapdf/type/acro_form/text_field.rb +187 -0
  167. data/lib/hexapdf/type/acro_form/variable_text_field.rb +122 -0
  168. data/lib/hexapdf/type/action.rb +1 -1
  169. data/lib/hexapdf/type/actions.rb +1 -1
  170. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  171. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  172. data/lib/hexapdf/type/actions/launch.rb +1 -1
  173. data/lib/hexapdf/type/actions/uri.rb +4 -3
  174. data/lib/hexapdf/type/annotation.rb +73 -3
  175. data/lib/hexapdf/type/annotations.rb +1 -1
  176. data/lib/hexapdf/type/annotations/link.rb +2 -2
  177. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  178. data/lib/hexapdf/type/annotations/text.rb +1 -1
  179. data/lib/hexapdf/type/annotations/widget.rb +238 -2
  180. data/lib/hexapdf/type/catalog.rb +23 -3
  181. data/lib/hexapdf/type/cid_font.rb +1 -1
  182. data/lib/hexapdf/type/embedded_file.rb +1 -1
  183. data/lib/hexapdf/type/file_specification.rb +2 -2
  184. data/lib/hexapdf/type/font.rb +18 -1
  185. data/lib/hexapdf/type/font_descriptor.rb +2 -2
  186. data/lib/hexapdf/type/font_simple.rb +4 -2
  187. data/lib/hexapdf/type/font_true_type.rb +7 -3
  188. data/lib/hexapdf/type/font_type0.rb +2 -2
  189. data/lib/hexapdf/type/font_type1.rb +16 -1
  190. data/lib/hexapdf/type/font_type3.rb +1 -1
  191. data/lib/hexapdf/type/form.rb +12 -2
  192. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  193. data/lib/hexapdf/type/icon_fit.rb +1 -1
  194. data/lib/hexapdf/type/image.rb +5 -3
  195. data/lib/hexapdf/type/info.rb +1 -1
  196. data/lib/hexapdf/type/names.rb +1 -1
  197. data/lib/hexapdf/type/object_stream.rb +1 -1
  198. data/lib/hexapdf/type/page.rb +36 -12
  199. data/lib/hexapdf/type/page_tree_node.rb +37 -16
  200. data/lib/hexapdf/type/resources.rb +17 -3
  201. data/lib/hexapdf/type/trailer.rb +4 -6
  202. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  203. data/lib/hexapdf/type/xref_stream.rb +1 -1
  204. data/lib/hexapdf/utils/bit_field.rb +38 -24
  205. data/lib/hexapdf/utils/bit_stream.rb +1 -1
  206. data/lib/hexapdf/utils/graphics_helpers.rb +1 -1
  207. data/lib/hexapdf/utils/lru_cache.rb +1 -1
  208. data/lib/hexapdf/utils/math_helpers.rb +1 -1
  209. data/lib/hexapdf/utils/object_hash.rb +1 -1
  210. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  211. data/lib/hexapdf/utils/sorted_tree_node.rb +19 -16
  212. data/lib/hexapdf/version.rb +2 -2
  213. data/lib/hexapdf/writer.rb +1 -1
  214. data/lib/hexapdf/xref_section.rb +1 -1
  215. data/test/hexapdf/common_tokenizer_tests.rb +6 -1
  216. data/test/hexapdf/content/common.rb +2 -2
  217. data/test/hexapdf/content/graphic_object/test_arc.rb +4 -4
  218. data/test/hexapdf/content/test_canvas.rb +3 -3
  219. data/test/hexapdf/content/test_color_space.rb +71 -8
  220. data/test/hexapdf/content/test_operator.rb +22 -22
  221. data/test/hexapdf/content/test_parser.rb +14 -0
  222. data/test/hexapdf/document/test_fonts.rb +1 -1
  223. data/test/hexapdf/document/test_pages.rb +6 -6
  224. data/test/hexapdf/encryption/test_aes.rb +4 -4
  225. data/test/hexapdf/encryption/test_standard_security_handler.rb +11 -11
  226. data/test/hexapdf/filter/test_ascii85_decode.rb +1 -1
  227. data/test/hexapdf/filter/test_ascii_hex_decode.rb +1 -1
  228. data/test/hexapdf/font/encoding/test_base.rb +10 -0
  229. data/test/hexapdf/font/encoding/test_difference_encoding.rb +8 -0
  230. data/test/hexapdf/font/test_true_type_wrapper.rb +10 -7
  231. data/test/hexapdf/font/test_type1_wrapper.rb +33 -8
  232. data/test/hexapdf/layout/test_style.rb +1 -1
  233. data/test/hexapdf/layout/test_text_layouter.rb +3 -4
  234. data/test/hexapdf/test_configuration.rb +2 -2
  235. data/test/hexapdf/test_dictionary.rb +3 -1
  236. data/test/hexapdf/test_dictionary_fields.rb +2 -2
  237. data/test/hexapdf/test_document.rb +16 -4
  238. data/test/hexapdf/test_object.rb +44 -26
  239. data/test/hexapdf/test_parser.rb +125 -55
  240. data/test/hexapdf/test_pdf_array.rb +7 -0
  241. data/test/hexapdf/test_rectangle.rb +14 -0
  242. data/test/hexapdf/test_revision.rb +3 -0
  243. data/test/hexapdf/test_revisions.rb +35 -0
  244. data/test/hexapdf/test_writer.rb +2 -2
  245. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +521 -0
  246. data/test/hexapdf/type/acro_form/test_button_field.rb +281 -0
  247. data/test/hexapdf/type/acro_form/test_choice_field.rb +137 -0
  248. data/test/hexapdf/type/acro_form/test_field.rb +163 -6
  249. data/test/hexapdf/type/acro_form/test_form.rb +189 -22
  250. data/test/hexapdf/type/acro_form/test_text_field.rb +121 -0
  251. data/test/hexapdf/type/acro_form/test_variable_text_field.rb +77 -0
  252. data/test/hexapdf/type/annotations/test_text.rb +1 -1
  253. data/test/hexapdf/type/annotations/test_widget.rb +199 -0
  254. data/test/hexapdf/type/test_annotation.rb +45 -0
  255. data/test/hexapdf/type/test_catalog.rb +18 -0
  256. data/test/hexapdf/type/test_font.rb +5 -0
  257. data/test/hexapdf/type/test_font_simple.rb +2 -1
  258. data/test/hexapdf/type/test_font_true_type.rb +6 -0
  259. data/test/hexapdf/type/test_font_type1.rb +8 -0
  260. data/test/hexapdf/type/test_form.rb +19 -1
  261. data/test/hexapdf/type/test_image.rb +7 -0
  262. data/test/hexapdf/type/test_page.rb +45 -7
  263. data/test/hexapdf/type/test_page_tree_node.rb +62 -12
  264. data/test/hexapdf/type/test_resources.rb +20 -0
  265. data/test/hexapdf/type/test_trailer.rb +4 -0
  266. data/test/hexapdf/utils/test_bit_field.rb +15 -1
  267. data/test/hexapdf/utils/test_sorted_tree_node.rb +10 -9
  268. data/test/test_helper.rb +1 -1
  269. metadata +34 -21
  270. data/lib/hexapdf/filter/dct_decode.rb +0 -60
@@ -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-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 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
@@ -0,0 +1,240 @@
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-2020 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
+ require 'strscan'
39
+
40
+ module HexaPDF
41
+ module CLI
42
+
43
+ # Processes a PDF that contains an interactive form (AcroForm).
44
+ class Form < Command
45
+
46
+ def initialize #:nodoc:
47
+ super('form', takes_commands: false)
48
+ short_desc("Show form fields and fill out a form")
49
+ long_desc(<<~EOF)
50
+ Use this command to process interactive PDF forms.
51
+
52
+ If the the output file name is not given, all form fields are listed in page order. Use
53
+ the global --verbose option to show additional information like field type and location.
54
+
55
+ If the output file name is given, the fields can be interactively filled out. By
56
+ additionally using the --template option, the data for the fields is read from the given
57
+ template file instead of the standard input.
58
+ EOF
59
+
60
+ options.on("--password PASSWORD", "-p", String,
61
+ "The password for decryption. Use - for reading from standard input.") do |pwd|
62
+ @password = (pwd == '-' ? read_password : pwd)
63
+ end
64
+ options.on("--template TEMPLATE_FILE", "-t TEMPLATE_FILE",
65
+ "Use the template file for the field values") do |template|
66
+ @template = template
67
+ end
68
+ options.on("--[no-]viewer-override", "Let the PDF viewer override the visual " \
69
+ "appearance. Default: use setting from input PDF") do |need_appearances|
70
+ @need_appearances = need_appearances
71
+ end
72
+ options.on("--[no-]incremental-save", "Append the changes instead of rewriting the " \
73
+ "whole file. Default: true") do |incremental|
74
+ @incremental = incremental
75
+ end
76
+
77
+ @password = nil
78
+ @template = nil
79
+ @need_appearances = nil
80
+ @incremental = true
81
+ end
82
+
83
+ def execute(in_file, out_file = nil) #:nodoc:
84
+ maybe_raise_on_existing_file(out_file) if out_file
85
+ with_document(in_file, password: @password, out_file: out_file,
86
+ incremental: @incremental) do |doc|
87
+ if !doc.acro_form
88
+ raise "This PDF doesn't contain an interactive form"
89
+ elsif out_file
90
+ doc.acro_form[:NeedAppearances] = @need_appearances unless @need_appearances.nil?
91
+ if @template
92
+ fill_form_with_template(doc)
93
+ else
94
+ fill_form(doc)
95
+ end
96
+ else
97
+ list_form_fields(doc)
98
+ end
99
+ end
100
+ end
101
+
102
+ private
103
+
104
+ # Lists all terminal form fields.
105
+ def list_form_fields(doc)
106
+ current_page_index = -1
107
+ each_field(doc) do |_page, page_index, field, widget|
108
+ if current_page_index != page_index
109
+ puts "Page #{page_index + 1}"
110
+ current_page_index = page_index
111
+ end
112
+
113
+ field_name = field.full_field_name +
114
+ (field.alternate_field_name ? " (#{field.alternate_field_name})" : '')
115
+ concrete_field_type = field.concrete_field_type
116
+ nice_field_type = concrete_field_type.to_s.split('_').map(&:capitalize).join(' ')
117
+ position = "(#{widget[:Rect].left}, #{widget[:Rect].bottom})"
118
+
119
+ puts " #{field_name}"
120
+ if command_parser.verbosity_info?
121
+ printf(" └─ %-22s | %-20s\n", nice_field_type, position)
122
+ end
123
+ puts " └─ #{field.field_value.inspect}"
124
+ if command_parser.verbosity_info?
125
+ if field.field_type == :Ch
126
+ puts " └─ Options: #{field.option_items.map(&:inspect).join(', ')}"
127
+ elsif concrete_field_type == :radio_button
128
+ puts " └─ Options: #{field.radio_button_values.map(&:inspect).join(', ')}"
129
+ end
130
+ end
131
+ end
132
+ end
133
+
134
+ # Fills out the form by interactively asking the user for field values.
135
+ def fill_form(doc)
136
+ current_page_index = -1
137
+ each_field(doc) do |_page, page_index, field, _widget|
138
+ if current_page_index != page_index
139
+ puts "Page #{page_index + 1}"
140
+ current_page_index = page_index
141
+ end
142
+
143
+ field_name = field.full_field_name +
144
+ (field.alternate_field_name ? " (#{field.alternate_field_name})" : '')
145
+ concrete_field_type = field.concrete_field_type
146
+
147
+ puts " #{field_name}"
148
+ puts " └─ Current value: #{field.field_value.inspect}"
149
+
150
+ if field.field_type == :Ch
151
+ puts " └─ Possible values: #{field.option_items.map(&:inspect).join(', ')}"
152
+ elsif concrete_field_type == :radio_button
153
+ puts " └─ Possible values: #{field.radio_button_values.map(&:inspect).join(', ')}"
154
+ elsif concrete_field_type == :check_box
155
+ puts " └─ Possible values: y(es), t(rue); n(o), f(alse)"
156
+ end
157
+
158
+ begin
159
+ print " └─ New value: "
160
+ value = $stdin.readline.chomp
161
+ next if value.empty?
162
+ apply_field_value(field, value)
163
+ rescue HexaPDF::Error => e
164
+ puts " ⚠ #{e.message}"
165
+ retry
166
+ end
167
+ end
168
+ end
169
+
170
+ # Fills out the form using the data from the provided template file.
171
+ def fill_form_with_template(doc)
172
+ data = parse_template
173
+ form = doc.acro_form
174
+ data.each do |name, value|
175
+ field = form.field_by_name(name)
176
+ raise "Field '#{name}' not found in input PDF" unless field
177
+ apply_field_value(field, value)
178
+ end
179
+ end
180
+
181
+ # Parses the data from the given template file.
182
+ def parse_template
183
+ data = {}
184
+ scanner = StringScanner.new(File.read(@template))
185
+ until scanner.eos?
186
+ field_name = scanner.scan(/(\\:|[^:])*?:/)
187
+ break unless field_name
188
+ field_name.gsub!(/\\:/, ':')
189
+ field_value = scanner.scan(/.*?(?=^\S|\z)/m)
190
+ data[field_name.chop] = field_value.strip.gsub(/^\s*/, '') if field_value
191
+ end
192
+ if !scanner.eos? && command_parser.verbosity_warning?
193
+ $stderr.puts "Warning: Some template could not be parsed"
194
+ end
195
+ data
196
+ end
197
+
198
+ # Applies the given value to the field.
199
+ def apply_field_value(field, value)
200
+ case field.concrete_field_type
201
+ when :single_line_text_field
202
+ field.field_value = value
203
+ when :combo_box, :list_box
204
+ field.field_value = value
205
+ when :editable_combo_box
206
+ field.field_value = value
207
+ when :check_box
208
+ unless value.match?(/y(es)?|t(rue)?|f(alse)?|n(o)/)
209
+ raise HexaPDF::Error, "Invalid input, use one of the possible values"
210
+ end
211
+ field.field_value = value.match?(/y(es)?|t(rue)?/)
212
+ when :radio_button
213
+ field.field_value = value.intern
214
+ else
215
+ raise "Field type not yet supported"
216
+ end
217
+ end
218
+
219
+ # Iterates over all non-push button fields in page order. If a field appears on multiple
220
+ # pages, it is only yielded on the first page.
221
+ def each_field(doc) # :yields: page, page_index, field
222
+ seen = {}
223
+
224
+ doc.pages.each_with_index do |page, page_index|
225
+ page[:Annots]&.each do |annotation|
226
+ next unless annotation[:Subtype] == :Widget
227
+ field = annotation.form_field
228
+ next if field.concrete_field_type == :push_button
229
+ unless seen[field]
230
+ yield(page, page_index, field, annotation)
231
+ seen[field] = true
232
+ end
233
+ end
234
+ end
235
+ end
236
+
237
+ end
238
+
239
+ end
240
+ 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-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 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
@@ -64,7 +64,8 @@ module HexaPDF
64
64
  orientation = :landscape
65
65
  page_size.delete_suffix!('-landscape')
66
66
  end
67
- HexaPDF::Type::Page.media_box(page_size.to_sym, orientation: orientation)
67
+ page_size = page_size.capitalize.to_sym
68
+ HexaPDF::Type::Page.media_box(page_size, orientation: orientation)
68
69
  end
69
70
  end
70
71
  options.on("--[no-]auto-rotate", "Automatically rotate pages based on image dimesions. " \
@@ -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-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 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-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 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,13 +55,21 @@ module HexaPDF
55
55
  long_desc(<<~EOF)
56
56
  This command extracts information from the Info dictionary of a PDF file as well
57
57
  as some other useful information like the used PDF version and encryption information.
58
+
59
+ If the --check option is specified, the PDF file will also be checked for parse and
60
+ validation errors. And if the process doesn't abort, HexaPDF is still able to handle the
61
+ file by correcting the errors.
58
62
  EOF
63
+ options.on("--check", "-c", "Check the PDF file for parse errors and validity") do |check|
64
+ @check_file = check
65
+ end
59
66
  options.on("--password PASSWORD", "-p", String,
60
67
  "The password for decryption. Use - for reading from standard input.") do |pwd|
61
68
  @password = (pwd == '-' ? read_password : pwd)
62
69
  end
63
70
  @password = nil
64
71
  @auto_decrypt = true
72
+ @check_file = false
65
73
  end
66
74
 
67
75
  def execute(file) #:nodoc:
@@ -79,8 +87,30 @@ module HexaPDF
79
87
  options = pdf_options(@password)
80
88
  options[:config]['document.auto_decrypt'] = @auto_decrypt
81
89
  HexaPDF::Document.open(file, **options) do |doc|
90
+ if @check_file
91
+ indirect_object = nil
92
+ validation_block = lambda do |msg, correctable, object|
93
+ object = indirect_object unless object.indirect? || object.type == :XXTrailer
94
+ object_type = if object.type == :XXTrailer
95
+ 'trailer'
96
+ elsif !object.type.to_s.start_with?("XX")
97
+ "object type #{object.type} (#{object.oid},#{object.gen})"
98
+ else
99
+ "object (#{object.oid},#{object.gen})"
100
+ end
101
+ object_type = "sub-object of #{object_type}" if object == indirect_object
102
+ puts "WARNING: Validation error for #{object_type}: #{msg} " \
103
+ "#{correctable ? '(correctable)' : ''}"
104
+ end
105
+ doc.trailer.validate(auto_correct: true, &validation_block)
106
+ doc.each(only_current: false, only_loaded: false) do |obj|
107
+ indirect_object = obj
108
+ obj.validate(auto_correct: true, &validation_block)
109
+ end
110
+ end
111
+
82
112
  output_line("File name", file)
83
- output_line("File size", File.stat(file).size.to_s + " bytes")
113
+ output_line("File size", File.stat(file).size.to_s << " bytes")
84
114
  @auto_decrypt && INFO_KEYS.each do |name|
85
115
  next unless doc.trailer.info.key?(name)
86
116
  output_line(name.to_s, doc.trailer.info[name].to_s)
@@ -110,10 +140,29 @@ module HexaPDF
110
140
  else
111
141
  raise
112
142
  end
143
+ rescue HexaPDF::MalformedPDFError => e
144
+ $stderr.puts "Error: PDF file #{file} is damaged and cannot be recovered"
145
+ $stderr.puts " #{e}"
146
+ end
147
+
148
+ # Use custom options if we are checking the PDF file for errors.
149
+ def pdf_options(password)
150
+ if @check_file
151
+ options = {decryption_opts: {password: password}, config: {}}
152
+ HexaPDF::GlobalConfiguration['filter.predictor.strict'] = false
153
+ options[:config]['parser.try_xref_reconstruction'] = true
154
+ options[:config]['parser.on_correctable_error'] = lambda do |_, msg, pos|
155
+ puts "WARNING: Parse error at position #{pos}: #{msg}"
156
+ false
157
+ end
158
+ options
159
+ else
160
+ super
161
+ end
113
162
  end
114
163
 
115
164
  def output_line(header, text) #:nodoc:
116
- puts((header + ":").ljust(COLUMN_WIDTH) << text)
165
+ puts(("#{header}:").ljust(COLUMN_WIDTH) << text)
117
166
  end
118
167
 
119
168
  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-2019 Thomas Leitner
7
+ # Copyright (C) 2014-2020 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
@@ -122,22 +122,22 @@ module HexaPDF
122
122
  case command
123
123
  when /^\d+(,\d+)?$/, 'o', 'object'
124
124
  arg = (command.start_with?('o') ? data.shift : command)
125
- obj = pdf_object_from_string_reference(arg) rescue puts($!.message)
126
- if obj.data.stream && command_parser.verbosity_info?
125
+ obj = pdf_object_from_string_reference(arg) rescue $stderr.puts($!.message)
126
+ if obj&.data&.stream && command_parser.verbosity_info?
127
127
  $stderr.puts("Note: Object also has stream data")
128
128
  end
129
129
  serialize(obj.value, recursive: false) if obj
130
130
 
131
131
  when 'r', 'recursive'
132
132
  obj = if (obj = data.shift)
133
- pdf_object_from_string_reference(obj) rescue puts($!.message)
133
+ pdf_object_from_string_reference(obj) rescue $stderr.puts($!.message)
134
134
  else
135
135
  @doc.trailer
136
136
  end
137
137
  serialize(obj.value, recursive: true) if obj
138
138
 
139
139
  when 's', 'stream', 'raw', 'raw-stream'
140
- if (obj = pdf_object_from_string_reference(data.shift) rescue puts($!.message)) &&
140
+ if (obj = pdf_object_from_string_reference(data.shift) rescue $stderr.puts($!.message)) &&
141
141
  obj.kind_of?(HexaPDF::Stream)
142
142
  source = (command.start_with?('raw') ? obj.stream_source : obj.stream_decoder)
143
143
  while source.alive? && (stream_data = source.resume)
@@ -148,7 +148,7 @@ module HexaPDF
148
148
  end
149
149
 
150
150
  when 'x', 'xref'
151
- if (obj = pdf_object_from_string_reference(data.shift) rescue puts($!.message))
151
+ if (obj = pdf_object_from_string_reference(data.shift) rescue $stderr.puts($!.message))
152
152
  @doc.revisions.reverse_each do |rev|
153
153
  if (xref = rev.xref(obj))
154
154
  puts xref
@@ -178,6 +178,26 @@ module HexaPDF
178
178
  puts str
179
179
  end
180
180
 
181
+ when 'po', 'ps'
182
+ page_number_str = data.shift
183
+ unless page_number_str
184
+ $stderr.puts("Error: Missing PAGE argument to #{command}")
185
+ next
186
+ end
187
+ page_number = parse_pages_specification(page_number_str, @doc.pages.count).first&.first
188
+ unless page_number
189
+ $stderr.puts("Error: Invalid page number #{page_number_str}")
190
+ next
191
+ end
192
+ page = @doc.pages[page_number]
193
+ if command.start_with?('ps')
194
+ $stdout.write(page.contents)
195
+ else
196
+ puts "#{page.oid} #{page.gen} obj"
197
+ serialize(page.value, recursive: false)
198
+ puts "endobj"
199
+ end
200
+
181
201
  when 'pc', 'page-count'
182
202
  puts @doc.pages.count
183
203
 
@@ -217,9 +237,9 @@ module HexaPDF
217
237
  if str.nil?
218
238
  raise "Error: Missing argument object identifier OID[,GEN]"
219
239
  elsif !str.match?(/^\d+(,\d+)?$/)
220
- raise "Error: Invalid argument: Must be of form OID[,GEN]"
240
+ raise "Error: Invalid argument: Must be of form OID[,GEN], not '#{str}'"
221
241
  elsif !(obj = @doc.object(pdf_reference_from_string(str)))
222
- raise "Error: No object with the given object identifier found"
242
+ raise "Error: No object with the given object identifier '#{str}' found"
223
243
  else
224
244
  obj
225
245
  end
@@ -240,7 +260,7 @@ module HexaPDF
240
260
  puts "<<"
241
261
  (recursive ? val.sort : val).each do |k, v|
242
262
  next if v.nil? || (v.respond_to?(:null?) && v.null?)
243
- print ' ' * (indent + 1) + @serializer.serialize_symbol(k) + " "
263
+ print '%s%s ' % [' ' * (indent + 1), @serializer.serialize_symbol(k)]
244
264
  serialize(v, recursive: recursive, seen: seen, indent: indent + 1)
245
265
  puts
246
266
  end
@@ -283,6 +303,8 @@ module HexaPDF
283
303
  ["c[atalog]", "Print the catalog dictionary"],
284
304
  ["t[railer]", "Print the trailer dictionary"],
285
305
  ["p[ages] [RANGE]", "Print information about pages"],
306
+ ["po PAGE", "Print the page object"],
307
+ ["ps PAGE", "Print the content stream of the page"],
286
308
  ["pc | page-count", "Print the number of pages"],
287
309
  ["search REGEXP", "Print objects matching the pattern"],
288
310
  ["h[elp]", "Show the help"],