pdfwalker 1.0.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 +7 -0
- data/COPYING +674 -0
- data/bin/pdfwalker +30 -0
- data/lib/pdfwalker.rb +21 -0
- data/lib/pdfwalker/about.rb +37 -0
- data/lib/pdfwalker/config.rb +120 -0
- data/lib/pdfwalker/file.rb +320 -0
- data/lib/pdfwalker/hexview.rb +84 -0
- data/lib/pdfwalker/imgview.rb +69 -0
- data/lib/pdfwalker/menu.rb +382 -0
- data/lib/pdfwalker/properties.rb +126 -0
- data/lib/pdfwalker/signing.rb +558 -0
- data/lib/pdfwalker/textview.rb +88 -0
- data/lib/pdfwalker/treeview.rb +416 -0
- data/lib/pdfwalker/version.rb +23 -0
- data/lib/pdfwalker/walker.rb +300 -0
- data/lib/pdfwalker/xrefs.rb +75 -0
- metadata +103 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of PDF Walker, a graphical PDF file browser
|
4
|
+
Copyright (C) 2017 Guillaume Delugré.
|
5
|
+
|
6
|
+
PDF Walker is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the GNU General Public License as published by
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
9
|
+
(at your option) any later version.
|
10
|
+
|
11
|
+
PDF Walker is distributed in the hope that it will be useful,
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
GNU General Public License for more details.
|
15
|
+
|
16
|
+
You should have received a copy of the GNU General Public License
|
17
|
+
along with PDF Walker. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
=end
|
20
|
+
|
21
|
+
module PDFWalker
|
22
|
+
VERSION = "1.0.0"
|
23
|
+
end
|
@@ -0,0 +1,300 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
=begin
|
4
|
+
|
5
|
+
This file is part of PDF Walker, a graphical PDF file browser
|
6
|
+
Copyright (C) 2017 Guillaume Delugré.
|
7
|
+
|
8
|
+
PDF Walker is free software: you can redistribute it and/or modify
|
9
|
+
it under the terms of the GNU General Public License as published by
|
10
|
+
the Free Software Foundation, either version 3 of the License, or
|
11
|
+
(at your option) any later version.
|
12
|
+
|
13
|
+
PDF Walker is distributed in the hope that it will be useful,
|
14
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
+
GNU General Public License for more details.
|
17
|
+
|
18
|
+
You should have received a copy of the GNU General Public License
|
19
|
+
along with PDF Walker. If not, see <http://www.gnu.org/licenses/>.
|
20
|
+
|
21
|
+
=end
|
22
|
+
|
23
|
+
require 'origami'
|
24
|
+
require 'gtk2'
|
25
|
+
|
26
|
+
include Gtk
|
27
|
+
|
28
|
+
require_relative 'menu'
|
29
|
+
require_relative 'version'
|
30
|
+
require_relative 'about'
|
31
|
+
require_relative 'file'
|
32
|
+
require_relative 'hexview'
|
33
|
+
require_relative 'treeview'
|
34
|
+
require_relative 'textview'
|
35
|
+
require_relative 'imgview'
|
36
|
+
require_relative 'config'
|
37
|
+
require_relative 'properties'
|
38
|
+
require_relative 'xrefs'
|
39
|
+
require_relative 'signing'
|
40
|
+
|
41
|
+
module PDFWalker #:nodoc:all
|
42
|
+
|
43
|
+
class Walker < Window
|
44
|
+
attr_reader :treeview, :hexview, :objectview
|
45
|
+
attr_reader :explorer_history
|
46
|
+
attr_reader :config
|
47
|
+
attr_reader :filename
|
48
|
+
|
49
|
+
def self.start(file = nil)
|
50
|
+
Gtk.init
|
51
|
+
Walker.new(file)
|
52
|
+
Gtk.main
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize(target_file = nil)
|
56
|
+
super("PDF Walker")
|
57
|
+
|
58
|
+
@config = Walker::Config.new
|
59
|
+
@opened = nil
|
60
|
+
|
61
|
+
@last_search_result = []
|
62
|
+
@last_search =
|
63
|
+
{
|
64
|
+
:expr => "",
|
65
|
+
:regexp => false,
|
66
|
+
:type => :body
|
67
|
+
}
|
68
|
+
|
69
|
+
@explorer_history = []
|
70
|
+
|
71
|
+
init_interface
|
72
|
+
|
73
|
+
open(target_file)
|
74
|
+
end
|
75
|
+
|
76
|
+
def error(msg)
|
77
|
+
dialog = Gtk::MessageDialog.new(self,
|
78
|
+
Gtk::Dialog::DESTROY_WITH_PARENT,
|
79
|
+
Gtk::MessageDialog::ERROR,
|
80
|
+
Gtk::MessageDialog::BUTTONS_CLOSE,
|
81
|
+
msg
|
82
|
+
)
|
83
|
+
|
84
|
+
dialog.run
|
85
|
+
dialog.destroy
|
86
|
+
end
|
87
|
+
|
88
|
+
def reload
|
89
|
+
@treeview.load(@opened) if @opened
|
90
|
+
end
|
91
|
+
|
92
|
+
def search
|
93
|
+
dialog = Gtk::Dialog.new("Search...",
|
94
|
+
self,
|
95
|
+
Gtk::Dialog::MODAL | Gtk::Dialog::DESTROY_WITH_PARENT,
|
96
|
+
[Gtk::Stock::FIND, Gtk::Dialog::RESPONSE_OK],
|
97
|
+
[Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL]
|
98
|
+
)
|
99
|
+
|
100
|
+
entry = Gtk::Entry.new
|
101
|
+
entry.signal_connect('activate') { dialog.response(Gtk::Dialog::RESPONSE_OK) }
|
102
|
+
entry.text = @last_search[:expr]
|
103
|
+
|
104
|
+
button_bydata = Gtk::RadioButton.new("In object body")
|
105
|
+
button_byname = Gtk::RadioButton.new(button_bydata, "In object name")
|
106
|
+
button_regexp = Gtk::CheckButton.new("Regular expression")
|
107
|
+
|
108
|
+
button_bydata.set_active(true) if @last_search[:type] == :body
|
109
|
+
button_byname.set_active(true) if @last_search[:type] == :name
|
110
|
+
button_regexp.set_active(@last_search[:regexp])
|
111
|
+
|
112
|
+
hbox = Gtk::HBox.new
|
113
|
+
hbox.pack_start Gtk::Label.new("Search for expression ")
|
114
|
+
hbox.pack_start entry
|
115
|
+
|
116
|
+
dialog.vbox.pack_start(hbox)
|
117
|
+
dialog.vbox.pack_start(button_bydata)
|
118
|
+
dialog.vbox.pack_start(button_byname)
|
119
|
+
dialog.vbox.pack_end(button_regexp)
|
120
|
+
|
121
|
+
dialog.signal_connect('response') do |_, response|
|
122
|
+
if response != Gtk::Dialog::RESPONSE_OK
|
123
|
+
dialog.destroy
|
124
|
+
next
|
125
|
+
end
|
126
|
+
|
127
|
+
search_document(entry.text, regexp: button_regexp.active?, type: (button_bydata.active? ? 'body' : 'name'))
|
128
|
+
end
|
129
|
+
|
130
|
+
dialog.show_all
|
131
|
+
end
|
132
|
+
|
133
|
+
def goto_catalog
|
134
|
+
@treeview.goto(@opened.Catalog.reference)
|
135
|
+
end
|
136
|
+
|
137
|
+
def goto_docinfo
|
138
|
+
@treeview.goto(@opened.document_info.reference) if @opened.document_info?
|
139
|
+
end
|
140
|
+
|
141
|
+
def goto_metadata
|
142
|
+
@treeview.goto(@opened.Catalog.Metadata.reference) if @opened.metadata?
|
143
|
+
end
|
144
|
+
|
145
|
+
def goto_object
|
146
|
+
dialog = Gtk::Dialog.new("Jump to object...",
|
147
|
+
self,
|
148
|
+
Gtk::Dialog::MODAL | Gtk::Dialog::DESTROY_WITH_PARENT,
|
149
|
+
[Gtk::Stock::OK, Gtk::Dialog::RESPONSE_OK],
|
150
|
+
[Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL]
|
151
|
+
)
|
152
|
+
|
153
|
+
entry = Gtk::Entry.new
|
154
|
+
entry.signal_connect('activate') { dialog.response(Gtk::Dialog::RESPONSE_OK) }
|
155
|
+
|
156
|
+
dialog.vbox.pack_start Gtk::Label.new("Object number: ")
|
157
|
+
dialog.vbox.pack_start entry
|
158
|
+
dialog.show_all
|
159
|
+
|
160
|
+
no = 0
|
161
|
+
dialog.run do |response|
|
162
|
+
no = entry.text.to_i if response == Gtk::Dialog::RESPONSE_OK
|
163
|
+
|
164
|
+
dialog.destroy
|
165
|
+
end
|
166
|
+
|
167
|
+
return unless no > 0
|
168
|
+
|
169
|
+
obj = @opened[no]
|
170
|
+
if obj.nil?
|
171
|
+
error("Object #{no} not found.")
|
172
|
+
else
|
173
|
+
@treeview.goto(obj)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
def init_interface
|
180
|
+
signal_connect('destroy') {
|
181
|
+
@config.save
|
182
|
+
Gtk.main_quit
|
183
|
+
}
|
184
|
+
|
185
|
+
add_events(Gdk::Event::KEY_RELEASE_MASK)
|
186
|
+
signal_connect('key_release_event') { |_w, event|
|
187
|
+
if event.keyval == Gdk::Keyval::GDK_F1 then about
|
188
|
+
elsif event.keyval == Gdk::Keyval::GDK_Escape and @opened and not @explorer_history.empty?
|
189
|
+
@treeview.goto(@explorer_history.pop)
|
190
|
+
end
|
191
|
+
}
|
192
|
+
|
193
|
+
create_menus
|
194
|
+
create_treeview
|
195
|
+
create_hexview
|
196
|
+
create_objectview
|
197
|
+
create_panels
|
198
|
+
create_statusbar
|
199
|
+
|
200
|
+
@vbox = Gtk::VBox.new
|
201
|
+
@vbox.pack_start(@menu, false, false)
|
202
|
+
@vbox.pack_start(@hpaned)
|
203
|
+
@vbox.pack_end(@statusbar, false, false)
|
204
|
+
|
205
|
+
add @vbox
|
206
|
+
|
207
|
+
set_default_size(self.screen.width * 0.5, self.screen.height * 0.5)
|
208
|
+
show_all
|
209
|
+
end
|
210
|
+
|
211
|
+
def search_document(expr, regexp: false, type: 'body')
|
212
|
+
search =
|
213
|
+
{
|
214
|
+
expr: expr,
|
215
|
+
regexp: regexp,
|
216
|
+
type: type,
|
217
|
+
}
|
218
|
+
|
219
|
+
if search == @last_search
|
220
|
+
@last_search_result.push @last_search_result.shift
|
221
|
+
results = @last_search_result
|
222
|
+
else
|
223
|
+
expr = Regexp.new(expr) if search[:regexp]
|
224
|
+
|
225
|
+
results =
|
226
|
+
if search[:type] == 'body'
|
227
|
+
@opened.grep(expr)
|
228
|
+
else
|
229
|
+
@opened.ls(expr, follow_references: false)
|
230
|
+
end
|
231
|
+
|
232
|
+
@last_search = search
|
233
|
+
end
|
234
|
+
|
235
|
+
goto_next_search_result(results)
|
236
|
+
end
|
237
|
+
|
238
|
+
def goto_next_search_result(results)
|
239
|
+
if results.empty?
|
240
|
+
error("No result found.")
|
241
|
+
else
|
242
|
+
if results != @last_search_result
|
243
|
+
# Reset the previous search highlighting.
|
244
|
+
@last_search_result.each do |obj| @treeview.highlight(obj, nil) end
|
245
|
+
|
246
|
+
# Highlight the new results.
|
247
|
+
results.each do |obj| @treeview.highlight(obj, "lightpink") end
|
248
|
+
|
249
|
+
@last_search_result = results
|
250
|
+
end
|
251
|
+
|
252
|
+
@treeview.goto(results.first, follow_references: false)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def create_panels
|
257
|
+
@hpaned = Gtk::HPaned.new
|
258
|
+
|
259
|
+
@treepanel = Gtk::ScrolledWindow.new.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC)
|
260
|
+
@treepanel.add @treeview
|
261
|
+
|
262
|
+
@vpaned = Gtk::VPaned.new
|
263
|
+
@vpaned.pack1(@objectview, true, false)
|
264
|
+
@vpaned.pack2(@hexview, true, false)
|
265
|
+
|
266
|
+
@hpaned.pack1(@treepanel, true, false)
|
267
|
+
@hpaned.pack2(@vpaned, true, false)
|
268
|
+
end
|
269
|
+
|
270
|
+
def create_statusbar
|
271
|
+
@statusbar = Gtk::Statusbar.new
|
272
|
+
|
273
|
+
@main_context = @statusbar.get_context_id 'Main'
|
274
|
+
@statusbar.push(@main_context, 'No file selected')
|
275
|
+
end
|
276
|
+
|
277
|
+
def start_profiling
|
278
|
+
if @help_menu_profile.active?
|
279
|
+
require 'ruby-prof'
|
280
|
+
RubyProf.start
|
281
|
+
end
|
282
|
+
|
283
|
+
result = yield
|
284
|
+
|
285
|
+
if @help_menu_profile.active?
|
286
|
+
result = RubyProf.stop
|
287
|
+
multiprinter = RubyProf::MultiPrinter.new(result)
|
288
|
+
|
289
|
+
Dir.mkdir(@config.profile_output_dir) unless Dir.exist?(@config.profile_output_dir)
|
290
|
+
|
291
|
+
multiprinter.print(path: @config.profile_output_dir, profile: File.basename(filename))
|
292
|
+
end
|
293
|
+
|
294
|
+
result
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|
299
|
+
|
300
|
+
PDFWalker::Walker.start if __FILE__ == $0
|
@@ -0,0 +1,75 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of PDF Walker, a graphical PDF file browser
|
4
|
+
Copyright (C) 2017 Guillaume Delugré.
|
5
|
+
|
6
|
+
PDF Walker is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the GNU General Public License as published by
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
9
|
+
(at your option) any later version.
|
10
|
+
|
11
|
+
PDF Walker is distributed in the hope that it will be useful,
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
GNU General Public License for more details.
|
15
|
+
|
16
|
+
You should have received a copy of the GNU General Public License
|
17
|
+
along with PDF Walker. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
=end
|
20
|
+
|
21
|
+
module PDFWalker
|
22
|
+
|
23
|
+
class Walker < Window
|
24
|
+
def show_xrefs(target)
|
25
|
+
XrefsDialog.new(self, target)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class XrefsDialog < Dialog
|
30
|
+
OBJCOL = 0
|
31
|
+
REFCOL = 1
|
32
|
+
NAMECOL = 2
|
33
|
+
|
34
|
+
def initialize(parent, target)
|
35
|
+
super("Xrefs to #{target.reference}", parent, Dialog::MODAL, [Stock::CLOSE, Dialog::RESPONSE_NONE])
|
36
|
+
@parent = parent
|
37
|
+
|
38
|
+
@list = ListStore.new(Object, String, String)
|
39
|
+
@view = TreeView.new(@list)
|
40
|
+
|
41
|
+
column = Gtk::TreeViewColumn.new("Origin", Gtk::CellRendererText.new, text: REFCOL)
|
42
|
+
@view.append_column(column)
|
43
|
+
|
44
|
+
column = Gtk::TreeViewColumn.new("Objects", Gtk::CellRendererText.new, text: NAMECOL)
|
45
|
+
@view.append_column(column)
|
46
|
+
|
47
|
+
target.xrefs.each { |obj|
|
48
|
+
str = obj.type.to_s
|
49
|
+
iter = @list.append
|
50
|
+
@list.set_value(iter, OBJCOL, obj)
|
51
|
+
|
52
|
+
obj = obj.parent until obj.indirect?
|
53
|
+
@list.set_value(iter, REFCOL, obj.reference.to_s)
|
54
|
+
|
55
|
+
@list.set_value(iter, NAMECOL, str)
|
56
|
+
}
|
57
|
+
|
58
|
+
@view.signal_connect("row_activated") { |_tree, _path, _column|
|
59
|
+
if @view.selection.selected
|
60
|
+
from = @list.get_value(@view.selection.selected, OBJCOL)
|
61
|
+
@parent.treeview.goto(from)
|
62
|
+
end
|
63
|
+
}
|
64
|
+
|
65
|
+
scroll = ScrolledWindow.new.set_policy(POLICY_NEVER, POLICY_AUTOMATIC)
|
66
|
+
scroll.add(@view)
|
67
|
+
vbox.add(scroll)
|
68
|
+
|
69
|
+
set_default_size(200, 200)
|
70
|
+
|
71
|
+
signal_connect('response') { destroy }
|
72
|
+
show_all
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pdfwalker
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Guillaume Delugré
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-10-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: gtk2
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: ruby-gtkhex
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: origami
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.1'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.1'
|
55
|
+
description: PDFWalker is a GTK frontend to explore the internals of a PDF document
|
56
|
+
with Origami
|
57
|
+
email: origami@subvert.technology
|
58
|
+
executables:
|
59
|
+
- pdfwalker
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- COPYING
|
64
|
+
- bin/pdfwalker
|
65
|
+
- lib/pdfwalker.rb
|
66
|
+
- lib/pdfwalker/about.rb
|
67
|
+
- lib/pdfwalker/config.rb
|
68
|
+
- lib/pdfwalker/file.rb
|
69
|
+
- lib/pdfwalker/hexview.rb
|
70
|
+
- lib/pdfwalker/imgview.rb
|
71
|
+
- lib/pdfwalker/menu.rb
|
72
|
+
- lib/pdfwalker/properties.rb
|
73
|
+
- lib/pdfwalker/signing.rb
|
74
|
+
- lib/pdfwalker/textview.rb
|
75
|
+
- lib/pdfwalker/treeview.rb
|
76
|
+
- lib/pdfwalker/version.rb
|
77
|
+
- lib/pdfwalker/walker.rb
|
78
|
+
- lib/pdfwalker/xrefs.rb
|
79
|
+
homepage: http://github.com/gdelugre/pdfwalker
|
80
|
+
licenses:
|
81
|
+
- GPL-3.0+
|
82
|
+
metadata: {}
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 2.6.13
|
100
|
+
signing_key:
|
101
|
+
specification_version: 4
|
102
|
+
summary: a PDF document explorer
|
103
|
+
test_files: []
|