class PDFWalker::PDFTree

Constants

BGCOL
FGCOL
LOADCOL
OBJCOL
STYLECOL
TEXTCOL
WEIGHTCOL

Attributes

parent[R]

Public Class Methods

new(parent) click to toggle source
Calls superclass method
# File lib/pdfwalker/treeview.rb, line 58
def initialize(parent)
    @parent = parent

    reset_appearance

    @treestore = TreeStore.new(Object::Object, String, Pango::Weight, Pango::Style, String, String, Integer)
    super(@treestore)

    signal_connect('cursor-changed') {
        iter = selection.selected
        if iter
            obj = @treestore.get_value(iter, OBJCOL)

            parent.hexview.load(obj)
            parent.objectview.load(obj)
        end
    }

    signal_connect('row-activated') { |_tree, path,_column|
        if selection.selected
            obj = @treestore.get_value(selection.selected, OBJCOL)

            if row_expanded?(path)
                collapse_row(path)
            else
                expand_row(path, false)
            end

            goto(obj) if obj.is_a?(Origami::Reference)
        end
    }

    signal_connect('row-expanded') { |_tree, iter, _path|
        obj = @treestore.get_value(iter, OBJCOL)

        if obj.is_a?(Origami::Stream) and iter.n_children == 1

            # Processing with an XRef or Object Stream
            if obj.is_a?(Origami::ObjectStream)
                obj.each { |embeddedobj|
                    load_object(iter, embeddedobj)
                }

            elsif obj.is_a?(Origami::XRefStream)
                obj.each { |xref|
                    load_xrefstm(iter, xref)
                }
            end
        end

        for i in 0...iter.n_children
            subiter = iter.nth_child(i)
            subobj = @treestore.get_value(subiter, OBJCOL)

            load_sub_objects(subiter, subobj)
        end
    }

    add_events(Gdk::Event::BUTTON_PRESS_MASK)
    signal_connect('button_press_event') { |_widget, event|
        if event.button == 3 && parent.opened
            path = get_path(event.x,event.y).first
            set_cursor(path, nil, false)

            obj = @treestore.get_value(@treestore.get_iter(path), OBJCOL)
            popup_menu(obj, event, path)
        end
    }
end

Public Instance Methods

clear() click to toggle source
# File lib/pdfwalker/treeview.rb, line 128
def clear
    @treestore.clear
end
goto(obj, follow_references: true) click to toggle source
# File lib/pdfwalker/treeview.rb, line 132
def goto(obj, follow_references: true)
    if obj.is_a?(TreePath)
        set_cursor(obj, nil, false)
    else
        if obj.is_a?(Origami::Name) and obj.parent.is_a?(Origami::Dictionary) and obj.parent.has_key?(obj)
            obj = obj.parent[obj]
        elsif obj.is_a?(Origami::Reference) and follow_references
            obj =
                begin
                    obj.solve
                rescue Origami::InvalidReferenceError
                    @parent.error("Object not found : #{obj}")
                    return
                end
        end

        _, path = object_to_tree_pos(obj)
        if path.nil?
            @parent.error("Object not found : #{obj.type}")
            return
        end

        expand_to_path(path) unless row_expanded?(path)
        @parent.explorer_history << cursor.first if cursor.first
        set_cursor(path, nil, false)
    end
end
highlight(obj, color) click to toggle source
# File lib/pdfwalker/treeview.rb, line 160
def highlight(obj, color)
    if obj.is_a?(Origami::Name) and obj.parent.is_a?(Origami::Dictionary) and obj.parent.has_key?(obj)
        obj = obj.parent[obj]
    end

    iter, path = object_to_tree_pos(obj)
    if iter.nil? or path.nil?
        @parent.error("Object not found : #{obj.type}")
        return
    end

    @treestore.set_value(iter, BGCOL, color)
    expand_to_path(path) unless row_expanded?(path)
end
load(pdf) click to toggle source
# File lib/pdfwalker/treeview.rb, line 175
def load(pdf)
    return unless pdf

    self.clear

    begin
        #
        # Create root entry
        #
        root = @treestore.append(nil)
        @treestore.set_value(root, OBJCOL, pdf)

        set_node(root, :Filename, @parent.filename)

        #
        # Create header entry
        #
        header = @treestore.append(root)
        @treestore.set_value(header, OBJCOL, pdf.header)

        set_node(header, :Header,
                 "Header (version #{pdf.header.major_version}.#{pdf.header.minor_version})")

        no = 1
        pdf.revisions.each { |revision|
            load_revision(root, no, revision)
            no = no + 1
        }

        set_model(@treestore)

    ensure
        expand(@treestore.iter_first, 3)
        set_cursor(@treestore.iter_first.path, nil, false)
    end
end
object_by_path(path) click to toggle source
# File lib/pdfwalker/treeview.rb, line 212
def object_by_path(path)
    iter = @treestore.get_iter(path)

    @treestore.get_value(iter, OBJCOL)
end

Private Instance Methods

expand(row, depth) click to toggle source
# File lib/pdfwalker/treeview.rb, line 253
def expand(row, depth)
    if row and depth != 0
        loop do
            expand_row(row.path, false)
            expand(row.first_child, depth - 1)

            break if not row.next!
        end
    end
end
get_object_appearance(type) click to toggle source
# File lib/pdfwalker/treeview.rb, line 402
def get_object_appearance(type)
    @@appearance[type]
end
load_body(rev, body) click to toggle source
# File lib/pdfwalker/treeview.rb, line 275
def load_body(rev, body)
    bodyroot = @treestore.append(rev)
    @treestore.set_value(bodyroot, OBJCOL, body)

    set_node(bodyroot, :Body, "Body")

    body.sort_by{|obj| obj.file_offset.to_i }.each { |object|
        begin
            load_object(bodyroot, object)
        rescue
            msg = "#{$!.class}: #{$!.message}\n#{$!.backtrace.join($/)}"
            STDERR.puts(msg)

            #@parent.error(msg)
            next
        end
    }
end
load_object(container, object, depth = 1, name = nil) click to toggle source
# File lib/pdfwalker/treeview.rb, line 294
def load_object(container, object, depth = 1, name = nil)
    iter = @treestore.append(container)
    @treestore.set_value(iter, OBJCOL, object)

    type = object.native_type.to_s.split('::').last.to_sym

    if name.nil?
        name =
            case object
            when Origami::String
                '"' + object.to_utf8.tr("\x00", ".") + '"'
            when Origami::Number, Origami::Name
                object.value.to_s
            else
                object.type.to_s
            end
    end

    set_node(iter, type, name)
    return unless depth > 0

    load_sub_objects(iter, object, depth)
end
load_revision(root, no, revision) click to toggle source
# File lib/pdfwalker/treeview.rb, line 264
def load_revision(root, no, revision)
    revroot = @treestore.append(root)
    @treestore.set_value(revroot, OBJCOL, revision)

    set_node(revroot, :Revision, "Revision #{no}")

    load_body(revroot, revision.body.values)
    load_xrefs(revroot, revision.xreftable)
    load_trailer(revroot, revision.trailer)
end
load_sub_objects(container, object, depth = 1) click to toggle source
# File lib/pdfwalker/treeview.rb, line 318
def load_sub_objects(container, object, depth = 1)
    return unless depth > 0 and @treestore.get_value(container, LOADCOL) != 1

    case object
    when Origami::Array
        object.each do |subobject|
            load_object(container, subobject, depth - 1)
        end

    when Origami::Dictionary
        object.each_key do |subkey|
            load_object(container, object[subkey.value], depth - 1, subkey.value.to_s)
        end

    when Origami::Stream
        load_object(container, object.dictionary, depth - 1, "Stream Dictionary")
    end

    @treestore.set_value(container, LOADCOL, 1)
end
load_trailer(rev, trailer) click to toggle source
# File lib/pdfwalker/treeview.rb, line 373
def load_trailer(rev, trailer)
    trailer_root = @treestore.append(rev)
    @treestore.set_value(trailer_root, OBJCOL, trailer)

    set_node(trailer_root, :Trailer, "Trailer")
    load_object(trailer_root, trailer.dictionary) unless trailer.dictionary.nil?
end
load_xrefs(rev, table) click to toggle source
# File lib/pdfwalker/treeview.rb, line 350
def load_xrefs(rev, table)
    return unless table

    section = @treestore.append(rev)
    @treestore.set_value(section, OBJCOL, table)

    set_node(section, :XRefSection, "XRef section")

    table.each_subsection { |subtable|
        subsection = @treestore.append(section)
        @treestore.set_value(subsection, OBJCOL, subtable)

        set_node(subsection, :XRefSubSection, "#{subtable.range.begin} #{subtable.range.end - subtable.range.begin + 1}")

        subtable.each { |entry|
            xref = @treestore.append(subsection)
            @treestore.set_value(xref, OBJCOL, entry)

            set_node(xref, :XRef, entry.to_s.chomp)
        }
    }
end
load_xrefstm(stm, embxref) click to toggle source
# File lib/pdfwalker/treeview.rb, line 339
def load_xrefstm(stm, embxref)
    xref = @treestore.append(stm)
    @treestore.set_value(xref, OBJCOL, embxref)

    if embxref.is_a?(Origami::XRef)
        set_node(xref, :XRef, embxref.to_s.chomp)
    else
        set_node(xref, :XRef, "xref to ObjectStream #{embxref.objstmno}, object index #{embxref.index}")
    end
end
object_to_tree_pos(obj) click to toggle source
# File lib/pdfwalker/treeview.rb, line 220
def object_to_tree_pos(obj)

    # Locate the indirect object.
    root_obj = obj
    object_path = [ root_obj ]
    while root_obj.parent
        root_obj = root_obj.parent
        object_path.push(root_obj)
    end

    @treestore.each do |_model, path, iter|
        current_obj = @treestore.get_value(iter, OBJCOL)

        # Load the intermediate nodes if necessary.
        if object_path.any?{|object| object.equal?(current_obj)}
            load_sub_objects(iter, current_obj)
        end

        # Unfold the object stream if it's in the object path.
        if obj.is_a?(Origami::Object) and current_obj.is_a?(Origami::ObjectStream) and
           root_obj.equal?(current_obj) and iter.n_children == 1

            current_obj.each { |embeddedobj|
                load_object(iter, embeddedobj)
            }
        end

        return [ iter, path ] if obj.equal?(current_obj)
    end

    nil
end
reset_appearance() click to toggle source
# File lib/pdfwalker/treeview.rb, line 381
def reset_appearance
    @@appearance[:Filename] = {Weight: :bold, Style: :normal}
    @@appearance[:Header] = {Color: "darkgreen", Weight: :bold, Style: :normal}
    @@appearance[:Revision] = {Color: "blue", Weight: :bold, Style: :normal}
    @@appearance[:Body] = {Color: "purple", Weight: :bold, Style: :normal}
    @@appearance[:XRefSection] = {Color: "purple", Weight: :bold, Style: :normal}
    @@appearance[:XRefSubSection] = {Color: "brown", Weight: :bold, Style: :normal}
    @@appearance[:XRef] = {Weight: :bold, Style: :normal}
    @@appearance[:Trailer] = {Color: "purple", Weight: :bold, Style: :normal}
    @@appearance[:StartXref] = {Weight: :bold, Style: :normal}
    @@appearance[:String] = {Color: "red", Weight: :normal, Style: :italic}
    @@appearance[:Name] = {Color: "gray", Weight: :normal, Style: :italic}
    @@appearance[:Number] = {Color: "orange", Weight: :normal, Style: :normal}
    @@appearance[:Dictionary] = {Color: "brown", Weight: :bold, Style: :normal}
    @@appearance[:Stream] = {Color: "darkcyan", Weight: :bold, Style: :normal}
    @@appearance[:StreamData] = {Color: "darkcyan", Weight: :normal, Style: :oblique}
    @@appearance[:Array] = {Color: "darkgreen", Weight: :bold, Style: :normal}
    @@appearance[:Reference] = {Weight: :normal, Style: :oblique}
    @@appearance[:Boolean] = {Color: "deeppink", Weight: :normal, Style: :normal}
end
set_node(node, type, text) click to toggle source
# File lib/pdfwalker/treeview.rb, line 406
def set_node(node, type, text)
    @treestore.set_value(node, TEXTCOL, text)

    app = get_object_appearance(type)
    @treestore.set_value(node, WEIGHTCOL, app[:Weight])
    @treestore.set_value(node, STYLECOL, app[:Style])
    @treestore.set_value(node, FGCOL, app[:Color])
end