hexapdf 0.9.3 → 0.10.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.
- 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
|