pdfwalker 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,84 @@
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
+ require 'gtkhex'
22
+
23
+ module PDFWalker
24
+
25
+ class Walker < Window
26
+ private
27
+
28
+ def create_hexview
29
+ @hexview = DumpView.new(self)
30
+ end
31
+
32
+ class DumpView < ScrolledWindow
33
+
34
+ def initialize(parent)
35
+ @parent = parent
36
+ super()
37
+
38
+ set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
39
+
40
+ @current_obj = nil
41
+
42
+ @view = HexEditor.new
43
+ @view.show_offsets(true)
44
+
45
+ add_with_viewport @view
46
+ end
47
+
48
+ def clear
49
+ @view.set_data ''
50
+ end
51
+
52
+ def load(object)
53
+ return if @current_obj.equal?(object)
54
+
55
+ self.clear
56
+
57
+ case object
58
+ when Origami::Stream
59
+ load_stream(object)
60
+ when Origami::String
61
+ load_string(object)
62
+ end
63
+
64
+ @current_obj = object
65
+ end
66
+
67
+ private
68
+
69
+ def load_stream(object)
70
+ begin
71
+ @view.set_data(object.data)
72
+ rescue Origami::Filter::Error
73
+ @view.set_data($!.input_data) if $!.input_data
74
+
75
+ @parent.error("#{$!.class}: #{$!.message}") unless object.filters == [ :DCTDecode ]
76
+ end
77
+ end
78
+
79
+ def load_string(object)
80
+ @view.set_data(object.value)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,69 @@
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 ImgViewer < Window
24
+ attr_reader :image
25
+
26
+ def initialize
27
+ super()
28
+
29
+ set_title "Image view"
30
+ set_decorated false
31
+ set_resizable false
32
+
33
+ add_events(Gdk::Event::KEY_RELEASE_MASK)
34
+ signal_connect('key_release_event') { |_, event|
35
+ destroy if event.keyval == Gdk::Keyval::GDK_Escape
36
+ }
37
+ end
38
+
39
+ def show_raw_img(data, w, h, bpc, bpr)
40
+ set_default_size w,h
41
+
42
+ pixbuf = GdkPixbuf::Pixbuf.new data: data,
43
+ colorspace: GdkPixbuf::Colorspace::RGB,
44
+ has_alpha: false,
45
+ bits_per_sample: bpc,
46
+ width: w, height: h,
47
+ row_stride: bpr
48
+
49
+ @image = Gtk::Image.new(pixbuf)
50
+ add @image
51
+
52
+ show_all
53
+ end
54
+
55
+ def show_compressed_img(data)
56
+ loader = GdkPixbuf::PixbufLoader.new
57
+ loader.last_write data
58
+
59
+ pixbuf = loader.pixbuf
60
+ set_default_size pixbuf.width, pixbuf.height
61
+
62
+ @image = Gtk::Image.new(pixbuf)
63
+ add @image
64
+
65
+ show_all
66
+ end
67
+ end
68
+
69
+ end
@@ -0,0 +1,382 @@
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
+ module PopupMenu
24
+
25
+ @@menus = Hash.new([])
26
+ @@menus['PDF'] =
27
+ [
28
+ {
29
+ Name: Stock::SAVE_AS,
30
+ Sensitive: true,
31
+ Callback: lambda { |_widget, viewer, _path|
32
+ viewer.parent.save
33
+ }
34
+ },
35
+ {
36
+ Name: :"---"
37
+ },
38
+ {
39
+ Name: Stock::PROPERTIES,
40
+ Sensitive: true,
41
+ Callback: lambda { |_widget, viewer, _path|
42
+ viewer.parent.display_file_properties
43
+ }
44
+ },
45
+ {
46
+ Name: :"---"
47
+ },
48
+ {
49
+ Name: Stock::CLOSE,
50
+ Sensitive: true,
51
+ Callback: lambda { |_widget, viewer, _path|
52
+ viewer.parent.close
53
+ }
54
+ }
55
+ ]
56
+
57
+ @@menus['Reference'] =
58
+ [
59
+ {
60
+ Name: Stock::JUMP_TO,
61
+ Sensitive: true,
62
+ Callback: lambda { |_widget, viewer, path|
63
+ viewer.row_activated(path, viewer.get_column(viewer.class::TEXTCOL))
64
+ }
65
+ }
66
+ ]
67
+
68
+ @@menus['Revision'] =
69
+ [
70
+ {
71
+ Name: "Save to this revision",
72
+ Sensitive: true,
73
+ Callback: lambda { |_widget, viewer, path|
74
+ revstr = viewer.model.get_value(viewer.model.get_iter(path), viewer.class::TEXTCOL)
75
+ revstr.slice!(0, "Revision ".size)
76
+
77
+ revnum = revstr.to_i
78
+
79
+ dialog = Gtk::FileChooserDialog.new("Save PDF File",
80
+ viewer.parent,
81
+ Gtk::FileChooser::ACTION_SAVE,
82
+ nil,
83
+ [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL],
84
+ [Gtk::Stock::SAVE, Gtk::Dialog::RESPONSE_ACCEPT]
85
+ )
86
+
87
+ dialog.filter = FileFilter.new.add_pattern("*.pdf")
88
+
89
+ if dialog.run == Gtk::Dialog::RESPONSE_ACCEPT
90
+ viewer.parent.opened.save_upto(revnum, dialog.filename)
91
+ end
92
+
93
+ dialog.destroy
94
+ }
95
+ }
96
+ ]
97
+
98
+ @@menus['Stream'] =
99
+ [
100
+ {
101
+ Name: "Dump encoded stream",
102
+ Sensitive: true,
103
+ Callback: lambda { |_widget, viewer, path|
104
+ stream = viewer.object_by_path(path)
105
+
106
+ viewer.parent.save_data("Save encoded stream to file", stream.encoded_data)
107
+ }
108
+ },
109
+ {
110
+ Name: "Dump decoded stream",
111
+ Sensitive: true,
112
+ Callback: lambda { |_widget, viewer, path|
113
+ stream = viewer.object_by_path(path)
114
+
115
+ viewer.parent.save_data("Save decoded stream to file", stream.data)
116
+ }
117
+ }
118
+ ]
119
+
120
+ @@menus['String'] =
121
+ [
122
+ {
123
+ Name: "Dump string",
124
+ Sensitive: true,
125
+ Callback: lambda { |_widget, viewer, path|
126
+ string = viewer.object_by_path(path)
127
+
128
+ viewer.parent.save_data("Save string to file", string.value)
129
+ }
130
+ }
131
+ ]
132
+
133
+ @@menus['Image'] = @@menus['Stream'] +
134
+ [
135
+ {
136
+ Name: :"---"
137
+ },
138
+ {
139
+ Name: "View image",
140
+ Sensitive: true,
141
+ Callback: lambda { |_widget, viewer, path|
142
+ stm = viewer.object_by_path(path)
143
+ w,h = stm.Width, stm.Height
144
+
145
+ if stm.ColorSpace.nil?
146
+ colors = 1
147
+ else
148
+ colors =
149
+ case stm.ColorSpace.value
150
+ when :DeviceGray then 1
151
+ when :DeviceRGB then 3
152
+ when :DeviceCMYK then 4
153
+ else
154
+ 1
155
+ end
156
+ end
157
+
158
+ bpc = stm.BitsPerComponent || 8
159
+ bpr = (w * colors * bpc + 7) >> 3
160
+ data = stm.data
161
+
162
+ begin
163
+ imgview = ImgViewer.new
164
+ if stm.Filter == :DCTDecode or (stm.Filter.is_a?(Array) and stm.Filter[0] == :DCTDecode)
165
+ imgview.show_compressed_img data
166
+ else
167
+ imgview.show_raw_img data, w, h, bpc, bpr
168
+ end
169
+ rescue
170
+ viewer.parent.error("#{$!.class}: #{$!.message}")
171
+ end
172
+ }
173
+ }
174
+ ]
175
+
176
+ def popup_menu(obj, event, path)
177
+ menu = Menu.new
178
+
179
+ type = popup_menu_key(obj)
180
+
181
+ # Create menu title.
182
+ title = obj.is_a?(Origami::Object) ? "Object : " : ""
183
+ title << type.to_s
184
+ menu.append(MenuItem.new(title).set_sensitive(false).modify_text(Gtk::STATE_INSENSITIVE, Gdk::Color.new(255,0,255)))
185
+
186
+ # Object information.
187
+ create_object_menu(menu, obj) if obj.is_a?(Origami::Object)
188
+
189
+ # Type-specific menu.
190
+ create_type_menu(menu, type, path)
191
+
192
+ menu.show_all
193
+ menu.popup(nil, nil, event.button, event.time)
194
+ end
195
+
196
+ private
197
+
198
+ def create_object_menu(menu, object)
199
+ if object.indirect?
200
+ menu.append(MenuItem.new("Number : #{object.no}; Generation : #{object.generation}").set_sensitive(false))
201
+ menu.append(MenuItem.new("File offset : #{object.file_offset}").set_sensitive(false))
202
+
203
+ getxrefs = MenuItem.new("Search references to this object").set_sensitive(true)
204
+ getxrefs.signal_connect("activate") do
205
+ self.parent.show_xrefs(object)
206
+ end
207
+ menu.append(getxrefs)
208
+
209
+ elsif not object.parent.nil?
210
+ gotoparent = MenuItem.new("Goto Parent Object").set_sensitive(true)
211
+ gotoparent.signal_connect("activate") do
212
+ self.goto(object.parent)
213
+ end
214
+ menu.append(gotoparent)
215
+ end
216
+ end
217
+
218
+ def create_type_menu(menu, type, path)
219
+ items = @@menus[type]
220
+ menu.append(SeparatorMenuItem.new) if not items.empty?
221
+
222
+ items.each do |item|
223
+ if item[:Name] == :"---"
224
+ entry = SeparatorMenuItem.new
225
+ else
226
+ if item[:Name].is_a?(String)
227
+ entry = MenuItem.new(item[:Name])
228
+ else
229
+ entry = ImageMenuItem.new(item[:Name])
230
+ end
231
+
232
+ entry.set_sensitive(item[:Sensitive])
233
+ entry.signal_connect("activate", self, path, &item[:Callback])
234
+ end
235
+
236
+ menu.append(entry)
237
+ end
238
+ end
239
+
240
+ def popup_menu_key(object)
241
+ if object.is_a?(Origami::Object)
242
+ popup_menu_object_key(object)
243
+ else
244
+ popup_menu_struct_key(object)
245
+ end
246
+ end
247
+
248
+ def popup_menu_object_key(object)
249
+ if object.is_a?(Origami::Graphics::ImageXObject)
250
+ 'Image'
251
+ else
252
+ object.native_type.to_s.split("::").last
253
+ end
254
+ end
255
+
256
+ def popup_menu_struct_key(struct)
257
+ case struct
258
+ when ::Array
259
+ 'Body'
260
+ when Origami::XRef, Origami::XRefToCompressedObject
261
+ 'XRef'
262
+ else
263
+ struct.class.name.split('::').last
264
+ end
265
+ end
266
+ end
267
+
268
+ class Walker < Window
269
+
270
+ private
271
+
272
+ def create_menus
273
+ AccelMap.add_entry("<PDF Walker>/File/Open", Gdk::Keyval::GDK_O, Gdk::Window::CONTROL_MASK)
274
+ AccelMap.add_entry("<PDF Walker>/File/Refresh", Gdk::Keyval::GDK_R, Gdk::Window::CONTROL_MASK)
275
+ AccelMap.add_entry("<PDF Walker>/File/Close", Gdk::Keyval::GDK_W, Gdk::Window::CONTROL_MASK)
276
+ AccelMap.add_entry("<PDF Walker>/File/Save", Gdk::Keyval::GDK_S, Gdk::Window::CONTROL_MASK)
277
+ AccelMap.add_entry("<PDF Walker>/File/Quit", Gdk::Keyval::GDK_Q, Gdk::Window::CONTROL_MASK)
278
+ AccelMap.add_entry("<PDF Walker>/Document/Search", Gdk::Keyval::GDK_F, Gdk::Window::CONTROL_MASK)
279
+
280
+ @menu = MenuBar.new
281
+
282
+ create_file_menu
283
+ create_document_menu
284
+ create_help_menu
285
+ end
286
+
287
+ def create_file_menu
288
+ file_ag = Gtk::AccelGroup.new
289
+ @file_menu = Menu.new.set_accel_group(file_ag).set_accel_path("<PDF Walker>/File")
290
+ add_accel_group(file_ag)
291
+
292
+ entries = [
293
+ @file_menu_open = ImageMenuItem.new(Stock::OPEN).set_accel_path("<PDF Walker>/File/Open"),
294
+ @file_menu_recent = MenuItem.new("Last opened"),
295
+ @file_menu_refresh = ImageMenuItem.new(Stock::REFRESH).set_sensitive(false).set_accel_path("<PDF Walker>/File/Refresh"),
296
+ @file_menu_close = ImageMenuItem.new(Stock::CLOSE).set_sensitive(false).set_accel_path("<PDF Walker>/File/Close"),
297
+ @file_menu_saveas = ImageMenuItem.new(Stock::SAVE_AS).set_sensitive(false).set_accel_path("<PDF Walker>/File/Save"),
298
+ @file_menu_exit = ImageMenuItem.new(Stock::QUIT).set_accel_path("<PDF Walker>/File/Quit"),
299
+ ]
300
+
301
+ @file_menu_open.signal_connect('activate') { open }
302
+ @file_menu_refresh.signal_connect('activate') { open(@filename) }
303
+ @file_menu_close.signal_connect('activate') { close }
304
+ @file_menu_saveas.signal_connect('activate') { save }
305
+ @file_menu_exit.signal_connect('activate') { self.destroy }
306
+
307
+ update_recent_menu
308
+
309
+ entries.each do |entry|
310
+ @file_menu.append(entry)
311
+ end
312
+
313
+ @menu.append(MenuItem.new('_File').set_submenu(@file_menu))
314
+ end
315
+
316
+ def create_document_menu
317
+ doc_ag = Gtk::AccelGroup.new
318
+ @document_menu = Menu.new.set_accel_group(doc_ag)
319
+ add_accel_group(doc_ag)
320
+
321
+ entries = [
322
+ @document_menu_search = ImageMenuItem.new(Stock::FIND).set_sensitive(false).set_accel_path("<PDF Walker>/Document/Search"),
323
+ MenuItem.new,
324
+ @document_menu_gotocatalog = MenuItem.new("Jump to Catalog").set_sensitive(false),
325
+ @document_menu_gotodocinfo = MenuItem.new("Jump to Document Info").set_sensitive(false),
326
+ @document_menu_gotometadata = MenuItem.new("Jump to Metadata").set_sensitive(false),
327
+ @document_menu_gotorev = MenuItem.new("Jump to Revision...").set_sensitive(false),
328
+ @document_menu_gotopage = MenuItem.new("Jump to Page...").set_sensitive(false),
329
+ @document_menu_gotofield = MenuItem.new("Jump to Field...").set_sensitive(false),
330
+ @document_menu_gotoobj = MenuItem.new("Jump to Object...").set_sensitive(false),
331
+ MenuItem.new,
332
+ @document_menu_sign = MenuItem.new("Sign the document").set_sensitive(false),
333
+ @document_menu_ur = MenuItem.new("Enable Usage Rights").set_sensitive(false),
334
+ @document_menu_properties = ImageMenuItem.new(Stock::PROPERTIES).set_sensitive(false),
335
+ ]
336
+
337
+ @document_menu_search.signal_connect('activate') { search }
338
+ @document_menu_gotocatalog.signal_connect('activate') { goto_catalog }
339
+ @document_menu_gotodocinfo.signal_connect('activate') { goto_docinfo }
340
+ @document_menu_gotometadata.signal_connect('activate') { goto_metadata }
341
+ @document_menu_gotoobj.signal_connect('activate') { goto_object }
342
+ @document_menu_properties.signal_connect('activate') { display_file_properties }
343
+ @document_menu_sign.signal_connect('activate') { display_signing_wizard }
344
+ @document_menu_ur.signal_connect('activate') { display_usage_rights_wizard }
345
+
346
+ entries.each do |entry|
347
+ @document_menu.append(entry)
348
+ end
349
+
350
+ @menu.append(MenuItem.new('_Document').set_submenu(@document_menu))
351
+ end
352
+
353
+ def create_help_menu
354
+ @help_menu = Menu.new
355
+
356
+ @help_menu_profile = CheckMenuItem.new("Profiling (Debug purposes only)").set_active(@config.profile?)
357
+ @help_menu_about = ImageMenuItem.new(Stock::ABOUT)
358
+
359
+ @help_menu_profile.signal_connect('toggled') do @config.set_profiling(@help_menu_profile.active?) end
360
+ @help_menu_about.signal_connect('activate') { about }
361
+
362
+ @help_menu.append(@help_menu_profile)
363
+ @help_menu.append(@help_menu_about)
364
+
365
+ @menu.append(MenuItem.new('_Help').set_submenu(@help_menu))
366
+ end
367
+
368
+ def update_recent_menu
369
+ @recent_menu = Menu.new
370
+ @config.recent_files.each { |file|
371
+ menu = MenuItem.new(file)
372
+ menu.signal_connect('activate') do open(file) end
373
+
374
+ @recent_menu.append(menu)
375
+ }
376
+
377
+ @file_menu_recent.set_submenu(@recent_menu)
378
+ @file_menu_recent.show_all
379
+ end
380
+ end
381
+
382
+ end