hexapdf 0.9.3 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -0
- data/CONTRIBUTERS +2 -1
- data/VERSION +1 -1
- data/lib/hexapdf/cli/command.rb +9 -5
- data/lib/hexapdf/cli/images.rb +68 -13
- data/lib/hexapdf/cli/inspect.rb +201 -71
- data/lib/hexapdf/content/canvas.rb +1 -1
- data/lib/hexapdf/dictionary.rb +15 -1
- data/lib/hexapdf/dictionary_fields.rb +5 -4
- data/lib/hexapdf/document.rb +15 -6
- data/lib/hexapdf/encryption/security_handler.rb +3 -2
- data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +165 -165
- data/lib/hexapdf/font/true_type_wrapper.rb +2 -2
- data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
- data/lib/hexapdf/image_loader/jpeg.rb +13 -11
- data/lib/hexapdf/reference.rb +5 -0
- data/lib/hexapdf/revision.rb +14 -0
- data/lib/hexapdf/serializer.rb +6 -6
- data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
- data/lib/hexapdf/type/image.rb +3 -1
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/xref_section.rb +8 -0
- data/man/man1/hexapdf.1 +88 -20
- data/test/data/images/truecolour-alpha-8bit.png +0 -0
- data/test/data/images/ycck.jpg +0 -0
- data/test/hexapdf/content/test_canvas.rb +2 -2
- data/test/hexapdf/document/test_images.rb +7 -5
- data/test/hexapdf/encryption/test_security_handler.rb +13 -0
- data/test/hexapdf/image_loader/test_jpeg.rb +10 -0
- data/test/hexapdf/image_loader/test_png.rb +3 -2
- data/test/hexapdf/test_dictionary.rb +9 -0
- data/test/hexapdf/test_dictionary_fields.rb +15 -0
- data/test/hexapdf/test_document.rb +5 -2
- data/test/hexapdf/test_reference.rb +4 -0
- data/test/hexapdf/test_revision.rb +12 -0
- data/test/hexapdf/test_writer.rb +5 -5
- data/test/hexapdf/test_xref_section.rb +11 -0
- data/test/hexapdf/type/test_form.rb +1 -1
- data/test/hexapdf/type/test_image.rb +28 -19
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7d800154c0525b724aa0b1a91e360aff2d443bd56e260b5b051a06d605cdb9c
|
4
|
+
data.tar.gz: d658fa5ff09f12a169156ee091f66b189874ae4b847cb4effa8d2c60b5f836b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b600fb0c600a0093e08d5ed3a4d16c2bfee637b0d338783c663f664aa7b4eb5384b20f064137c26d83f4269ed213b5d46c390421946d747f16be9d7e5600f381
|
7
|
+
data.tar.gz: 31f7c75f80db9b467f0fe8582616a9d136b9da86995cd6c57c76678a4e916fef0e73a4ad22ef53d8e8c36ce7ab620e303d6d05268d3ce6d551bbc33b67c7f959
|
data/CHANGELOG.md
CHANGED
@@ -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
|
data/CONTRIBUTERS
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.10.0
|
data/lib/hexapdf/cli/command.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
259
|
-
|
260
|
-
|
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.
|
data/lib/hexapdf/cli/images.rb
CHANGED
@@ -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",
|
99
|
-
"writable")
|
100
|
-
puts("-" *
|
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
|
-
|
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,
|
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
|
-
|
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
|
-
|
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
|
data/lib/hexapdf/cli/inspect.rb
CHANGED
@@ -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
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
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
|
-
@
|
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)
|
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
|
105
|
-
|
106
|
-
|
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
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
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
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
181
|
+
|
182
|
+
false
|
134
183
|
end
|
135
184
|
|
136
|
-
|
137
|
-
|
138
|
-
if
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|