origami 1.0.2
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.
- data/COPYING.LESSER +165 -0
- data/README +77 -0
- data/VERSION +1 -0
- data/bin/config/pdfcop.conf.yml +237 -0
- data/bin/gui/about.rb +46 -0
- data/bin/gui/config.rb +132 -0
- data/bin/gui/file.rb +385 -0
- data/bin/gui/hexdump.rb +74 -0
- data/bin/gui/hexview.rb +91 -0
- data/bin/gui/imgview.rb +72 -0
- data/bin/gui/menu.rb +392 -0
- data/bin/gui/properties.rb +132 -0
- data/bin/gui/signing.rb +635 -0
- data/bin/gui/textview.rb +107 -0
- data/bin/gui/treeview.rb +409 -0
- data/bin/gui/walker.rb +282 -0
- data/bin/gui/xrefs.rb +79 -0
- data/bin/pdf2graph +121 -0
- data/bin/pdf2ruby +353 -0
- data/bin/pdfcocoon +104 -0
- data/bin/pdfcop +455 -0
- data/bin/pdfdecompress +104 -0
- data/bin/pdfdecrypt +95 -0
- data/bin/pdfencrypt +112 -0
- data/bin/pdfextract +221 -0
- data/bin/pdfmetadata +123 -0
- data/bin/pdfsh +13 -0
- data/bin/pdfwalker +7 -0
- data/bin/shell/.irbrc +104 -0
- data/bin/shell/console.rb +136 -0
- data/bin/shell/hexdump.rb +83 -0
- data/origami.rb +36 -0
- data/origami/3d.rb +239 -0
- data/origami/acroform.rb +321 -0
- data/origami/actions.rb +299 -0
- data/origami/adobe/fdf.rb +259 -0
- data/origami/adobe/ppklite.rb +489 -0
- data/origami/annotations.rb +775 -0
- data/origami/array.rb +187 -0
- data/origami/boolean.rb +101 -0
- data/origami/catalog.rb +486 -0
- data/origami/destinations.rb +213 -0
- data/origami/dictionary.rb +188 -0
- data/origami/docmdp.rb +96 -0
- data/origami/encryption.rb +1293 -0
- data/origami/export.rb +283 -0
- data/origami/file.rb +222 -0
- data/origami/filters.rb +250 -0
- data/origami/filters/ascii.rb +189 -0
- data/origami/filters/ccitt.rb +515 -0
- data/origami/filters/crypt.rb +47 -0
- data/origami/filters/dct.rb +61 -0
- data/origami/filters/flate.rb +112 -0
- data/origami/filters/jbig2.rb +63 -0
- data/origami/filters/jpx.rb +53 -0
- data/origami/filters/lzw.rb +195 -0
- data/origami/filters/predictors.rb +276 -0
- data/origami/filters/runlength.rb +117 -0
- data/origami/font.rb +209 -0
- data/origami/functions.rb +93 -0
- data/origami/graphics.rb +33 -0
- data/origami/graphics/colors.rb +191 -0
- data/origami/graphics/instruction.rb +126 -0
- data/origami/graphics/path.rb +154 -0
- data/origami/graphics/patterns.rb +180 -0
- data/origami/graphics/state.rb +164 -0
- data/origami/graphics/text.rb +224 -0
- data/origami/graphics/xobject.rb +493 -0
- data/origami/header.rb +90 -0
- data/origami/linearization.rb +318 -0
- data/origami/metadata.rb +114 -0
- data/origami/name.rb +170 -0
- data/origami/null.rb +75 -0
- data/origami/numeric.rb +188 -0
- data/origami/obfuscation.rb +233 -0
- data/origami/object.rb +527 -0
- data/origami/outline.rb +59 -0
- data/origami/page.rb +559 -0
- data/origami/parser.rb +268 -0
- data/origami/parsers/fdf.rb +45 -0
- data/origami/parsers/pdf.rb +27 -0
- data/origami/parsers/pdf/linear.rb +113 -0
- data/origami/parsers/ppklite.rb +86 -0
- data/origami/pdf.rb +1144 -0
- data/origami/reference.rb +113 -0
- data/origami/signature.rb +474 -0
- data/origami/stream.rb +575 -0
- data/origami/string.rb +416 -0
- data/origami/trailer.rb +173 -0
- data/origami/webcapture.rb +87 -0
- data/origami/xfa.rb +3027 -0
- data/origami/xreftable.rb +447 -0
- data/templates/patterns.rb +66 -0
- data/templates/widgets.rb +173 -0
- data/templates/xdp.rb +92 -0
- data/tests/dataset/test.dummycrt +28 -0
- data/tests/dataset/test.dummykey +27 -0
- data/tests/tc_actions.rb +32 -0
- data/tests/tc_annotations.rb +85 -0
- data/tests/tc_pages.rb +37 -0
- data/tests/tc_pdfattach.rb +24 -0
- data/tests/tc_pdfencrypt.rb +110 -0
- data/tests/tc_pdfnew.rb +32 -0
- data/tests/tc_pdfparse.rb +98 -0
- data/tests/tc_pdfsig.rb +37 -0
- data/tests/tc_streams.rb +129 -0
- data/tests/ts_pdf.rb +45 -0
- metadata +193 -0
data/origami/array.rb
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
= File
|
4
|
+
array.rb
|
5
|
+
|
6
|
+
= Info
|
7
|
+
This file is part of Origami, PDF manipulation framework for Ruby
|
8
|
+
Copyright (C) 2010 Guillaume DelugrÈ <guillaume@security-labs.org>
|
9
|
+
All right reserved.
|
10
|
+
|
11
|
+
Origami is free software: you can redistribute it and/or modify
|
12
|
+
it under the terms of the GNU Lesser General Public License as published by
|
13
|
+
the Free Software Foundation, either version 3 of the License, or
|
14
|
+
(at your option) any later version.
|
15
|
+
|
16
|
+
Origami is distributed in the hope that it will be useful,
|
17
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
18
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
19
|
+
GNU Lesser General Public License for more details.
|
20
|
+
|
21
|
+
You should have received a copy of the GNU Lesser General Public License
|
22
|
+
along with Origami. If not, see <http://www.gnu.org/licenses/>.
|
23
|
+
|
24
|
+
=end
|
25
|
+
|
26
|
+
|
27
|
+
module Origami
|
28
|
+
|
29
|
+
class InvalidArrayObjectError < InvalidObjectError #:nodoc:
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Class representing an Array Object.
|
34
|
+
# Arrays contain a set of Object.
|
35
|
+
#
|
36
|
+
class Array < ::Array
|
37
|
+
include Origami::Object
|
38
|
+
|
39
|
+
TOKENS = %w{ [ ] } #:nodoc:
|
40
|
+
@@regexp_open = Regexp.new(WHITESPACES + Regexp.escape(TOKENS.first) + WHITESPACES)
|
41
|
+
@@regexp_close = Regexp.new(WHITESPACES + Regexp.escape(TOKENS.last) + WHITESPACES)
|
42
|
+
|
43
|
+
attr_reader :strings_cache, :names_cache, :xref_cache
|
44
|
+
|
45
|
+
#
|
46
|
+
# Creates a new PDF Array Object.
|
47
|
+
# _data_:: An array of objects.
|
48
|
+
#
|
49
|
+
def initialize(data = [])
|
50
|
+
raise TypeError, "Expected type Array, received #{data.class}." unless data.is_a?(::Array)
|
51
|
+
super()
|
52
|
+
|
53
|
+
@strings_cache = []
|
54
|
+
@names_cache = []
|
55
|
+
@xref_cache = {}
|
56
|
+
|
57
|
+
i = 0
|
58
|
+
while i < data.size
|
59
|
+
case val = data[i].to_o
|
60
|
+
when String then @strings_cache.push(val)
|
61
|
+
when Name then @names_cache.push(val)
|
62
|
+
when Reference then
|
63
|
+
(@xref_cache[val] ||= []).push(self)
|
64
|
+
when Dictionary,Array then
|
65
|
+
@strings_cache.concat(val.strings_cache)
|
66
|
+
@names_cache.concat(val.names_cache)
|
67
|
+
@xref_cache.update(val.xref_cache) do |ref, cache1, cache2|
|
68
|
+
cache1.concat(cache2)
|
69
|
+
end
|
70
|
+
|
71
|
+
val.strings_cache.clear
|
72
|
+
val.names_cache.clear
|
73
|
+
val.xref_cache.clear
|
74
|
+
end
|
75
|
+
|
76
|
+
self[i] = val
|
77
|
+
i = i + 1
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
def pre_build
|
83
|
+
self.map!{|obj| obj.to_o}
|
84
|
+
|
85
|
+
super
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.parse(stream) #:nodoc:
|
89
|
+
data = []
|
90
|
+
offset = stream.pos
|
91
|
+
|
92
|
+
if not stream.skip(@@regexp_open)
|
93
|
+
raise InvalidArrayObjectError, "No token '#{TOKENS.first}' found"
|
94
|
+
end
|
95
|
+
|
96
|
+
while stream.skip(@@regexp_close).nil? do
|
97
|
+
|
98
|
+
type = Object.typeof(stream)
|
99
|
+
if type.nil?
|
100
|
+
raise InvalidArrayObjectError, "Bad embedded object format"
|
101
|
+
end
|
102
|
+
|
103
|
+
value = type.parse(stream)
|
104
|
+
data << value
|
105
|
+
end
|
106
|
+
|
107
|
+
array = Array.new(data)
|
108
|
+
array.file_offset = offset
|
109
|
+
|
110
|
+
array
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Converts self into a Ruby array.
|
115
|
+
#
|
116
|
+
def to_a
|
117
|
+
super.map { |item|
|
118
|
+
item.is_a?(Origami::Object) ? item.value : item
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
def to_s #:nodoc:
|
123
|
+
content = "#{TOKENS.first} "
|
124
|
+
self.each { |entry|
|
125
|
+
content << entry.to_o.to_s + ' '
|
126
|
+
}
|
127
|
+
content << TOKENS.last
|
128
|
+
|
129
|
+
super(content)
|
130
|
+
end
|
131
|
+
|
132
|
+
def +(other)
|
133
|
+
|
134
|
+
a = Origami::Array.new(self.to_a + other.to_a, is_indirect?)
|
135
|
+
a.no, a.generation = @no, @generation
|
136
|
+
|
137
|
+
return a
|
138
|
+
end
|
139
|
+
|
140
|
+
def <<(item)
|
141
|
+
obj = item.to_o
|
142
|
+
obj.parent = self unless obj.is_indirect?
|
143
|
+
|
144
|
+
super(obj)
|
145
|
+
end
|
146
|
+
|
147
|
+
def []=(key,val)
|
148
|
+
key, val = key.to_o, val.to_o
|
149
|
+
super(key.to_o,val.to_o)
|
150
|
+
|
151
|
+
val.parent = self unless val.is_indirect? or val.parent.equal?(self)
|
152
|
+
|
153
|
+
val
|
154
|
+
end
|
155
|
+
|
156
|
+
alias value to_a
|
157
|
+
|
158
|
+
def real_type ; Origami::Array end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# Class representing a location on a page or a bounding box.
|
164
|
+
#
|
165
|
+
class Rectangle < Array
|
166
|
+
|
167
|
+
class << self
|
168
|
+
|
169
|
+
def [](coords)
|
170
|
+
corners = coords.values_at(:llx, :lly, :urx, :ury)
|
171
|
+
|
172
|
+
unless corners.all? { |corner| corner.is_a?(Numeric) }
|
173
|
+
raise TypeError, "All coords must be numbers"
|
174
|
+
end
|
175
|
+
|
176
|
+
Rectangle.new(*corners)
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
|
181
|
+
def initialize(lowerleftx, lowerlefty, upperrightx, upperrighty)
|
182
|
+
super([ lowerleftx, lowerlefty, upperrightx, upperrighty ])
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
data/origami/boolean.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
= File
|
4
|
+
boolean.rb
|
5
|
+
|
6
|
+
= Info
|
7
|
+
This file is part of Origami, PDF manipulation framework for Ruby
|
8
|
+
Copyright (C) 2010 Guillaume DelugrÈ <guillaume@security-labs.org>
|
9
|
+
All right reserved.
|
10
|
+
|
11
|
+
Origami is free software: you can redistribute it and/or modify
|
12
|
+
it under the terms of the GNU Lesser General Public License as published by
|
13
|
+
the Free Software Foundation, either version 3 of the License, or
|
14
|
+
(at your option) any later version.
|
15
|
+
|
16
|
+
Origami is distributed in the hope that it will be useful,
|
17
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
18
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
19
|
+
GNU Lesser General Public License for more details.
|
20
|
+
|
21
|
+
You should have received a copy of the GNU Lesser General Public License
|
22
|
+
along with Origami. If not, see <http://www.gnu.org/licenses/>.
|
23
|
+
|
24
|
+
=end
|
25
|
+
|
26
|
+
module Origami
|
27
|
+
|
28
|
+
class InvalidBooleanObjectError < InvalidObjectError #:nodoc:
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Class representing a Boolean Object.
|
33
|
+
# A Boolean Object can be *true* or *false*.
|
34
|
+
#
|
35
|
+
class Boolean
|
36
|
+
|
37
|
+
include Origami::Object
|
38
|
+
|
39
|
+
TOKENS = [ %w{ true false } ] #:nodoc:
|
40
|
+
|
41
|
+
@@regexp = Regexp.new(WHITESPACES + "(#{TOKENS.first.join('|')})")
|
42
|
+
|
43
|
+
#
|
44
|
+
# Creates a new Boolean value.
|
45
|
+
# _value_:: *true* or *false*.
|
46
|
+
#
|
47
|
+
def initialize(value)
|
48
|
+
|
49
|
+
unless value.is_a?(TrueClass) or value.is_a?(FalseClass)
|
50
|
+
raise TypeError, "Expected type TrueClass or FalseClass, received #{value.class}."
|
51
|
+
end
|
52
|
+
|
53
|
+
super()
|
54
|
+
|
55
|
+
@value = (value == nil || value == false) ? false : true
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s #:nodoc:
|
59
|
+
super(@value.to_s)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.parse(stream) #:nodoc:
|
63
|
+
|
64
|
+
offset = stream.pos
|
65
|
+
|
66
|
+
if stream.scan(@@regexp).nil?
|
67
|
+
raise InvalidBooleanObjectError
|
68
|
+
end
|
69
|
+
|
70
|
+
value = stream[2] == "true" ? true : false
|
71
|
+
|
72
|
+
bool = Boolean.new(value)
|
73
|
+
bool.file_offset = offset
|
74
|
+
|
75
|
+
bool
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Converts self into a Ruby boolean, that is TrueClass or FalseClass instance.
|
80
|
+
#
|
81
|
+
def value
|
82
|
+
@value
|
83
|
+
end
|
84
|
+
|
85
|
+
def real_type ; Boolean end
|
86
|
+
|
87
|
+
def false?
|
88
|
+
@value == false
|
89
|
+
end
|
90
|
+
|
91
|
+
def true?
|
92
|
+
@value == true
|
93
|
+
end
|
94
|
+
|
95
|
+
def ==(bool)
|
96
|
+
@value == bool
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
data/origami/catalog.rb
ADDED
@@ -0,0 +1,486 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
= File
|
4
|
+
catalog.rb
|
5
|
+
|
6
|
+
= Info
|
7
|
+
This file is part of Origami, PDF manipulation framework for Ruby
|
8
|
+
Copyright (C) 2010 Guillaume Delugr� <guillaume@security-labs.org>
|
9
|
+
All right reserved.
|
10
|
+
|
11
|
+
Origami is free software: you can redistribute it and/or modify
|
12
|
+
it under the terms of the GNU Lesser General Public License as published by
|
13
|
+
the Free Software Foundation, either version 3 of the License, or
|
14
|
+
(at your option) any later version.
|
15
|
+
|
16
|
+
Origami is distributed in the hope that it will be useful,
|
17
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
18
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
19
|
+
GNU Lesser General Public License for more details.
|
20
|
+
|
21
|
+
You should have received a copy of the GNU Lesser General Public License
|
22
|
+
along with Origami. If not, see <http://www.gnu.org/licenses/>.
|
23
|
+
|
24
|
+
=end
|
25
|
+
|
26
|
+
module Origami
|
27
|
+
|
28
|
+
class PDF
|
29
|
+
|
30
|
+
#
|
31
|
+
# Sets PDF extension level and version. Only supported values are "1.7" and 3.
|
32
|
+
#
|
33
|
+
def set_extension_level(version, level)
|
34
|
+
exts = (self.Catalog.Extensions ||= Extensions.new)
|
35
|
+
|
36
|
+
exts[:ADBE] = DeveloperExtension.new
|
37
|
+
exts[:ADBE].BaseVersion = Name.new(version)
|
38
|
+
exts[:ADBE].ExtensionLevel = level
|
39
|
+
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Returns the current Catalog Dictionary.
|
45
|
+
#
|
46
|
+
def Catalog
|
47
|
+
cat = get_doc_attr(:Root)
|
48
|
+
|
49
|
+
case cat
|
50
|
+
when Catalog then
|
51
|
+
cat
|
52
|
+
when Dictionary then
|
53
|
+
casted = Catalog.new(cat)
|
54
|
+
casted.no, casted.generation = cat.no, cat.generation
|
55
|
+
casted.set_indirect(true)
|
56
|
+
casted.set_pdf(self)
|
57
|
+
|
58
|
+
casted
|
59
|
+
else
|
60
|
+
raise InvalidPDFError, "Broken catalog"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# Sets the current Catalog Dictionary.
|
66
|
+
#
|
67
|
+
def Catalog=(cat)
|
68
|
+
#unless cat.is_a?(Catalog)
|
69
|
+
# raise TypeError, "Expected type Catalog, received #{cat.class}"
|
70
|
+
#end
|
71
|
+
cat = Catalog.new(cat) unless cat.is_a? Catalog
|
72
|
+
|
73
|
+
if @revisions.last.trailer.Root
|
74
|
+
delete_object(@revisions.last.trailer[:Root])
|
75
|
+
end
|
76
|
+
|
77
|
+
@revisions.last.trailer.Root = self << cat
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# Sets an action to run on document opening.
|
82
|
+
# _action_:: An Action Object.
|
83
|
+
#
|
84
|
+
def onDocumentOpen(action)
|
85
|
+
|
86
|
+
unless action.is_a?(Action) or action.is_a?(Destination) or action.is_a?(Reference)
|
87
|
+
raise TypeError, "An Action object must be passed."
|
88
|
+
end
|
89
|
+
|
90
|
+
unless self.Catalog
|
91
|
+
raise InvalidPDFError, "A catalog object must exist to add this action."
|
92
|
+
end
|
93
|
+
|
94
|
+
self.Catalog.OpenAction = action
|
95
|
+
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# Sets an action to run on document closing.
|
101
|
+
# _action_:: A JavaScript Action Object.
|
102
|
+
#
|
103
|
+
def onDocumentClose(action)
|
104
|
+
|
105
|
+
unless action.is_a?(Action::JavaScript) or action.is_a?(Reference)
|
106
|
+
raise TypeError, "An Action::JavaScript object must be passed."
|
107
|
+
end
|
108
|
+
|
109
|
+
unless self.Catalog
|
110
|
+
raise InvalidPDFError, "A catalog object must exist to add this action."
|
111
|
+
end
|
112
|
+
|
113
|
+
self.Catalog.AA ||= CatalogAdditionalActions.new
|
114
|
+
self.Catalog.AA.WC = action
|
115
|
+
|
116
|
+
self
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Sets an action to run on document printing.
|
121
|
+
# _action_:: A JavaScript Action Object.
|
122
|
+
#
|
123
|
+
def onDocumentPrint(action)
|
124
|
+
|
125
|
+
unless action.is_a?(Action::JavaScript) or action.is_a?(Reference)
|
126
|
+
raise TypeError, "An Action::JavaScript object must be passed."
|
127
|
+
end
|
128
|
+
|
129
|
+
unless self.Catalog
|
130
|
+
raise InvalidPDFError, "A catalog object must exist to add this action."
|
131
|
+
end
|
132
|
+
|
133
|
+
self.Catalog.AA ||= CatalogAdditionalActions.new
|
134
|
+
self.Catalog.AA.WP = action
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
#
|
139
|
+
# Registers an object into a specific Names root dictionary.
|
140
|
+
# _root_:: The root dictionary (see Names::Root)
|
141
|
+
# _name_:: The value name.
|
142
|
+
# _value_:: The value to associate with this name.
|
143
|
+
#
|
144
|
+
def register(root, name, value)
|
145
|
+
self.Catalog.Names ||= Names.new
|
146
|
+
|
147
|
+
value.set_indirect(true) unless value.is_a? Reference
|
148
|
+
|
149
|
+
namesroot = self.Catalog.Names[root]
|
150
|
+
if namesroot.nil?
|
151
|
+
names = NameTreeNode.new(:Names => []).set_indirect(true)
|
152
|
+
self.Catalog.Names[root] = names
|
153
|
+
names.Names << name << value
|
154
|
+
else
|
155
|
+
namesroot.solve[:Names] << name << value
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def each_name(root, &b)
|
160
|
+
namesroot = get_names_root(root)
|
161
|
+
return if namesroot.nil?
|
162
|
+
|
163
|
+
each_name_from_node(namesroot, [], &b)
|
164
|
+
self
|
165
|
+
end
|
166
|
+
|
167
|
+
#
|
168
|
+
# Retrieve the corresponding value associated with _name_ in
|
169
|
+
# the specified _root_ name directory, or nil if the value does
|
170
|
+
# not exist.
|
171
|
+
#
|
172
|
+
def resolve_name(root, name)
|
173
|
+
namesroot = get_names_root(root)
|
174
|
+
return nil if namesroot.nil?
|
175
|
+
|
176
|
+
resolve_name_from_node(namesroot, name)
|
177
|
+
end
|
178
|
+
|
179
|
+
#
|
180
|
+
# Returns a Hash of all names under specified _root_ name directory.
|
181
|
+
# Returns nil if the directory does not exist.
|
182
|
+
#
|
183
|
+
def ls_names(root)
|
184
|
+
namesroot = get_names_root(root)
|
185
|
+
return {} if namesroot.nil?
|
186
|
+
|
187
|
+
names = names_from_node(namesroot)
|
188
|
+
if names.length % 2 != 0
|
189
|
+
return InvalidNameTreeError, "Odd number of elements"
|
190
|
+
end
|
191
|
+
|
192
|
+
Hash[*names]
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
def names_from_node(node, browsed_nodes = []) #:nodoc:
|
198
|
+
children = []
|
199
|
+
|
200
|
+
unless browsed_nodes.any? {|browsed| browsed.equal?(node)}
|
201
|
+
browsed_nodes.push(node)
|
202
|
+
if node.has_key?(:Names) # leaf node
|
203
|
+
children.concat(node[:Names].solve)
|
204
|
+
elsif node.has_key?(:Kids) # intermediate node
|
205
|
+
node[:Kids].solve.each do |kid|
|
206
|
+
children.concat(names_from_node(kid.solve, browsed_nodes))
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
children
|
212
|
+
end
|
213
|
+
|
214
|
+
def resolve_name_from_node(node, name, browsed_nodes = []) #:nodoc:
|
215
|
+
unless browsed_nodes.any? {|browsed| browsed.equal?(node)}
|
216
|
+
browsed_nodes.push(node)
|
217
|
+
|
218
|
+
if node.has_key?(:Names) # leaf node
|
219
|
+
limits = node[:Limits]
|
220
|
+
|
221
|
+
if limits
|
222
|
+
limits = limits.solve
|
223
|
+
min, max = limits[0].value, limits[1].value
|
224
|
+
if (min..max) === name.to_str
|
225
|
+
names = Hash[*node[:Names].solve]
|
226
|
+
target = names[name]
|
227
|
+
return target && target.solve
|
228
|
+
end
|
229
|
+
else
|
230
|
+
names = Hash[*node[:Names].solve]
|
231
|
+
target = names[name]
|
232
|
+
return target && target.solve
|
233
|
+
end
|
234
|
+
|
235
|
+
elsif node.has_key?(:Kids) # intermediate node
|
236
|
+
node[:Kids].solve.each do |kid|
|
237
|
+
kid = kid.solve
|
238
|
+
limits = kid[:Limits].solve
|
239
|
+
min, max = limits[0].value, limits[1].value
|
240
|
+
|
241
|
+
if (min..max) === name.to_str
|
242
|
+
return resolve_name_from_node(kid, name, browsed_nodes)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def each_name_from_node(node, browsed_nodes = [], &b) #:nodoc:
|
250
|
+
if node.has_key?(:Names) # leaf node
|
251
|
+
names = Hash[*node[:Names].solve]
|
252
|
+
names.each_pair do |name, value|
|
253
|
+
b.call(name, value.solve)
|
254
|
+
end
|
255
|
+
elsif node.has_key?(:Kids) # intermediate node
|
256
|
+
node[:Kids].solve.each do |kid|
|
257
|
+
each_name_from_node(kid.solve, browsed_nodes, &b)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def get_names_root(root) #:nodoc:
|
263
|
+
namedirs = self.Catalog.Names
|
264
|
+
return nil if namedirs.nil? or namedirs[root].nil?
|
265
|
+
|
266
|
+
namedirs[root].solve
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
module PageLayout #:nodoc:
|
271
|
+
SINGLE = :SinglePage
|
272
|
+
ONE_COLUMN = :OneColumn
|
273
|
+
TWO_COLUMN_LEFT = :TwoColumnLeft
|
274
|
+
TWO_COLUMN_RIGHT = :TwoColumnRight
|
275
|
+
TWO_PAGE_LEFT = :TwoPageLeft
|
276
|
+
TWO_PAGE_RIGHT = :TwoPageRight
|
277
|
+
end
|
278
|
+
|
279
|
+
module PageMode #:nodoc:
|
280
|
+
NONE = :UseNone
|
281
|
+
OUTLINES = :UseOutlines
|
282
|
+
THUMBS = :UseThumbs
|
283
|
+
FULLSCREEN = :FullScreen
|
284
|
+
OPTIONAL_CONTENT = :UseOC
|
285
|
+
ATTACHMENTS = :UseAttachments
|
286
|
+
end
|
287
|
+
|
288
|
+
#
|
289
|
+
# Class representing the Catalog Dictionary of a PDF file.
|
290
|
+
#
|
291
|
+
class Catalog < Dictionary
|
292
|
+
|
293
|
+
include StandardObject
|
294
|
+
|
295
|
+
field :Type, :Type => Name, :Default => :Catalog, :Required => true
|
296
|
+
field :Version, :Type => Name, :Version => "1.4"
|
297
|
+
field :Pages, :Type => Dictionary, :Required => true
|
298
|
+
field :PageLabels, :Type => Dictionary, :Version => "1.3"
|
299
|
+
field :Names, :Type => Dictionary, :Version => "1.2"
|
300
|
+
field :Dests, :Type => Dictionary, :Version => "1.1"
|
301
|
+
field :ViewerPreferences, :Type => Dictionary, :Version => "1.2"
|
302
|
+
field :PageLayout, :Type => Name, :Default => PageLayout::SINGLE
|
303
|
+
field :PageMode, :Type => Name, :Default => PageMode::NONE
|
304
|
+
field :Outlines, :Type => Dictionary
|
305
|
+
field :Threads, :Type => Array, :Version => "1.1"
|
306
|
+
field :OpenAction, :Type => [ Array, Dictionary ], :Version => "1.1"
|
307
|
+
field :AA, :Type => Dictionary, :Version => "1.4"
|
308
|
+
field :URI, :Type => Dictionary, :Version => "1.1"
|
309
|
+
field :AcroForm, :Type => Dictionary, :Version => "1.2"
|
310
|
+
field :Metadata, :Type => Stream, :Version => "1.4"
|
311
|
+
field :StructTreeRoot, :Type => Dictionary, :Version => "1.3"
|
312
|
+
field :MarkInfo, :Type => Dictionary, :Version => "1.4"
|
313
|
+
field :Lang, :Type => String, :Version => "1.4"
|
314
|
+
field :SpiderInfo, :Type => Dictionary, :Version => "1.3"
|
315
|
+
field :OutputIntents, :Type => Array, :Version => "1.4"
|
316
|
+
field :PieceInfo, :Type => Dictionary, :Version => "1.4"
|
317
|
+
field :OCProperties, :Type => Dictionary, :Version => "1.5"
|
318
|
+
field :Perms, :Type => Dictionary, :Version => "1.5"
|
319
|
+
field :Legal, :Type => Dictionary, :Version => "1.5"
|
320
|
+
field :Requirements, :Type => Array, :Version => "1.7"
|
321
|
+
field :Collection, :Type => Dictionary, :Version => "1.7"
|
322
|
+
field :NeedsRendering, :Type => Boolean, :Version => "1.7", :Default => false
|
323
|
+
field :Extensions, :Type => Dictionary, :Version => "1.7", :ExtensionLevel => 3
|
324
|
+
|
325
|
+
def initialize(hash = {})
|
326
|
+
set_indirect(true)
|
327
|
+
|
328
|
+
super(hash)
|
329
|
+
end
|
330
|
+
|
331
|
+
end
|
332
|
+
|
333
|
+
#
|
334
|
+
# Class representing additional actions which can be associated with a Catalog.
|
335
|
+
#
|
336
|
+
class CatalogAdditionalActions < Dictionary
|
337
|
+
include StandardObject
|
338
|
+
|
339
|
+
field :WC, :Type => Dictionary, :Version => "1.4"
|
340
|
+
field :WS, :Type => Dictionary, :Version => "1.4"
|
341
|
+
field :DS, :Type => Dictionary, :Version => "1.4"
|
342
|
+
field :WP, :Type => Dictionary, :Version => "1.4"
|
343
|
+
field :DP, :Type => Dictionary, :Version => "1.4"
|
344
|
+
end
|
345
|
+
|
346
|
+
class InvalidNameTreeError < Exception #:nodoc:
|
347
|
+
end
|
348
|
+
|
349
|
+
#
|
350
|
+
# Class representing the Names Dictionary of a PDF file.
|
351
|
+
#
|
352
|
+
class Names < Dictionary
|
353
|
+
include StandardObject
|
354
|
+
|
355
|
+
#
|
356
|
+
# Defines constants for Names tree root entries.
|
357
|
+
#
|
358
|
+
module Root
|
359
|
+
DESTS = :Dests
|
360
|
+
AP = :AP
|
361
|
+
JAVASCRIPT = :JavaScript
|
362
|
+
PAGES = :Pages
|
363
|
+
TEMPLATES = :Templates
|
364
|
+
IDS = :IDS
|
365
|
+
URLS = :URLS
|
366
|
+
EMBEDDEDFILES = :EmbeddedFiles
|
367
|
+
ALTERNATEPRESENTATIONS = :AlternatePresentations
|
368
|
+
RENDITIONS = :Renditions
|
369
|
+
XFARESOURCES = :XFAResources
|
370
|
+
end
|
371
|
+
|
372
|
+
field Root::DESTS, :Type => Dictionary, :Version => "1.2"
|
373
|
+
field Root::AP, :Type => Dictionary, :Version => "1.3"
|
374
|
+
field Root::JAVASCRIPT, :Type => Dictionary, :Version => "1.3"
|
375
|
+
field Root::PAGES, :Type => Dictionary, :Version => "1.3"
|
376
|
+
field Root::TEMPLATES, :Type => Dictionary, :Version => "1.3"
|
377
|
+
field Root::IDS, :Type => Dictionary, :Version => "1.3"
|
378
|
+
field Root::URLS, :Type => Dictionary, :Version => "1.3"
|
379
|
+
field Root::EMBEDDEDFILES, :Type => Dictionary, :Version => "1.4"
|
380
|
+
field Root::ALTERNATEPRESENTATIONS, :Type => Dictionary, :Version => "1.4"
|
381
|
+
field Root::RENDITIONS, :Type => Dictionary, :Version => "1.5"
|
382
|
+
field Root::XFARESOURCES, :Type => Dictionary, :Version => "1.7", :ExtensionLevel => 3
|
383
|
+
end
|
384
|
+
|
385
|
+
#
|
386
|
+
# Class representing a node in a Name tree.
|
387
|
+
#
|
388
|
+
class NameTreeNode < Dictionary
|
389
|
+
include StandardObject
|
390
|
+
|
391
|
+
field :Kids, :Type => Array
|
392
|
+
field :Names, :Type => Array
|
393
|
+
field :Limits, :Type => Array
|
394
|
+
end
|
395
|
+
|
396
|
+
#
|
397
|
+
# Class representing a leaf in a Name tree.
|
398
|
+
#
|
399
|
+
class NameLeaf < Origami::Array
|
400
|
+
|
401
|
+
#
|
402
|
+
# Creates a new leaf in a Name tree.
|
403
|
+
# _hash_:: A hash of couples, associating a Name with an Reference.
|
404
|
+
#
|
405
|
+
def initialize(hash = {})
|
406
|
+
|
407
|
+
names = []
|
408
|
+
hash.each_pair do |k,v|
|
409
|
+
names << k.to_o << v.to_o
|
410
|
+
end
|
411
|
+
|
412
|
+
super(names)
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
#
|
417
|
+
# Class representing the ViewerPreferences Dictionary of a PDF.
|
418
|
+
# This dictionary modifies the way the UI looks when the file is opened in a viewer.
|
419
|
+
#
|
420
|
+
class ViewerPreferences < Dictionary
|
421
|
+
include StandardObject
|
422
|
+
|
423
|
+
field :HideToolbar, :Type => Boolean, :Default => false
|
424
|
+
field :HideMenubar, :Type => Boolean, :Default => false
|
425
|
+
field :HideWindowUI, :Type => Boolean, :Default => false
|
426
|
+
field :FitWindow, :Type => Boolean, :Default => false
|
427
|
+
field :CenterWindow, :Type => Boolean, :Default => false
|
428
|
+
field :DisplayDocTitle, :Type => Boolean, :Default => false, :Version => "1.4"
|
429
|
+
field :NonFullScreenPageMode, :Type => Name, :Default => :UseNone
|
430
|
+
field :Direction, :Type => Name, :Default => :L2R
|
431
|
+
field :ViewArea, :Type => Name, :Default => :CropBox, :Version => "1.4"
|
432
|
+
field :ViewClip, :Type => Name, :Default => :CropBox, :Version => "1.4"
|
433
|
+
field :PrintArea, :Type => Name, :Default => :CropBox, :Version => "1.4"
|
434
|
+
field :PrintClip, :Type => Name, :Default => :CropBox, :Version => "1.4"
|
435
|
+
field :PrintScaling, :Type => Name, :Default => :AppDefault, :Version => "1.6"
|
436
|
+
field :Duplex, :Type => Name, :Default => :Simplex, :Version => "1.7"
|
437
|
+
field :PickTrayByPDFSize, :Type => Boolean, :Version => "1.7"
|
438
|
+
field :PrintPageRange, :Type => Array, :Version => "1.7"
|
439
|
+
field :NumCopies, :Type => Integer, :Version => "1.7"
|
440
|
+
field :Enforce, :Type => Array, :Version => "1.7", :ExtensionLevel => 3
|
441
|
+
|
442
|
+
end
|
443
|
+
|
444
|
+
class Requirement < Dictionary
|
445
|
+
include StandardObject
|
446
|
+
|
447
|
+
class Handler < Dictionary
|
448
|
+
include StandardObject
|
449
|
+
|
450
|
+
module Type
|
451
|
+
JS = :JS
|
452
|
+
NOOP = :NoOp
|
453
|
+
end
|
454
|
+
|
455
|
+
field :Type, :Type => Name, :Default => :ReqHandler
|
456
|
+
field :S, :Type => Name, :Default => Type::NOOP, :Required => true
|
457
|
+
field :Script, :Type => ByteString
|
458
|
+
end
|
459
|
+
|
460
|
+
field :Type, :Type => Name, :Default => :Requirement
|
461
|
+
field :S, :Type => Name, :Default => :EnableJavaScripts, :Version => "1.7", :Required => true
|
462
|
+
field :RH, :Type => Array
|
463
|
+
end
|
464
|
+
|
465
|
+
#
|
466
|
+
# Class representing an extension Dictionary.
|
467
|
+
#
|
468
|
+
class Extensions < Dictionary
|
469
|
+
include StandardObject
|
470
|
+
|
471
|
+
field :Type, :Type => Name, :Default => :Extensions
|
472
|
+
end
|
473
|
+
|
474
|
+
#
|
475
|
+
# Class representing a developer extension.
|
476
|
+
#
|
477
|
+
class DeveloperExtension < Dictionary
|
478
|
+
include StandardObject
|
479
|
+
|
480
|
+
field :Type, :Type => Name, :Default => :DeveloperExtensions
|
481
|
+
field :BaseVersion, :Type => Name, :Required => true
|
482
|
+
field :ExtensionLevel, :Type => Integer, :Required => true
|
483
|
+
|
484
|
+
end
|
485
|
+
|
486
|
+
end
|