hexapdf 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -1
  3. data/CONTRIBUTERS +1 -1
  4. data/LICENSE +1 -1
  5. data/Rakefile +1 -1
  6. data/VERSION +1 -1
  7. data/lib/hexapdf.rb +1 -1
  8. data/lib/hexapdf/cli.rb +19 -52
  9. data/lib/hexapdf/cli/command.rb +251 -0
  10. data/lib/hexapdf/cli/{extract.rb → files.rb} +19 -23
  11. data/lib/hexapdf/cli/images.rb +147 -0
  12. data/lib/hexapdf/cli/info.rb +5 -5
  13. data/lib/hexapdf/cli/inspect.rb +13 -12
  14. data/lib/hexapdf/cli/merge.rb +200 -0
  15. data/lib/hexapdf/cli/modify.rb +39 -242
  16. data/lib/hexapdf/cli/optimize.rb +104 -0
  17. data/lib/hexapdf/configuration.rb +1 -1
  18. data/lib/hexapdf/content.rb +1 -1
  19. data/lib/hexapdf/content/canvas.rb +1 -1
  20. data/lib/hexapdf/content/color_space.rb +1 -1
  21. data/lib/hexapdf/content/graphic_object.rb +1 -1
  22. data/lib/hexapdf/content/graphic_object/arc.rb +1 -1
  23. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
  24. data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
  25. data/lib/hexapdf/content/graphics_state.rb +1 -1
  26. data/lib/hexapdf/content/operator.rb +1 -1
  27. data/lib/hexapdf/content/parser.rb +16 -15
  28. data/lib/hexapdf/content/processor.rb +1 -1
  29. data/lib/hexapdf/content/transformation_matrix.rb +1 -1
  30. data/lib/hexapdf/data_dir.rb +1 -1
  31. data/lib/hexapdf/dictionary.rb +1 -1
  32. data/lib/hexapdf/dictionary_fields.rb +1 -1
  33. data/lib/hexapdf/document.rb +1 -1
  34. data/lib/hexapdf/document/files.rb +1 -1
  35. data/lib/hexapdf/document/fonts.rb +1 -1
  36. data/lib/hexapdf/document/images.rb +1 -1
  37. data/lib/hexapdf/document/pages.rb +1 -1
  38. data/lib/hexapdf/encryption.rb +1 -1
  39. data/lib/hexapdf/encryption/aes.rb +1 -1
  40. data/lib/hexapdf/encryption/arc4.rb +1 -1
  41. data/lib/hexapdf/encryption/fast_aes.rb +1 -1
  42. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  43. data/lib/hexapdf/encryption/identity.rb +1 -1
  44. data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
  45. data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
  46. data/lib/hexapdf/encryption/security_handler.rb +1 -1
  47. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
  48. data/lib/hexapdf/error.rb +1 -1
  49. data/lib/hexapdf/filter.rb +1 -1
  50. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  51. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  52. data/lib/hexapdf/filter/dct_decode.rb +1 -1
  53. data/lib/hexapdf/filter/encryption.rb +1 -1
  54. data/lib/hexapdf/filter/flate_decode.rb +1 -1
  55. data/lib/hexapdf/filter/jpx_decode.rb +1 -1
  56. data/lib/hexapdf/filter/lzw_decode.rb +2 -3
  57. data/lib/hexapdf/filter/predictor.rb +11 -11
  58. data/lib/hexapdf/filter/run_length_decode.rb +1 -1
  59. data/lib/hexapdf/font/cmap.rb +1 -1
  60. data/lib/hexapdf/font/cmap/parser.rb +1 -1
  61. data/lib/hexapdf/font/cmap/writer.rb +1 -1
  62. data/lib/hexapdf/font/encoding.rb +1 -1
  63. data/lib/hexapdf/font/encoding/base.rb +1 -1
  64. data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
  65. data/lib/hexapdf/font/encoding/glyph_list.rb +1 -1
  66. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
  67. data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
  68. data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
  69. data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
  70. data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +1 -1
  71. data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
  72. data/lib/hexapdf/font/true_type.rb +2 -1
  73. data/lib/hexapdf/font/true_type/font.rb +1 -1
  74. data/lib/hexapdf/font/true_type/subsetter.rb +186 -0
  75. data/lib/hexapdf/font/true_type/table.rb +8 -4
  76. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  77. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
  78. data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
  79. data/lib/hexapdf/font/true_type/table/glyf.rb +6 -2
  80. data/lib/hexapdf/font/true_type/table/head.rb +2 -2
  81. data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
  82. data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
  83. data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
  84. data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
  85. data/lib/hexapdf/font/true_type/table/name.rb +1 -1
  86. data/lib/hexapdf/font/true_type/table/os2.rb +1 -1
  87. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  88. data/lib/hexapdf/font/true_type_wrapper.rb +56 -8
  89. data/lib/hexapdf/font/type1.rb +1 -1
  90. data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
  91. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  92. data/lib/hexapdf/font/type1/font.rb +1 -1
  93. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  94. data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
  95. data/lib/hexapdf/font/type1_wrapper.rb +1 -1
  96. data/lib/hexapdf/font_loader.rb +1 -1
  97. data/lib/hexapdf/font_loader/from_configuration.rb +6 -3
  98. data/lib/hexapdf/font_loader/standard14.rb +1 -1
  99. data/lib/hexapdf/image_loader.rb +1 -1
  100. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  101. data/lib/hexapdf/image_loader/pdf.rb +1 -1
  102. data/lib/hexapdf/image_loader/png.rb +1 -1
  103. data/lib/hexapdf/importer.rb +1 -1
  104. data/lib/hexapdf/name_tree_node.rb +1 -1
  105. data/lib/hexapdf/number_tree_node.rb +1 -1
  106. data/lib/hexapdf/object.rb +1 -1
  107. data/lib/hexapdf/parser.rb +1 -1
  108. data/lib/hexapdf/rectangle.rb +1 -1
  109. data/lib/hexapdf/reference.rb +1 -1
  110. data/lib/hexapdf/revision.rb +1 -1
  111. data/lib/hexapdf/revisions.rb +13 -15
  112. data/lib/hexapdf/serializer.rb +7 -3
  113. data/lib/hexapdf/stream.rb +1 -1
  114. data/lib/hexapdf/task.rb +1 -1
  115. data/lib/hexapdf/task/dereference.rb +1 -1
  116. data/lib/hexapdf/task/optimize.rb +1 -1
  117. data/lib/hexapdf/tokenizer.rb +12 -12
  118. data/lib/hexapdf/type.rb +1 -1
  119. data/lib/hexapdf/type/catalog.rb +1 -1
  120. data/lib/hexapdf/type/embedded_file.rb +1 -1
  121. data/lib/hexapdf/type/file_specification.rb +1 -1
  122. data/lib/hexapdf/type/font.rb +1 -1
  123. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  124. data/lib/hexapdf/type/font_simple.rb +1 -1
  125. data/lib/hexapdf/type/font_true_type.rb +1 -1
  126. data/lib/hexapdf/type/font_type1.rb +1 -1
  127. data/lib/hexapdf/type/form.rb +1 -1
  128. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  129. data/lib/hexapdf/type/image.rb +187 -1
  130. data/lib/hexapdf/type/info.rb +1 -1
  131. data/lib/hexapdf/type/names.rb +1 -1
  132. data/lib/hexapdf/type/object_stream.rb +1 -1
  133. data/lib/hexapdf/type/page.rb +1 -1
  134. data/lib/hexapdf/type/page_tree_node.rb +6 -1
  135. data/lib/hexapdf/type/resources.rb +1 -1
  136. data/lib/hexapdf/type/trailer.rb +2 -2
  137. data/lib/hexapdf/type/viewer_preferences.rb +1 -1
  138. data/lib/hexapdf/type/xref_stream.rb +22 -18
  139. data/lib/hexapdf/utils/bit_field.rb +1 -1
  140. data/lib/hexapdf/utils/bit_stream.rb +16 -32
  141. data/lib/hexapdf/utils/lru_cache.rb +1 -1
  142. data/lib/hexapdf/utils/math_helpers.rb +1 -1
  143. data/lib/hexapdf/utils/object_hash.rb +1 -1
  144. data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
  145. data/lib/hexapdf/utils/sorted_tree_node.rb +1 -1
  146. data/lib/hexapdf/version.rb +2 -2
  147. data/lib/hexapdf/writer.rb +2 -1
  148. data/lib/hexapdf/xref_section.rb +6 -1
  149. data/man/man1/hexapdf.1 +194 -115
  150. data/test/data/images/greyscale-1bit.png +0 -0
  151. data/test/data/images/greyscale-2bit.png +0 -0
  152. data/test/data/images/greyscale-8bit.png +0 -0
  153. data/test/data/images/indexed-alpha-4bit.png +0 -0
  154. data/test/data/images/truecolour-8bit.png +0 -0
  155. data/test/hexapdf/content/test_operator.rb +8 -8
  156. data/test/hexapdf/content/test_processor.rb +1 -1
  157. data/test/hexapdf/encryption/test_security_handler.rb +1 -1
  158. data/test/hexapdf/font/test_true_type_wrapper.rb +89 -48
  159. data/test/hexapdf/font/true_type/table/test_glyf.rb +1 -0
  160. data/test/hexapdf/font/true_type/test_subsetter.rb +70 -0
  161. data/test/hexapdf/font/true_type/test_table.rb +16 -0
  162. data/test/hexapdf/font_loader/test_from_configuration.rb +7 -0
  163. data/test/hexapdf/test_document.rb +1 -1
  164. data/test/hexapdf/test_object.rb +1 -1
  165. data/test/hexapdf/test_revisions.rb +34 -8
  166. data/test/hexapdf/test_serializer.rb +3 -0
  167. data/test/hexapdf/test_writer.rb +11 -2
  168. data/test/hexapdf/test_xref_section.rb +15 -0
  169. data/test/hexapdf/type/test_image.rb +234 -0
  170. data/test/hexapdf/type/test_object_stream.rb +2 -2
  171. data/test/hexapdf/type/test_trailer.rb +4 -0
  172. data/test/hexapdf/utils/test_bit_stream.rb +69 -0
  173. metadata +14 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 874a5d2e5a2408ceed91a9bc61df138248fbbcd2
4
- data.tar.gz: 894e67110daae93c1479a5ced992a84dabc66cd1
3
+ metadata.gz: ecec6549dbb8ccc811ea329857ff768bb248d9ca
4
+ data.tar.gz: 2e04be0ab4a81fc11491314b18ad95411dc2a0cc
5
5
  SHA512:
6
- metadata.gz: 9d7c18191ea0d64992fc01b05904c3324cc1f32e98fe848bc931970b53f42d9ab6878d4e8c3730a570c607f677dfdaa8a8adebe7ab4c8e279cc13383124dee1b
7
- data.tar.gz: 1e080dd694ad58c3256a647a98a9b03c439ae340311b037fad3b1bdbe436ffbed74fec0c00af3df68540ca32aa75336c328a317a74233428dcf16d8920cc8e75
6
+ metadata.gz: 4a447429d78977fb78336e4f000d4b53d13bb40fd9e27a4d5a02b9a984d6e2fad76dbbe4d8a069236e24d1295b49ec996eb09d416cbe2668018b3331109e4df1
7
+ data.tar.gz: aba92a4c25811ab09146a3dafb544194c4aa42373098137ffe339061c1b779c5239afd092521cfe683d4bc8b9e11e609e108213e94375894c854278dac98be41
@@ -1,4 +1,36 @@
1
- ## 0.2.0 - unreleased
1
+ ## 0.3.0 - 2017-01-25
2
+
3
+ ### Added
4
+
5
+ * TrueType font subsetting support
6
+ * Image extraction ability to CLI via `hexapdf images` command
7
+ * [HexaPDF::Type::Image#write] for writing an image XObject to an IO stream or
8
+ file
9
+ * [HexaPDF::Type::Image#info] for getting image properties of an image XObject
10
+ * CLI option `--[no-]force` to force overwriting existing files
11
+
12
+ ### Changed
13
+
14
+ * Refactor `hexapdf modify` command into three individual commands `modify`,
15
+ `merge` and `optimize`
16
+ * Rename `hexapdf extract` to `hexapdf files` and the option `--indices` to
17
+ `--extract`
18
+ * Show PDF trailer by default with `hexapdf inspect`
19
+ * Refactor CLI command classes to use specialized superclass
20
+ [HexaPDF::CLI::Command]
21
+ * Optimize parsing of PDF files for better performance and memory efficiency
22
+
23
+ ### Fixed
24
+
25
+ * Writing of hybrid-reference PDF files - they are written as standard PDF files
26
+ since all current applications should be able to handle PDF 1.5
27
+ * Serialization of self-referential, indirect PDF objects
28
+ * Performance problem for `hexapdf inspect --pages` when inspecting huge files
29
+ * TrueType compound glyph component offset calculation
30
+ * Parsing of TrueType data type 'fixed'
31
+ * Updating a PDF trailer's ID field when it isn't an array
32
+
33
+ ## 0.2.0 - 2016-11-28
2
34
 
3
35
  ### Added
4
36
 
@@ -1,3 +1,3 @@
1
1
  Count Name
2
2
  ======= ====
3
- 647 Thomas Leitner <t_leitner@gmx.at>
3
+ 682 Thomas Leitner <t_leitner@gmx.at>
data/LICENSE CHANGED
@@ -1,5 +1,5 @@
1
1
  HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
2
- Copyright (C) 2016 Thomas Leitner
2
+ Copyright (C) 2014-2017 Thomas Leitner
3
3
 
4
4
  HexaPDF is free software: you can redistribute it and/or modify it
5
5
  under the terms of the GNU Affero General Public License version 3 as
data/Rakefile CHANGED
@@ -64,7 +64,7 @@ namespace :dev do
64
64
  s.require_path = 'lib'
65
65
  s.executables = ['hexapdf']
66
66
  s.default_executable = 'hexapdf'
67
- s.add_dependency('cmdparse', '~> 3.0', '>= 3.0.1')
67
+ s.add_dependency('cmdparse', '~> 3.0', '>= 3.0.3')
68
68
  s.add_development_dependency('kramdown', '~> 1.0', '>= 1.13.0')
69
69
 
70
70
  s.author = 'Thomas Leitner'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
@@ -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) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 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) 2016 Thomas Leitner
7
+ # Copyright (C) 2014-2017 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
@@ -31,12 +31,14 @@
31
31
  # is created or manipulated using HexaPDF.
32
32
  #++
33
33
 
34
- require 'io/console'
35
34
  require 'cmdparse'
36
35
  require 'hexapdf/cli/info'
37
- require 'hexapdf/cli/extract'
36
+ require 'hexapdf/cli/files'
38
37
  require 'hexapdf/cli/inspect'
39
38
  require 'hexapdf/cli/modify'
39
+ require 'hexapdf/cli/merge'
40
+ require 'hexapdf/cli/optimize'
41
+ require 'hexapdf/cli/images'
40
42
  require 'hexapdf/version'
41
43
  require 'hexapdf/document'
42
44
 
@@ -50,23 +52,35 @@ module HexaPDF
50
52
  def self.run(args = ARGV)
51
53
  Application.new.parse(args)
52
54
  rescue => e
53
- $stderr.puts "An error occurred: #{e.message}"
55
+ $stderr.puts "Problem encountered: #{e.message}"
54
56
  exit(1)
55
57
  end
56
58
 
57
59
  # The CmdParse::CommandParser class that is used for running the CLI application.
58
60
  class Application < CmdParse::CommandParser
59
61
 
62
+ # Specifies whether an operation should be forced. For example, if an existing file should be
63
+ # overwritten.
64
+ attr_reader :force
65
+
60
66
  def initialize #:nodoc:
61
67
  super(handle_exceptions: :no_help)
62
68
  main_command.options.program_name = "hexapdf"
63
69
  main_command.options.version = HexaPDF::VERSION
64
70
  add_command(HexaPDF::CLI::Info.new)
65
- add_command(HexaPDF::CLI::Extract.new)
71
+ add_command(HexaPDF::CLI::Files.new)
72
+ add_command(HexaPDF::CLI::Images.new)
66
73
  add_command(HexaPDF::CLI::Inspect.new)
67
74
  add_command(HexaPDF::CLI::Modify.new)
75
+ add_command(HexaPDF::CLI::Optimize.new)
76
+ add_command(HexaPDF::CLI::Merge.new)
68
77
  add_command(CmdParse::HelpCommand.new)
69
78
  add_command(CmdParse::VersionCommand.new)
79
+
80
+ @force = false
81
+ global_options.on("--[no-]force", "Force overwriting existing files. Default: false") do |f|
82
+ @force = f
83
+ end
70
84
  end
71
85
 
72
86
  def parse(argv = ARGV) #:nodoc:
@@ -74,53 +88,6 @@ module HexaPDF
74
88
  super
75
89
  end
76
90
 
77
- PAGE_NUMBER_SPEC = "([1-9]\\d*|e)".freeze #:nodoc:
78
- ROTATE_MAP = {'l' => -90, 'r' => 90, 'd' => 180, 'n' => :none}.freeze #:nodoc:
79
-
80
- # Parses the pages specification string and returns an array of tuples containing a page
81
- # number and a rotation value (either -90, 90, 180 or :none).
82
- #
83
- # The parameter +count+ needs to be the total number of pages in the document.
84
- def parse_pages_specification(range, count)
85
- range.split(',').each_with_object([]) do |str, arr|
86
- case str
87
- when /\A#{PAGE_NUMBER_SPEC}(l|r|d|n)?\z/o
88
- arr << [($1 == 'e' ? count : str.to_i) - 1, ROTATE_MAP[$2]]
89
- when /\A#{PAGE_NUMBER_SPEC}-#{PAGE_NUMBER_SPEC}(?:\/([1-9]\d*))?(l|r|d|n)?\z/
90
- start_nr = ($1 == 'e' ? count : $1.to_i) - 1
91
- end_nr = ($2 == 'e' ? count : $2.to_i) - 1
92
- step = ($3 ? $3.to_i : 1) * (start_nr > end_nr ? -1 : 1)
93
- rotation = ROTATE_MAP[$4]
94
- start_nr.step(to: end_nr, by: step) {|n| arr << [n, rotation]}
95
- else
96
- raise OptionParser::InvalidArgument, "invalid page range format: #{str}"
97
- end
98
- end
99
- end
100
-
101
- # Reads a password from the standard input and falls back to the console if needed.
102
- #
103
- # The optional argument +prompt+ can be used to customize the prompt when reading from the
104
- # console.
105
- def read_password(prompt = "Password")
106
- if $stdin.tty?
107
- read_from_console(prompt)
108
- else
109
- pwd = $stdin.gets
110
- pwd = read_from_console(prompt) unless pwd
111
- pwd.chomp
112
- end
113
- end
114
-
115
- private
116
-
117
- # Displays the given prompt, reads from the console without echo and returns the read string.
118
- def read_from_console(prompt)
119
- IO.console.write("#{prompt}: ")
120
- str = IO.console.noecho {|io| io.gets.chomp}
121
- puts
122
- str
123
- end
124
91
  end
125
92
 
126
93
  end
@@ -0,0 +1,251 @@
1
+ # -*- encoding: utf-8 -*-
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-2017 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
+
34
+ require 'io/console'
35
+ require 'ostruct'
36
+ require 'cmdparse'
37
+ require 'hexapdf/document'
38
+
39
+ module HexaPDF
40
+ module CLI
41
+
42
+ # Base class for all hexapdf commands. It provides utility methods needed by the individual
43
+ # commands.
44
+ class Command < CmdParse::Command
45
+
46
+ def initialize(*args, &block) #:nodoc:
47
+ super
48
+ @out_options = OpenStruct.new
49
+ @out_options.compact = true
50
+ @out_options.compress_pages = false
51
+ @out_options.object_streams = :preserve
52
+ @out_options.xref_streams = :preserve
53
+ @out_options.streams = :preserve
54
+
55
+ @out_options.encryption = :preserve
56
+ @out_options.enc_user_pwd = @out_options.enc_owner_pwd = nil
57
+ @out_options.enc_key_length = 128
58
+ @out_options.enc_algorithm = :aes
59
+ @out_options.enc_force_v4 = false
60
+ @out_options.enc_permissions = []
61
+ end
62
+
63
+ protected
64
+
65
+ # Checks whether the given output file exists and raises an error if it does and
66
+ # HexaPDF::CLI#force is not set.
67
+ def maybe_raise_on_existing_file(filename)
68
+ if !command_parser.force && File.exist?(filename)
69
+ raise "Output file '#{filename}' already exists, not overwriting. Use --force to " \
70
+ "force writing"
71
+ end
72
+ end
73
+
74
+ # Defines the optimization options.
75
+ #
76
+ # See: #out_options, #apply_optimization_options
77
+ def define_optimization_options
78
+ options.on("--[no-]compact", "Delete unnecessary PDF objects (default: " \
79
+ "#{@out_options.compact})") do |c|
80
+ @out_options.compact = c
81
+ end
82
+ options.on("--object-streams MODE", [:generate, :preserve, :delete],
83
+ "Handling of object streams (either generate, preserve or delete; " \
84
+ "default: #{@out_options.object_streams})") do |os|
85
+ @out_options.object_streams = os
86
+ end
87
+ options.on("--xref-streams MODE", [:generate, :preserve, :delete],
88
+ "Handling of cross-reference streams (either generate, preserve or delete; " \
89
+ "default: #{@out_options.xref_streams})") do |x|
90
+ @out_options.xref_streams = x
91
+ end
92
+ options.on("--streams MODE", [:compress, :preserve, :uncompress],
93
+ "Handling of stream data (either compress, preserve or uncompress; default: " \
94
+ "#{@out_options.streams})") do |streams|
95
+ @out_options.streams = streams
96
+ end
97
+ options.on("--[no-]compress-pages", "Recompress page content streams (may take a long " \
98
+ "time; default: #{@out_options.compress_pages})") do |c|
99
+ @out_options.compress_pages = c
100
+ end
101
+ end
102
+
103
+ # Defines the encryption options.
104
+ #
105
+ # See: #out_options, #apply_encryption_options
106
+ def define_encryption_options
107
+ options.on("--decrypt", "Remove any encryption") do
108
+ @out_options.encryption = :remove
109
+ end
110
+ options.on("--encrypt", "Encrypt the output file") do
111
+ @out_options.encryption = :add
112
+ end
113
+ options.on("--owner-password PASSWORD", String, "The owner password to be set on the " \
114
+ "output file (use - for reading from standard input)") do |pwd|
115
+ @out_options.encryption = :add
116
+ @out_options.enc_owner_pwd = (pwd == '-' ? read_password("Owner password") : pwd)
117
+ end
118
+ options.on("--user-password PASSWORD", String, "The user password to be set on the " \
119
+ "output file (use - for reading from standard input)") do |pwd|
120
+ @out_options.encryption = :add
121
+ @out_options.enc_user_pwd = (pwd == '-' ? read_password("User password") : pwd)
122
+ end
123
+ options.on("--algorithm ALGORITHM", [:aes, :arc4],
124
+ "The encryption algorithm: aes or arc4 (default: " \
125
+ "#{@out_options.enc_algorithm})") do |a|
126
+ @out_options.encryption = :add
127
+ @out_options.enc_algorithm = a
128
+ end
129
+ options.on("--key-length BITS", Integer,
130
+ "The encryption key length in bits (default: " \
131
+ "#{@out_options.enc_key_length})") do |i|
132
+ @out_options.encryption = :add
133
+ @out_options.enc_key_length = i
134
+ end
135
+ options.on("--force-V4",
136
+ "Force use of encryption version 4 if key length=128 and algorithm=arc4") do
137
+ @out_options.encryption = :add
138
+ @out_options.enc_force_v4 = true
139
+ end
140
+ syms = HexaPDF::Encryption::StandardSecurityHandler::Permissions::SYMBOL_TO_PERMISSION.keys
141
+ options.on("--permissions PERMS", Array,
142
+ "Comma separated list of permissions to be set on the output file. Possible " \
143
+ "values: #{syms.join(', ')}") do |perms|
144
+ perms.map! do |perm|
145
+ unless syms.include?(perm.to_sym)
146
+ raise OptionParser::InvalidArgument, "#{perm} (invalid permission name)"
147
+ end
148
+ perm.to_sym
149
+ end
150
+ @out_options.encryption = :add
151
+ @out_options.enc_permissions = perms
152
+ end
153
+ end
154
+
155
+ # Applies the optimization options to the given HexaPDF::Document instance.
156
+ #
157
+ # See: #define_optimization_options
158
+ def apply_optimization_options(doc)
159
+ doc.task(:optimize, compact: @out_options.compact,
160
+ object_streams: @out_options.object_streams,
161
+ xref_streams: @out_options.xref_streams,
162
+ compress_pages: @out_options.compress_pages)
163
+ handle_streams(doc) unless @out_options.streams == :preserve
164
+ end
165
+
166
+ IGNORED_FILTERS = { #:nodoc:
167
+ CCITTFaxDecode: true, JBIG2Decode: true, DCTDecode: true, JPXDecode: true, Crypt: true
168
+ }.freeze
169
+
170
+ # Applies the chosen stream mode to all streams.
171
+ def handle_streams(doc)
172
+ doc.each(current: false) do |obj|
173
+ next if !obj.respond_to?(:set_filter) || Array(obj[:Filter]).any? {|f| IGNORED_FILTERS[f]}
174
+ if @out_options.streams == :compress
175
+ obj.set_filter(:FlateDecode)
176
+ else
177
+ obj.set_filter(nil)
178
+ end
179
+ end
180
+ end
181
+
182
+ # Applies the encryption related options to the given HexaPDF::Document instance.
183
+ #
184
+ # See: #define_encryption_options
185
+ def apply_encryption_options(doc)
186
+ if @out_options.encryption == :add
187
+ doc.encrypt(algorithm: @out_options.enc_algorithm,
188
+ key_length: @out_options.enc_key_length,
189
+ force_V4: @out_options.enc_force_v4,
190
+ permissions: @out_options.enc_permissions,
191
+ owner_password: @out_options.enc_owner_pwd,
192
+ user_password: @out_options.enc_user_pwd)
193
+ elsif @out_options.encryption == :remove
194
+ doc.encrypt(name: nil)
195
+ end
196
+ end
197
+
198
+ PAGE_NUMBER_SPEC = "([1-9]\\d*|e)".freeze #:nodoc:
199
+ ROTATE_MAP = {'l' => -90, 'r' => 90, 'd' => 180, 'n' => :none}.freeze #:nodoc:
200
+
201
+ # Parses the pages specification string and returns an array of tuples containing a page
202
+ # number and a rotation value (either -90, 90, 180 or :none).
203
+ #
204
+ # The parameter +count+ needs to be the total number of pages in the document.
205
+ #
206
+ # For details on the pages specification see the hexapdf(1) manual page.
207
+ def parse_pages_specification(range, count)
208
+ range.split(',').each_with_object([]) do |str, arr|
209
+ case str
210
+ when /\A#{PAGE_NUMBER_SPEC}(l|r|d|n)?\z/o
211
+ arr << [($1 == 'e' ? count : str.to_i) - 1, ROTATE_MAP[$2]]
212
+ when /\A#{PAGE_NUMBER_SPEC}-#{PAGE_NUMBER_SPEC}(?:\/([1-9]\d*))?(l|r|d|n)?\z/
213
+ start_nr = ($1 == 'e' ? count : $1.to_i) - 1
214
+ end_nr = ($2 == 'e' ? count : $2.to_i) - 1
215
+ step = ($3 ? $3.to_i : 1) * (start_nr > end_nr ? -1 : 1)
216
+ rotation = ROTATE_MAP[$4]
217
+ start_nr.step(to: end_nr, by: step) {|n| arr << [n, rotation]}
218
+ else
219
+ raise OptionParser::InvalidArgument, "invalid page range format: #{str}"
220
+ end
221
+ end
222
+ end
223
+
224
+ # Reads a password from the standard input and falls back to the console if needed.
225
+ #
226
+ # The optional argument +prompt+ can be used to customize the prompt when reading from the
227
+ # console.
228
+ def read_password(prompt = "Password")
229
+ if $stdin.tty?
230
+ read_from_console(prompt)
231
+ else
232
+ pwd = $stdin.gets
233
+ pwd = read_from_console(prompt) unless pwd
234
+ pwd.chomp
235
+ end
236
+ end
237
+
238
+ private
239
+
240
+ # Displays the given prompt, reads from the console without echo and returns the read string.
241
+ def read_from_console(prompt)
242
+ IO.console.write("#{prompt}: ")
243
+ str = IO.console.noecho {|io| io.gets.chomp}
244
+ puts
245
+ str
246
+ end
247
+
248
+ end
249
+
250
+ end
251
+ end