hexapdf 0.9.3 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/CONTRIBUTERS +2 -1
  4. data/VERSION +1 -1
  5. data/lib/hexapdf/cli/command.rb +9 -5
  6. data/lib/hexapdf/cli/images.rb +68 -13
  7. data/lib/hexapdf/cli/inspect.rb +201 -71
  8. data/lib/hexapdf/content/canvas.rb +1 -1
  9. data/lib/hexapdf/dictionary.rb +15 -1
  10. data/lib/hexapdf/dictionary_fields.rb +5 -4
  11. data/lib/hexapdf/document.rb +15 -6
  12. data/lib/hexapdf/encryption/security_handler.rb +3 -2
  13. data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +165 -165
  14. data/lib/hexapdf/font/true_type_wrapper.rb +2 -2
  15. data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
  16. data/lib/hexapdf/image_loader/jpeg.rb +13 -11
  17. data/lib/hexapdf/reference.rb +5 -0
  18. data/lib/hexapdf/revision.rb +14 -0
  19. data/lib/hexapdf/serializer.rb +6 -6
  20. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  21. data/lib/hexapdf/type/image.rb +3 -1
  22. data/lib/hexapdf/version.rb +1 -1
  23. data/lib/hexapdf/xref_section.rb +8 -0
  24. data/man/man1/hexapdf.1 +88 -20
  25. data/test/data/images/truecolour-alpha-8bit.png +0 -0
  26. data/test/data/images/ycck.jpg +0 -0
  27. data/test/hexapdf/content/test_canvas.rb +2 -2
  28. data/test/hexapdf/document/test_images.rb +7 -5
  29. data/test/hexapdf/encryption/test_security_handler.rb +13 -0
  30. data/test/hexapdf/image_loader/test_jpeg.rb +10 -0
  31. data/test/hexapdf/image_loader/test_png.rb +3 -2
  32. data/test/hexapdf/test_dictionary.rb +9 -0
  33. data/test/hexapdf/test_dictionary_fields.rb +15 -0
  34. data/test/hexapdf/test_document.rb +5 -2
  35. data/test/hexapdf/test_reference.rb +4 -0
  36. data/test/hexapdf/test_revision.rb +12 -0
  37. data/test/hexapdf/test_writer.rb +5 -5
  38. data/test/hexapdf/test_xref_section.rb +11 -0
  39. data/test/hexapdf/type/test_form.rb +1 -1
  40. data/test/hexapdf/type/test_image.rb +28 -19
  41. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a1011a745720b76eaa963d0a54b6807e732fe6ecd3bcb029870309e42296bbe9
4
- data.tar.gz: 0aed16b283825785a7039ebca11a61da853c7fb01e8d2fadc9e5d7a722e56754
3
+ metadata.gz: e7d800154c0525b724aa0b1a91e360aff2d443bd56e260b5b051a06d605cdb9c
4
+ data.tar.gz: d658fa5ff09f12a169156ee091f66b189874ae4b847cb4effa8d2c60b5f836b1
5
5
  SHA512:
6
- metadata.gz: 70bee8b172f67ab59559ec1b0d740d767dad0db0a5d30476bdbfd71f80f443b31fc8b98bfd62a6d632411c246aa04af9c4203c443f4affc7b0307694bd5b725b
7
- data.tar.gz: 04002b0d6728a37c499c1453a4068161b6c3c2631e318981559e52207681ae268de39b6a5b9127b39ee009be079c06e8ea761c2633d1f586efb0294a923e7a61
6
+ metadata.gz: b600fb0c600a0093e08d5ed3a4d16c2bfee637b0d338783c663f664aa7b4eb5384b20f064137c26d83f4269ed213b5d46c390421946d747f16be9d7e5600f381
7
+ data.tar.gz: 31f7c75f80db9b467f0fe8582616a9d136b9da86995cd6c57c76678a4e916fef0e73a4ad22ef53d8e8c36ce7ab620e303d6d05268d3ce6d551bbc33b67c7f959
@@ -1,3 +1,38 @@
1
+ ## 0.10.0 - 2019-10-02
2
+
3
+ ### Added
4
+
5
+ * [HexaPDF::Reference#to_s] to return the serialized form of the PDF reference
6
+ * [HexaPDF::Revision#xref] for getting cross-reference entries
7
+ * HexaPDF::XRefSection::Entry#to_s to return a description of the
8
+ cross-reference entry
9
+
10
+ ### Changed
11
+
12
+ * Enhanced the `hexapdf images` command to also show information on PPI (pixels
13
+ per inch) and size
14
+ * Completely revamped the `hexapdf inspect` command with an interactive mode,
15
+ structure output, cross-reference entry output and object search
16
+ * Output of validation problem messages for `hexapdf` command to include more
17
+ information
18
+ * The Validation feature to automatically correct String-for-Symbol and
19
+ Symbol-for-String problems
20
+
21
+ ### Fixed
22
+
23
+ * [HexaPDF::Document#wrap] to better handle subtype mappings in case of unknown
24
+ type information
25
+ * [HexaPDF::DictionaryFields::DictionaryConverter] to not allow conversion to a
26
+ [HexaPDF::Stream] subclass from objects without stream data
27
+ * Import of JPEG images with YCCK color encoding
28
+ * Export of images without `/FlateDecode` filter or `/DecodeParms` to PNG files
29
+ * Mistyped name of field type for field `/Popup` of
30
+ [HexaPDF::Type::Annotations::MarkupAnnotation]
31
+ * Loading and saving of encrypted and signed PDFs
32
+ * CLI commands that optimize font data structures won't crash when encountering
33
+ invalid font objects
34
+
35
+
1
36
  ## 0.9.3 - 2019-06-13
2
37
 
3
38
  ### Changed
@@ -1,4 +1,5 @@
1
1
  Count Name
2
2
  ======= ====
3
- 1041 Thomas Leitner <t_leitner@gmx.at>
3
+ 1069 Thomas Leitner <t_leitner@gmx.at>
4
+ 1 Stanislav (Stas) Katkov <sk@skylup.com>
4
5
  1 Daniel Kraus <bovender@bovender.de>
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.3
1
+ 0.10.0
@@ -120,7 +120,7 @@ module HexaPDF
120
120
  if out_file
121
121
  doc.validate(auto_correct: true) do |object, msg, correctable|
122
122
  if command_parser.strict && !correctable
123
- raise "Validation error: #{msg}"
123
+ raise "Validation error for object (#{object.oid},#{object.gen}): #{msg}"
124
124
  elsif command_parser.verbosity_info?
125
125
  $stderr.puts "#{correctable ? 'Corrected' : 'Ignored'} validation problem " \
126
126
  "for object (#{object.oid},#{object.gen}): #{msg}"
@@ -247,7 +247,7 @@ module HexaPDF
247
247
  # Applies the chosen stream mode to the given object.
248
248
  def optimize_stream(obj)
249
249
  return if @out_options.streams == :preserve || !obj.respond_to?(:set_filter) ||
250
- Array(obj[:Filter]).any? {|f| IGNORED_FILTERS[f] }
250
+ Array(obj[:Filter]).any? {|f| IGNORED_FILTERS[f] }
251
251
 
252
252
  obj.set_filter(@out_options.streams == :compress ? :FlateDecode : nil)
253
253
  end
@@ -255,14 +255,18 @@ module HexaPDF
255
255
  # Optimize the object if it is a font object.
256
256
  def optimize_font(obj)
257
257
  return unless @out_options.optimize_fonts && obj.kind_of?(HexaPDF::Type::Font) &&
258
- (obj[:Subtype] == :TrueType ||
259
- (obj[:Subtype] == :Type0 && obj.descendant_font[:Subtype] == :CIDFontType2)) &&
260
- obj.embedded?
258
+ (obj[:Subtype] == :TrueType ||
259
+ (obj[:Subtype] == :Type0 && obj.descendant_font[:Subtype] == :CIDFontType2)) &&
260
+ obj.embedded?
261
261
 
262
262
  font = HexaPDF::Font::TrueType::Font.new(StringIO.new(obj.font_file.stream))
263
263
  data = HexaPDF::Font::TrueType::Optimizer.build_for_pdf(font)
264
264
  obj.font_file.stream = data
265
265
  obj.font_file[:Length1] = data.size
266
+ rescue StandardError => e
267
+ if command_parser.verbosity_info?
268
+ $stderr.puts "Error optimizing font object (#{obj.oid},#{obj.gen}): #{e.message}"
269
+ end
266
270
  end
267
271
 
268
272
  # Applies the encryption related options to the given HexaPDF::Document instance.
@@ -45,6 +45,41 @@ module HexaPDF
45
45
  # See: HexaPDF::Type::Image
46
46
  class Images < Command
47
47
 
48
+ # Extracts the PPI (pixel per inch) information for each image of a content stream.
49
+ class ImageLocationProcessor < HexaPDF::Content::Processor
50
+
51
+ # The mapping of XObject name to [x_ppi, y_ppi].
52
+ attr_reader :result
53
+
54
+ # Initialize the processor with the names of the images for which the PPI should be
55
+ # determined.
56
+ def initialize(names, user_unit)
57
+ super()
58
+ @names = names
59
+ @user_unit = user_unit
60
+ @result = {}
61
+ end
62
+
63
+ # Determine the PPI in x- and y-directions of the specified images.
64
+ def paint_xobject(name)
65
+ super
66
+ return unless @names.delete(name)
67
+ xobject = resources.xobject(name)
68
+ return unless xobject[:Subtype] == :Image
69
+
70
+ w, h = xobject.width, xobject.height
71
+ llx, lly = graphics_state.ctm.evaluate(0, 0).map {|i| i * @user_unit }
72
+ lrx, lry = graphics_state.ctm.evaluate(1, 0).map {|i| i * @user_unit }
73
+ ulx, uly = graphics_state.ctm.evaluate(0, 1).map {|i| i * @user_unit }
74
+
75
+ x_ppi = 72.0 * w / Math.sqrt((lrx - llx)**2 + (lry - lly)**2)
76
+ y_ppi = 72.0 * h / Math.sqrt((ulx - llx)**2 + (uly - lly)**2)
77
+ @result[name] = [x_ppi.round, y_ppi.round]
78
+ raise StopIteration if @names.empty?
79
+ end
80
+
81
+ end
82
+
48
83
  def initialize #:nodoc:
49
84
  super('images', takes_commands: false)
50
85
  short_desc("List or extract images from a PDF file")
@@ -94,16 +129,17 @@ module HexaPDF
94
129
 
95
130
  # Outputs a table with the images of the PDF document.
96
131
  def list_images(doc)
97
- printf("%5s %5s %9s %6s %6s %5s %4s %3s %5s %8s\n",
98
- "index", "page", "oid", "width", "height", "color", "comp", "bpc", "type",
99
- "writable")
100
- puts("-" * 65)
101
- each_image(doc) do |image, index, pindex|
132
+ printf("%5s %5s %9s %6s %6s %5s %4s %3s %5s %5s %6s %5s %8s\n",
133
+ "index", "page", "oid", "width", "height", "color", "comp", "bpc",
134
+ "x-ppi", "y-ppi", "size", "type", "writable")
135
+ puts("-" * 77)
136
+ each_image(doc) do |image, index, pindex, (x_ppi, y_ppi)|
102
137
  info = image.info
103
- printf("%5i %5s %9s %6i %6i %5s %4i %3i %5s %8s\n",
138
+ size = human_readable_file_size(image[:Length] + image[:SMask]&.[](:Length).to_i)
139
+ printf("%5i %5s %9s %6i %6i %5s %4i %3i %5s %5s %6s %5s %8s\n",
104
140
  index, pindex || '-', "#{image.oid},#{image.gen}", info.width, info.height,
105
- info.color_space, info.components, info.bits_per_component, info.type,
106
- info.writable)
141
+ info.color_space, info.components, info.bits_per_component, x_ppi, y_ppi,
142
+ size, info.type, info.writable)
107
143
  end
108
144
  end
109
145
 
@@ -131,11 +167,21 @@ module HexaPDF
131
167
  seen = {}
132
168
 
133
169
  doc.pages.each_with_index do |page, pindex|
134
- page.resources[:XObject]&.each do |_name, xobject|
170
+ image_names = []
171
+ xobjects = page.resources[:XObject]
172
+
173
+ xobjects&.each&.map do |name, xobject|
174
+ image_names << name if xobject[:Subtype] == :Image && !xobject[:ImageMask]
175
+ end
176
+
177
+ processor = ImageLocationProcessor.new(image_names, page[:UserUnit] || 1)
178
+ page.process_contents(processor)
179
+ processor.result.each do |name, ppi|
180
+ xobject = xobjects[name]
135
181
  if seen[xobject]
136
- yield(xobject, seen[xobject], pindex + 1)
137
- elsif xobject[:Subtype] == :Image && !xobject[:ImageMask]
138
- yield(xobject, index, pindex + 1)
182
+ yield(xobject, seen[xobject], pindex + 1, ppi)
183
+ else
184
+ yield(xobject, index, pindex + 1, ppi)
139
185
  seen[xobject] = index
140
186
  index += 1
141
187
  end
@@ -145,12 +191,21 @@ module HexaPDF
145
191
  if @search
146
192
  doc.images.each do |image|
147
193
  next if seen[image]
148
- yield(image, index, nil)
194
+ yield(image, index, nil, nil)
149
195
  index += 1
150
196
  end
151
197
  end
152
198
  end
153
199
 
200
+ # Returns the human readable file size.
201
+ def human_readable_file_size(size)
202
+ case size
203
+ when 0..9999 then "#{size}B"
204
+ when 10_000..999_999 then "#{(size / 1024.to_f).round(1)}K"
205
+ else "#{(size.to_f / 1024 / 1024).round(1)}M"
206
+ end
207
+ end
208
+
154
209
  end
155
210
 
156
211
  end
@@ -50,98 +50,148 @@ module HexaPDF
50
50
  needs to inspect the internal object structure or a stream of a PDF file. A PDF object is
51
51
  always shown in the PDF syntax.
52
52
 
53
- If no option is given, the PDF trailer is shown. Otherwise the various, mutually exclusive
54
- display options define the shown content. If multiple such options are specified only the
55
- last is respected.
56
- EOF
53
+ If no arguments are given, the interactive mode is started. Otherwise the arguments are
54
+ interpreted as interactive mode commands and executed. It is possible to specify more than
55
+ one command in this way by separating them with semicolons, or whitespace in case the
56
+ number of command arguments is fixed.
57
57
 
58
- options.on("--catalog", "Show the PDF catalog dictionary.") do
59
- @exec = :catalog
60
- end
61
- options.on("-c", "--page-count", "Print the number of pages.") do
62
- @exec = :page_count
63
- end
64
- options.on("--pages [PAGES]", "Show the pages with their object and generation numbers " \
65
- "and their associated content streams. If the optional argument PAGES is " \
66
- "specified, only the specified pages are listed.") do |range|
67
- @exec = :pages
68
- @param = range || '1-e'
69
- end
70
- options.on("-o", "--object OID[,GEN]", "Show the object with the given object and " \
71
- "generation numbers. The generation number defaults to 0 if not given.") do |str|
72
- @exec = :object
73
- @param = str
74
- end
75
- options.on("-s", "--stream OID[,GEN]", "Show the filtered stream data (add --raw to get " \
76
- "the raw stream data) of the object with the given object and generation " \
77
- "numbers. The generation number defaults to 0 if not given.") do |str|
78
- @exec = :stream
79
- @param = str
80
- end
81
- options.on("--raw", "Modifies --stream to show the raw stream data instead of the " \
82
- "filtered one.") do
83
- @raw = true
84
- end
58
+ EOF
85
59
 
86
- options.separator("")
87
60
  options.on("--password PASSWORD", "-p", String,
88
61
  "The password for decryption. Use - for reading from standard input.") do |pwd|
89
62
  @password = (pwd == '-' ? read_password : pwd)
90
63
  end
91
64
 
92
65
  @password = nil
93
- @exec = :trailer
94
- @param = nil
95
- @raw = nil
66
+ @serializer = HexaPDF::Serializer.new
96
67
  end
97
68
 
98
- def execute(file) #:nodoc:
99
- with_document(file, password: @password) {|doc| send("do_#{@exec}", doc) }
69
+ def execute(file, *commands) #:nodoc:
70
+ with_document(file, password: @password) do |doc|
71
+ @doc = doc
72
+ if commands.empty?
73
+ while true
74
+ print "cmd> "
75
+ input = $stdin.gets
76
+ (puts; break) unless input
77
+ commands = input.scan(/(["'])(.+?)\1|(\S+)/).map {|a| a[1] || a[2] }
78
+ break if execute_commands(commands)
79
+ end
80
+ else
81
+ execute_commands(commands)
82
+ end
83
+ end
100
84
  end
101
85
 
102
86
  private
103
87
 
104
- def do_catalog(doc) #:nodoc:
105
- puts HexaPDF::Serializer.new.serialize(doc.catalog)
106
- end
88
+ def execute_commands(data) #:nodoc:
89
+ data.map! {|item| item == ";" ? nil : item }
90
+ until data.empty?
91
+ command = data.shift || next
92
+ case command
93
+ when /^\d+(,\d+)?$/, 'o', 'object'
94
+ arg = (command.start_with?('o') ? data.shift : command)
95
+ obj = pdf_object_from_string_reference(arg) rescue puts($!.message)
96
+ if obj.data.stream && command_parser.verbosity_info?
97
+ $stderr.puts("Note: Object also has stream data")
98
+ end
99
+ serialize(obj.value, recursive: false) if obj
107
100
 
108
- def do_trailer(doc) #:nodoc:
109
- puts HexaPDF::Serializer.new.serialize(doc.trailer)
110
- end
101
+ when 'r', 'recursive'
102
+ obj = if (obj = data.shift)
103
+ pdf_object_from_string_reference(obj) rescue puts($!.message)
104
+ else
105
+ @doc.trailer
106
+ end
107
+ serialize(obj.value, recursive: true) if obj
111
108
 
112
- def do_page_count(doc) #:nodoc:
113
- puts doc.pages.count
114
- end
109
+ when 's', 'stream', 'raw', 'raw-stream'
110
+ if (obj = pdf_object_from_string_reference(data.shift) rescue puts($!.message)) &&
111
+ obj.kind_of?(HexaPDF::Stream)
112
+ source = (command.start_with?('raw') ? obj.stream_source : obj.stream_decoder)
113
+ while source.alive? && (stream_data = source.resume)
114
+ $stdout.write(stream_data)
115
+ end
116
+ elsif command_parser.verbosity_info?
117
+ $stderr.puts("Note: Object has no stream data")
118
+ end
115
119
 
116
- def do_pages(doc) #:nodoc:
117
- pages = parse_pages_specification(@param, doc.pages.count)
118
- page_list = doc.pages.to_a
119
- pages.each do |index, _|
120
- page = page_list[index]
121
- str = +"page #{index + 1} (#{page.oid},#{page.gen}): "
122
- str << Array(page[:Contents]).map {|c| "#{c.oid},#{c.gen}" }.join(" ")
123
- puts str
124
- end
125
- end
120
+ when 'x', 'xref'
121
+ if (obj = pdf_object_from_string_reference(data.shift) rescue puts($!.message))
122
+ @doc.revisions.reverse_each do |rev|
123
+ if (xref = rev.xref(obj))
124
+ puts xref
125
+ break
126
+ end
127
+ end
128
+ end
129
+
130
+ when 'c', 'catalog'
131
+ serialize(@doc.catalog.value, recursive: false)
132
+
133
+ when 't', 'trailer'
134
+ serialize(@doc.trailer.value, recursive: false)
135
+
136
+ when 'p', 'pages'
137
+ begin
138
+ pages = parse_pages_specification(data.shift || '1-e', @doc.pages.count)
139
+ rescue StandardError
140
+ $stderr.puts("Error: Invalid page range argument")
141
+ next
142
+ end
143
+ page_list = @doc.pages.to_a
144
+ pages.each do |index, _|
145
+ page = page_list[index]
146
+ str = +"page #{index + 1} (#{page.oid},#{page.gen}): "
147
+ str << Array(page[:Contents]).map {|c| "#{c.oid},#{c.gen}" }.join(" ")
148
+ puts str
149
+ end
150
+
151
+ when 'pc', 'page-count'
152
+ puts @doc.pages.count
126
153
 
127
- def do_object(doc) #:nodoc:
128
- object = doc.object(pdf_reference_from_string(@param))
129
- return unless object
130
- if object.data.stream && command_parser.verbosity_info?
131
- $stderr.puts("Note: Object also has stream data")
154
+ when 'search'
155
+ regexp = data.shift
156
+ unless regexp
157
+ $stderr.puts("Error: Missing argument regexp")
158
+ next
159
+ end
160
+ re = Regexp.new(regexp, Regexp::IGNORECASE)
161
+ @doc.each do |object|
162
+ if @serializer.serialize(object.value).match?(re)
163
+ puts "#{object.oid} #{object.gen} obj"
164
+ serialize(object.value, recursive: false)
165
+ puts "endobj"
166
+ end
167
+ end
168
+
169
+ when 'q', 'quit'
170
+ return true
171
+
172
+ when 'h', 'help'
173
+ puts COMMAND_DESCRIPTIONS.map {|cmd, desc| cmd.ljust(35) << desc }.join("\n")
174
+
175
+ else
176
+ if command
177
+ $stderr.puts("Error: Unknown command '#{command}' - enter 'h' for a list of commands")
178
+ end
179
+ end
132
180
  end
133
- puts HexaPDF::Serializer.new.serialize(object.value)
181
+
182
+ false
134
183
  end
135
184
 
136
- def do_stream(doc) #:nodoc:
137
- object = doc.object(pdf_reference_from_string(@param))
138
- if object.kind_of?(HexaPDF::Stream)
139
- source = (@raw ? object.stream_source : object.stream_decoder)
140
- while source.alive? && (data = source.resume)
141
- $stdout.write(data)
142
- end
143
- elsif command_parser.verbosity_info?
144
- $stderr.puts("Note: Object has no stream data")
185
+ # Resolves the PDF object from the given string reference and returns it.
186
+ def pdf_object_from_string_reference(str)
187
+ if str.nil?
188
+ raise "Error: Missing argument object identifier OID[,GEN]"
189
+ elsif !str.match?(/^\d+(,\d+)?$/)
190
+ raise "Error: Invalid argument: Must be of form OID[,GEN]"
191
+ elsif !(obj = @doc.object(pdf_reference_from_string(str)))
192
+ raise "Error: No object with the given object identifier found"
193
+ else
194
+ obj
145
195
  end
146
196
  end
147
197
 
@@ -151,6 +201,86 @@ module HexaPDF
151
201
  HexaPDF::Reference.new(oid, gen || 0)
152
202
  end
153
203
 
204
+ # Prints the serialized value to the standard output. If +recursive+ is +true+, then the whole
205
+ # object tree is printed, with object references to already printed objects replaced by
206
+ # specially generated PDF references.
207
+ def serialize(val, recursive: true, seen: {}, indent: 0) #:nodoc:
208
+ case val
209
+ when Hash
210
+ puts "<<"
211
+ (recursive ? val.sort : val).each do |k, v|
212
+ next if v.nil? || (v.respond_to?(:null?) && v.null?)
213
+ print ' ' * (indent + 1) + @serializer.serialize_symbol(k) + " "
214
+ serialize(v, recursive: recursive, seen: seen, indent: indent + 1)
215
+ puts
216
+ end
217
+ print "#{' ' * indent}>>"
218
+ when Array
219
+ print "["
220
+ val.each do |v|
221
+ serialize(v, recursive: recursive, seen: seen, indent: indent)
222
+ print " "
223
+ end
224
+ print "]"
225
+ when HexaPDF::Reference
226
+ serialize(@doc.object(val), recursive: recursive, seen: seen, indent: indent)
227
+ when HexaPDF::Object
228
+ if !recursive
229
+ if val.indirect?
230
+ print "#{val.oid} #{val.gen} R"
231
+ else
232
+ serialize(val.value, recursive: recursive, seen: seen, indent: indent)
233
+ end
234
+ elsif val.nil? || seen.key?(val.data)
235
+ print "{ref #{seen[val.data]}}"
236
+ else
237
+ seen[val.data] = (val.type == :Page ? "page #{val.index + 1}" : seen.length + 1)
238
+ print "{obj #{seen[val.data]}} "
239
+ serialize(val.value, recursive: recursive, seen: seen, indent: indent)
240
+ end
241
+ else
242
+ print @serializer.serialize(val)
243
+ end
244
+ puts if indent == 0
245
+ end
246
+
247
+ COMMAND_DESCRIPTIONS = [ #:nodoc:
248
+ ["OID[,GEN] | o[bject] OID[,GEN]", "Print object"],
249
+ ["r[ecursive] OID[,GEN]", "Print object recursively"],
250
+ ["s[tream] OID[,GEN]", "Print filtered stream"],
251
+ ["raw[-stream] OID[,GEN]", "Print raw stream"],
252
+ ["x[ref] OID[,GEN]", "Print the cross-reference entry"],
253
+ ["c[atalog]", "Print the catalog dictionary"],
254
+ ["t[railer]", "Print the trailer dictionary"],
255
+ ["p[ages] [RANGE]", "Print information about pages"],
256
+ ["pc | page-count", "Print the number of pages"],
257
+ ["search REGEXP", "Print objects matching the pattern"],
258
+ ["h[elp]", "Show the help"],
259
+ ["q[uit]", "Quit"],
260
+ ]
261
+
262
+ def help_long_desc #:nodoc:
263
+ output = super
264
+ summary_width = command_parser.main_options.summary_width
265
+ data = <<~HELP
266
+ If a command or an argument is OID[,GEN], object and generation numbers are expected. The
267
+ generation number defaults to 0 if not given. The available commands are:
268
+ HELP
269
+ content = format(data, indent: 0,
270
+ width: command_parser.help_line_width - command_parser.help_indent)
271
+ content << "\n\n"
272
+ COMMAND_DESCRIPTIONS.each do |cmd, desc|
273
+ content << format(cmd.ljust(summary_width + 1) << desc,
274
+ width: command_parser.help_line_width - command_parser.help_indent,
275
+ indent: summary_width + 1, indent_first_line: false) << "\n"
276
+ end
277
+ output << cond_format_help_section("Interactive Mode Commands", content, preformatted: true)
278
+ end
279
+
280
+ def usage_arguments #:nodoc:
281
+ "FILE [[CMD [ARGS]]...]"
282
+ end
283
+
154
284
  end
155
285
 
156
286
  end