origamindee 3.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/CHANGELOG.md +89 -0
- data/COPYING.LESSER +165 -0
- data/README.md +131 -0
- data/bin/config/pdfcop.conf.yml +236 -0
- data/bin/pdf2pdfa +87 -0
- data/bin/pdf2ruby +333 -0
- data/bin/pdfcop +476 -0
- data/bin/pdfdecompress +97 -0
- data/bin/pdfdecrypt +91 -0
- data/bin/pdfencrypt +113 -0
- data/bin/pdfexplode +223 -0
- data/bin/pdfextract +277 -0
- data/bin/pdfmetadata +143 -0
- data/bin/pdfsh +12 -0
- data/bin/shell/console.rb +128 -0
- data/bin/shell/hexdump.rb +59 -0
- data/bin/shell/irbrc +69 -0
- data/examples/README.md +34 -0
- data/examples/attachments/attachment.rb +38 -0
- data/examples/attachments/nested_document.rb +51 -0
- data/examples/encryption/encryption.rb +28 -0
- data/examples/events/events.rb +72 -0
- data/examples/flash/flash.rb +37 -0
- data/examples/flash/helloworld.swf +0 -0
- data/examples/forms/javascript.rb +54 -0
- data/examples/forms/xfa.rb +115 -0
- data/examples/javascript/hello_world.rb +22 -0
- data/examples/javascript/js_emulation.rb +54 -0
- data/examples/loop/goto.rb +32 -0
- data/examples/loop/named.rb +33 -0
- data/examples/signature/signature.rb +65 -0
- data/examples/uri/javascript.rb +56 -0
- data/examples/uri/open-uri.rb +21 -0
- data/examples/uri/submitform.rb +47 -0
- data/lib/origami/3d.rb +364 -0
- data/lib/origami/acroform.rb +321 -0
- data/lib/origami/actions.rb +318 -0
- data/lib/origami/annotations.rb +711 -0
- data/lib/origami/array.rb +242 -0
- data/lib/origami/boolean.rb +90 -0
- data/lib/origami/catalog.rb +418 -0
- data/lib/origami/collections.rb +144 -0
- data/lib/origami/compound.rb +161 -0
- data/lib/origami/destinations.rb +252 -0
- data/lib/origami/dictionary.rb +192 -0
- data/lib/origami/encryption.rb +1084 -0
- data/lib/origami/extensions/fdf.rb +347 -0
- data/lib/origami/extensions/ppklite.rb +422 -0
- data/lib/origami/filespec.rb +197 -0
- data/lib/origami/filters/ascii.rb +211 -0
- data/lib/origami/filters/ccitt/tables.rb +267 -0
- data/lib/origami/filters/ccitt.rb +357 -0
- data/lib/origami/filters/crypt.rb +38 -0
- data/lib/origami/filters/dct.rb +54 -0
- data/lib/origami/filters/flate.rb +69 -0
- data/lib/origami/filters/jbig2.rb +57 -0
- data/lib/origami/filters/jpx.rb +47 -0
- data/lib/origami/filters/lzw.rb +170 -0
- data/lib/origami/filters/predictors.rb +292 -0
- data/lib/origami/filters/runlength.rb +129 -0
- data/lib/origami/filters.rb +364 -0
- data/lib/origami/font.rb +196 -0
- data/lib/origami/functions.rb +79 -0
- data/lib/origami/graphics/colors.rb +230 -0
- data/lib/origami/graphics/instruction.rb +98 -0
- data/lib/origami/graphics/path.rb +182 -0
- data/lib/origami/graphics/patterns.rb +174 -0
- data/lib/origami/graphics/render.rb +62 -0
- data/lib/origami/graphics/state.rb +149 -0
- data/lib/origami/graphics/text.rb +225 -0
- data/lib/origami/graphics/xobject.rb +918 -0
- data/lib/origami/graphics.rb +38 -0
- data/lib/origami/header.rb +75 -0
- data/lib/origami/javascript.rb +713 -0
- data/lib/origami/linearization.rb +330 -0
- data/lib/origami/metadata.rb +172 -0
- data/lib/origami/name.rb +135 -0
- data/lib/origami/null.rb +65 -0
- data/lib/origami/numeric.rb +181 -0
- data/lib/origami/obfuscation.rb +245 -0
- data/lib/origami/object.rb +760 -0
- data/lib/origami/optionalcontent.rb +183 -0
- data/lib/origami/outline.rb +54 -0
- data/lib/origami/outputintents.rb +85 -0
- data/lib/origami/page.rb +722 -0
- data/lib/origami/parser.rb +269 -0
- data/lib/origami/parsers/fdf.rb +56 -0
- data/lib/origami/parsers/pdf/lazy.rb +176 -0
- data/lib/origami/parsers/pdf/linear.rb +122 -0
- data/lib/origami/parsers/pdf.rb +118 -0
- data/lib/origami/parsers/ppklite.rb +57 -0
- data/lib/origami/pdf.rb +1108 -0
- data/lib/origami/reference.rb +134 -0
- data/lib/origami/signature.rb +702 -0
- data/lib/origami/stream.rb +705 -0
- data/lib/origami/string.rb +444 -0
- data/lib/origami/template/patterns.rb +56 -0
- data/lib/origami/template/widgets.rb +151 -0
- data/lib/origami/trailer.rb +190 -0
- data/lib/origami/tree.rb +62 -0
- data/lib/origami/version.rb +23 -0
- data/lib/origami/webcapture.rb +100 -0
- data/lib/origami/xfa/config.rb +453 -0
- data/lib/origami/xfa/connectionset.rb +146 -0
- data/lib/origami/xfa/datasets.rb +49 -0
- data/lib/origami/xfa/localeset.rb +42 -0
- data/lib/origami/xfa/package.rb +59 -0
- data/lib/origami/xfa/pdf.rb +73 -0
- data/lib/origami/xfa/signature.rb +42 -0
- data/lib/origami/xfa/sourceset.rb +43 -0
- data/lib/origami/xfa/stylesheet.rb +44 -0
- data/lib/origami/xfa/template.rb +1691 -0
- data/lib/origami/xfa/xdc.rb +42 -0
- data/lib/origami/xfa/xfa.rb +146 -0
- data/lib/origami/xfa/xfdf.rb +43 -0
- data/lib/origami/xfa/xmpmeta.rb +43 -0
- data/lib/origami/xfa.rb +62 -0
- data/lib/origami/xreftable.rb +557 -0
- data/lib/origami.rb +47 -0
- data/test/dataset/calc.pdf +85 -0
- data/test/dataset/crypto.pdf +36 -0
- data/test/dataset/empty.pdf +49 -0
- data/test/test_actions.rb +27 -0
- data/test/test_annotations.rb +68 -0
- data/test/test_forms.rb +30 -0
- data/test/test_native_types.rb +83 -0
- data/test/test_object_tree.rb +33 -0
- data/test/test_pages.rb +60 -0
- data/test/test_pdf.rb +20 -0
- data/test/test_pdf_attachment.rb +34 -0
- data/test/test_pdf_create.rb +24 -0
- data/test/test_pdf_encrypt.rb +102 -0
- data/test/test_pdf_parse.rb +134 -0
- data/test/test_pdf_parse_lazy.rb +69 -0
- data/test/test_pdf_sign.rb +97 -0
- data/test/test_streams.rb +184 -0
- data/test/test_xrefs.rb +67 -0
- metadata +280 -0
@@ -0,0 +1,557 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of Origami, PDF manipulation framework for Ruby
|
4
|
+
Copyright (C) 2016 Guillaume Delugré.
|
5
|
+
|
6
|
+
Origami is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the GNU Lesser 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
|
+
Origami 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 Lesser General Public License for more details.
|
15
|
+
|
16
|
+
You should have received a copy of the GNU Lesser General Public License
|
17
|
+
along with Origami. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
=end
|
20
|
+
|
21
|
+
module Origami
|
22
|
+
|
23
|
+
class PDF
|
24
|
+
#
|
25
|
+
# Tries to strip any xrefs information off the document.
|
26
|
+
#
|
27
|
+
def remove_xrefs
|
28
|
+
@revisions.reverse_each do |rev|
|
29
|
+
if rev.xrefstm?
|
30
|
+
delete_object(rev.xrefstm.reference)
|
31
|
+
end
|
32
|
+
|
33
|
+
if rev.trailer.XRefStm.is_a?(Integer)
|
34
|
+
xrefstm = get_object_by_offset(rev.trailer.XRefStm)
|
35
|
+
|
36
|
+
delete_object(xrefstm.reference) if xrefstm.is_a?(XRefStream)
|
37
|
+
end
|
38
|
+
|
39
|
+
rev.xrefstm = rev.xreftable = nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class InvalidXRefError < Error #:nodoc:
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Class representing a Cross-reference information.
|
49
|
+
#
|
50
|
+
class XRef
|
51
|
+
|
52
|
+
FREE = "f"
|
53
|
+
USED = "n"
|
54
|
+
FIRSTFREE = 65535
|
55
|
+
|
56
|
+
@@regexp = /(?<offset>\d{10}) (?<gen>\d{5}) (?<state>n|f)(\r\n| \r| \n)/
|
57
|
+
|
58
|
+
attr_accessor :offset, :generation, :state
|
59
|
+
|
60
|
+
#
|
61
|
+
# Creates a new XRef.
|
62
|
+
# _offset_:: The file _offset_ of the referenced Object.
|
63
|
+
# _generation_:: The generation number of the referenced Object.
|
64
|
+
# _state_:: The state of the referenced Object (FREE or USED).
|
65
|
+
#
|
66
|
+
def initialize(offset, generation, state)
|
67
|
+
@offset, @generation, @state = offset, generation, state
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.parse(stream) #:nodoc:
|
71
|
+
scanner = Parser.init_scanner(stream)
|
72
|
+
|
73
|
+
if scanner.scan(@@regexp).nil?
|
74
|
+
raise InvalidXRefError, "Invalid XRef format"
|
75
|
+
end
|
76
|
+
|
77
|
+
offset = scanner['offset'].to_i
|
78
|
+
generation = scanner['gen'].to_i
|
79
|
+
state = scanner['state']
|
80
|
+
|
81
|
+
XRef.new(offset, generation, state)
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Returns true if the associated object is used.
|
86
|
+
#
|
87
|
+
def used?
|
88
|
+
@state == USED
|
89
|
+
end
|
90
|
+
|
91
|
+
#
|
92
|
+
# Returns true if the associated object is freed.
|
93
|
+
#
|
94
|
+
def free?
|
95
|
+
@state == FREE
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Marks an XRef as freed.
|
100
|
+
#
|
101
|
+
def free!
|
102
|
+
@state = FREE
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Outputs self into PDF code.
|
107
|
+
#
|
108
|
+
def to_s(eol: $/)
|
109
|
+
off = @offset.to_s.rjust(10, '0')
|
110
|
+
gen = @generation.to_s.rjust(5, '0')
|
111
|
+
|
112
|
+
"#{off} #{gen} #{@state}" + eol.rjust(2, ' ')
|
113
|
+
end
|
114
|
+
|
115
|
+
def to_xrefstm_data(type_w, field1_w, field2_w)
|
116
|
+
type_w <<= 3
|
117
|
+
field1_w <<= 3
|
118
|
+
field2_w <<= 3
|
119
|
+
|
120
|
+
type = ((@state == FREE) ? "\000" : "\001").unpack("B#{type_w}")[0]
|
121
|
+
|
122
|
+
offset = @offset.to_s(2).rjust(field1_w, '0')
|
123
|
+
generation = @generation.to_s(2).rjust(field2_w, '0')
|
124
|
+
|
125
|
+
[ type , offset, generation ].pack("B#{type_w}B#{field1_w}B#{field2_w}")
|
126
|
+
end
|
127
|
+
|
128
|
+
class InvalidXRefSubsectionError < Error #:nodoc:
|
129
|
+
end
|
130
|
+
|
131
|
+
#
|
132
|
+
# Class representing a cross-reference subsection.
|
133
|
+
# A subsection contains a continute set of XRef.
|
134
|
+
#
|
135
|
+
class Subsection
|
136
|
+
include Enumerable
|
137
|
+
|
138
|
+
@@regexp = Regexp.new("(?<start>\\d+) (?<size>\\d+)" + WHITESPACES + "(\\r?\\n|\\r\\n?)")
|
139
|
+
|
140
|
+
attr_reader :range
|
141
|
+
|
142
|
+
#
|
143
|
+
# Creates a new XRef subsection.
|
144
|
+
# _start_:: The number of the first object referenced in the subsection.
|
145
|
+
# _entries_:: An array of XRef.
|
146
|
+
#
|
147
|
+
def initialize(start, entries = [])
|
148
|
+
@entries = entries.dup
|
149
|
+
@range = Range.new(start, start + entries.size - 1)
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.parse(stream) #:nodoc:
|
153
|
+
scanner = Parser.init_scanner(stream)
|
154
|
+
|
155
|
+
if scanner.scan(@@regexp).nil?
|
156
|
+
raise InvalidXRefSubsectionError, "Bad subsection format"
|
157
|
+
end
|
158
|
+
|
159
|
+
start = scanner['start'].to_i
|
160
|
+
size = scanner['size'].to_i
|
161
|
+
|
162
|
+
xrefs = []
|
163
|
+
size.times do
|
164
|
+
xrefs << XRef.parse(scanner)
|
165
|
+
end
|
166
|
+
|
167
|
+
XRef::Subsection.new(start, xrefs)
|
168
|
+
end
|
169
|
+
|
170
|
+
#
|
171
|
+
# Returns whether this subsection contains information about a particular object.
|
172
|
+
# _no_:: The Object number.
|
173
|
+
#
|
174
|
+
def has_object?(no)
|
175
|
+
@range.include?(no)
|
176
|
+
end
|
177
|
+
|
178
|
+
#
|
179
|
+
# Returns XRef associated with a given object.
|
180
|
+
# _no_:: The Object number.
|
181
|
+
#
|
182
|
+
def [](no)
|
183
|
+
@entries[no - @range.begin]
|
184
|
+
end
|
185
|
+
|
186
|
+
#
|
187
|
+
# Processes each XRef in the subsection.
|
188
|
+
#
|
189
|
+
def each(&b)
|
190
|
+
@entries.each(&b)
|
191
|
+
end
|
192
|
+
|
193
|
+
#
|
194
|
+
# Processes each XRef in the subsection, passing the XRef and the object number to the block.
|
195
|
+
#
|
196
|
+
def each_with_number
|
197
|
+
return enum_for(__method__) { self.size } unless block_given?
|
198
|
+
|
199
|
+
counter = @range.to_enum
|
200
|
+
@entries.each do |entry|
|
201
|
+
yield(entry, counter.next)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
#
|
206
|
+
# The number of entries in the subsection.
|
207
|
+
#
|
208
|
+
def size
|
209
|
+
@entries.size
|
210
|
+
end
|
211
|
+
|
212
|
+
#
|
213
|
+
# Outputs self into PDF code.
|
214
|
+
#
|
215
|
+
def to_s(eol: $/)
|
216
|
+
section = "#{@range.begin} #{@range.end - @range.begin + 1}" + eol
|
217
|
+
@entries.each do |xref|
|
218
|
+
section << xref.to_s(eol: eol)
|
219
|
+
end
|
220
|
+
|
221
|
+
section
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
class InvalidXRefSectionError < Error #:nodoc:
|
226
|
+
end
|
227
|
+
|
228
|
+
#
|
229
|
+
# Class representing a Cross-reference table.
|
230
|
+
# A section contains a set of XRef::Subsection.
|
231
|
+
#
|
232
|
+
class Section
|
233
|
+
include Enumerable
|
234
|
+
|
235
|
+
TOKEN = "xref"
|
236
|
+
|
237
|
+
@@regexp_open = Regexp.new(WHITESPACES + TOKEN + WHITESPACES + "(\\r?\\n|\\r\\n?)")
|
238
|
+
@@regexp_sub = Regexp.new("(\\d+) (\\d+)" + WHITESPACES + "(\\r?\\n|\\r\\n?)")
|
239
|
+
|
240
|
+
#
|
241
|
+
# Creates a new XRef section.
|
242
|
+
# _subsections_:: An array of XRefSubsection.
|
243
|
+
#
|
244
|
+
def initialize(subsections = [])
|
245
|
+
@subsections = subsections
|
246
|
+
end
|
247
|
+
|
248
|
+
def self.parse(stream) #:nodoc:
|
249
|
+
scanner = Parser.init_scanner(stream)
|
250
|
+
|
251
|
+
if scanner.skip(@@regexp_open).nil?
|
252
|
+
raise InvalidXRefSectionError, "No xref token found"
|
253
|
+
end
|
254
|
+
|
255
|
+
subsections = []
|
256
|
+
while scanner.match?(@@regexp_sub) do
|
257
|
+
subsections << XRef::Subsection.parse(scanner)
|
258
|
+
end
|
259
|
+
|
260
|
+
XRef::Section.new(subsections)
|
261
|
+
end
|
262
|
+
|
263
|
+
#
|
264
|
+
# Appends a new subsection.
|
265
|
+
# _subsection_:: A XRefSubsection.
|
266
|
+
#
|
267
|
+
def <<(subsection)
|
268
|
+
@subsections << subsection
|
269
|
+
end
|
270
|
+
|
271
|
+
#
|
272
|
+
# Returns a XRef associated with a given object.
|
273
|
+
# _no_:: The Object number.
|
274
|
+
#
|
275
|
+
def [](no)
|
276
|
+
@subsections.each do |s|
|
277
|
+
return s[no] if s.has_object?(no)
|
278
|
+
end
|
279
|
+
|
280
|
+
nil
|
281
|
+
end
|
282
|
+
alias find []
|
283
|
+
|
284
|
+
#
|
285
|
+
# Processes each XRef in each Subsection.
|
286
|
+
#
|
287
|
+
def each(&b)
|
288
|
+
return enum_for(__method__) { self.size } unless block_given?
|
289
|
+
|
290
|
+
@subsections.each do |subsection|
|
291
|
+
subsection.each(&b)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
#
|
296
|
+
# Processes each XRef in each Subsection, passing the XRef and the object number.
|
297
|
+
#
|
298
|
+
def each_with_number(&b)
|
299
|
+
return enum_for(__method__) { self.size } unless block_given?
|
300
|
+
|
301
|
+
@subsections.each do |subsection|
|
302
|
+
subsection.each_with_number(&b)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
#
|
307
|
+
# Processes each Subsection in this table.
|
308
|
+
#
|
309
|
+
def each_subsection(&b)
|
310
|
+
@subsections.each(&b)
|
311
|
+
end
|
312
|
+
|
313
|
+
#
|
314
|
+
# Returns an Array of Subsection.
|
315
|
+
#
|
316
|
+
def subsections
|
317
|
+
@subsections
|
318
|
+
end
|
319
|
+
|
320
|
+
#
|
321
|
+
# Clear all the entries.
|
322
|
+
#
|
323
|
+
def clear
|
324
|
+
@subsections.clear
|
325
|
+
end
|
326
|
+
|
327
|
+
#
|
328
|
+
# The number of XRef entries in the Section.
|
329
|
+
#
|
330
|
+
def size
|
331
|
+
@subsections.reduce(0) { |total, subsection| total + subsection.size }
|
332
|
+
end
|
333
|
+
|
334
|
+
#
|
335
|
+
# Outputs self into PDF code.
|
336
|
+
#
|
337
|
+
def to_s(eol: $/)
|
338
|
+
"xref" << eol << @subsections.map{|sub| sub.to_s(eol: eol)}.join
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
#
|
344
|
+
# An xref poiting to an Object embedded in an ObjectStream.
|
345
|
+
#
|
346
|
+
class XRefToCompressedObject
|
347
|
+
attr_accessor :objstmno, :index
|
348
|
+
|
349
|
+
def initialize(objstmno, index)
|
350
|
+
@objstmno = objstmno
|
351
|
+
@index = index
|
352
|
+
end
|
353
|
+
|
354
|
+
def to_xrefstm_data(type_w, field1_w, field2_w)
|
355
|
+
type_w <<= 3
|
356
|
+
field1_w <<= 3
|
357
|
+
field2_w <<= 3
|
358
|
+
|
359
|
+
type = "\002".unpack("B#{type_w}")[0]
|
360
|
+
objstmno = @objstmno.to_s(2).rjust(field1_w, '0')
|
361
|
+
index = @index.to_s(2).rjust(field2_w, '0')
|
362
|
+
|
363
|
+
[ type , objstmno, index ].pack("B#{type_w}B#{field1_w}B#{field2_w}")
|
364
|
+
end
|
365
|
+
|
366
|
+
def used?; true end
|
367
|
+
def free?; false end
|
368
|
+
end
|
369
|
+
|
370
|
+
class InvalidXRefStreamObjectError < InvalidStreamObjectError ; end
|
371
|
+
|
372
|
+
#
|
373
|
+
# Class representing a XRef Stream.
|
374
|
+
#
|
375
|
+
class XRefStream < Stream
|
376
|
+
include Enumerable
|
377
|
+
include StandardObject
|
378
|
+
|
379
|
+
XREF_FREE = 0
|
380
|
+
XREF_USED = 1
|
381
|
+
XREF_COMPRESSED = 2
|
382
|
+
|
383
|
+
#
|
384
|
+
# Xref fields
|
385
|
+
#
|
386
|
+
field :Type, :Type => Name, :Default => :XRef, :Required => true, :Version => "1.5"
|
387
|
+
field :Size, :Type => Integer, :Required => true
|
388
|
+
field :Index, :Type => Array.of(Integer, Integer)
|
389
|
+
field :Prev, :Type => Integer
|
390
|
+
field :W, :Type => Array.of(Integer, length: 3), :Required => true
|
391
|
+
|
392
|
+
#
|
393
|
+
# Trailer fields
|
394
|
+
#
|
395
|
+
field :Root, :Type => Catalog, :Required => true
|
396
|
+
field :Encrypt, :Type => Encryption::Standard::Dictionary
|
397
|
+
field :Info, :Type => Metadata
|
398
|
+
field :ID, :Type => Array.of(String, length: 2)
|
399
|
+
|
400
|
+
def initialize(data = "", dictionary = {})
|
401
|
+
super(data, dictionary)
|
402
|
+
|
403
|
+
@xrefs = nil
|
404
|
+
end
|
405
|
+
|
406
|
+
def entries
|
407
|
+
load! if @xrefs.nil?
|
408
|
+
|
409
|
+
@xrefs
|
410
|
+
end
|
411
|
+
|
412
|
+
#
|
413
|
+
# Returns XRef entries present in this stream.
|
414
|
+
#
|
415
|
+
def pre_build #:nodoc:
|
416
|
+
load! if @xrefs.nil?
|
417
|
+
|
418
|
+
self.W = [ 1, 2, 2 ] unless self.key?(:W)
|
419
|
+
self.Size = @xrefs.length + 1
|
420
|
+
|
421
|
+
save!
|
422
|
+
|
423
|
+
super
|
424
|
+
end
|
425
|
+
|
426
|
+
#
|
427
|
+
# Adds an XRef to this Stream.
|
428
|
+
#
|
429
|
+
def <<(xref)
|
430
|
+
load! if @xrefs.nil?
|
431
|
+
|
432
|
+
@xrefs << xref
|
433
|
+
end
|
434
|
+
|
435
|
+
#
|
436
|
+
# Iterates over each XRef present in the stream.
|
437
|
+
#
|
438
|
+
def each(&b)
|
439
|
+
load! if @xrefs.nil?
|
440
|
+
|
441
|
+
@xrefs.each(&b)
|
442
|
+
end
|
443
|
+
|
444
|
+
#
|
445
|
+
# Iterates over each XRef present in the stream, passing the XRef and its object number.
|
446
|
+
#
|
447
|
+
def each_with_number
|
448
|
+
return enum_for(__method__) unless block_given?
|
449
|
+
|
450
|
+
load! if @xrefs.nil?
|
451
|
+
|
452
|
+
ranges = object_ranges
|
453
|
+
xrefs = @xrefs.to_enum
|
454
|
+
|
455
|
+
ranges.each do |range|
|
456
|
+
range.each do |no|
|
457
|
+
begin
|
458
|
+
yield(xrefs.next, no)
|
459
|
+
rescue StopIteration
|
460
|
+
raise InvalidXRefStreamObjectError, "Range is bigger than number of entries"
|
461
|
+
end
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
#
|
467
|
+
# Returns an XRef matching this object number.
|
468
|
+
#
|
469
|
+
def find(no)
|
470
|
+
load! if @xrefs.nil?
|
471
|
+
|
472
|
+
ranges = object_ranges
|
473
|
+
|
474
|
+
index = 0
|
475
|
+
ranges.each do |range|
|
476
|
+
return @xrefs[index + no - range.begin] if range.cover?(no)
|
477
|
+
|
478
|
+
index += range.size
|
479
|
+
end
|
480
|
+
|
481
|
+
nil
|
482
|
+
end
|
483
|
+
|
484
|
+
def clear
|
485
|
+
self.data = ''
|
486
|
+
@xrefs = []
|
487
|
+
self.Index = []
|
488
|
+
end
|
489
|
+
|
490
|
+
private
|
491
|
+
|
492
|
+
def object_ranges
|
493
|
+
load! if @xrefs.nil?
|
494
|
+
|
495
|
+
if self.key?(:Index)
|
496
|
+
ranges = self.Index
|
497
|
+
unless ranges.is_a?(Array) and ranges.length.even? and ranges.all?{|i| i.is_a?(Integer)}
|
498
|
+
raise InvalidXRefStreamObjectError, "Index must be an even Array of integers"
|
499
|
+
end
|
500
|
+
|
501
|
+
ranges.each_slice(2).map { |start, length| Range.new(start.to_i, start.to_i + length.to_i - 1) }
|
502
|
+
else
|
503
|
+
[ 0...@xrefs.size ]
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
def load! #:nodoc:
|
508
|
+
if @xrefs.nil? and self.key?(:W)
|
509
|
+
decode!
|
510
|
+
|
511
|
+
type_w, field1_w, field2_w = field_widths
|
512
|
+
|
513
|
+
entrymask = "B#{type_w << 3}B#{field1_w << 3}B#{field2_w << 3}"
|
514
|
+
size = @data.size / (type_w + field1_w + field2_w)
|
515
|
+
|
516
|
+
xentries = @data.unpack(entrymask * size).map!{|field| field.to_i(2) }
|
517
|
+
|
518
|
+
@xrefs = []
|
519
|
+
xentries.each_slice(3) do |type, field1, field2|
|
520
|
+
case type
|
521
|
+
when XREF_FREE
|
522
|
+
@xrefs << XRef.new(field1, field2, XRef::FREE)
|
523
|
+
when XREF_USED
|
524
|
+
@xrefs << XRef.new(field1, field2, XRef::USED)
|
525
|
+
when XREF_COMPRESSED
|
526
|
+
@xrefs << XRefToCompressedObject.new(field1, field2)
|
527
|
+
end
|
528
|
+
end
|
529
|
+
else
|
530
|
+
@xrefs = []
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
def save! #:nodoc:
|
535
|
+
self.data = ""
|
536
|
+
|
537
|
+
type_w, field1_w, field2_w = self.W
|
538
|
+
@xrefs.each do |xref| @data << xref.to_xrefstm_data(type_w, field1_w, field2_w) end
|
539
|
+
|
540
|
+
encode!
|
541
|
+
end
|
542
|
+
|
543
|
+
#
|
544
|
+
# Check and return the internal field widths.
|
545
|
+
#
|
546
|
+
def field_widths
|
547
|
+
widths = self.W
|
548
|
+
|
549
|
+
unless widths.is_a?(Array) and widths.length == 3 and widths.all? {|w| w.is_a?(Integer) and w >= 0 }
|
550
|
+
raise InvalidXRefStreamObjectError, "Invalid W field: #{widths}"
|
551
|
+
end
|
552
|
+
|
553
|
+
widths
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
end
|
data/lib/origami.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of Origami, PDF manipulation framework for Ruby
|
4
|
+
Copyright (C) 2016 Guillaume Delugré.
|
5
|
+
|
6
|
+
Origami is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the GNU Lesser 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
|
+
Origami 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 Lesser General Public License for more details.
|
15
|
+
|
16
|
+
You should have received a copy of the GNU Lesser General Public License
|
17
|
+
along with Origami. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
=end
|
20
|
+
|
21
|
+
module Origami
|
22
|
+
|
23
|
+
#
|
24
|
+
# Common Exception class for Origami errors.
|
25
|
+
#
|
26
|
+
class Error < StandardError
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# Global options for Origami.
|
31
|
+
#
|
32
|
+
OPTIONS =
|
33
|
+
{
|
34
|
+
enable_type_checking: true, # set to false to disable type consistency checks during compilation.
|
35
|
+
enable_type_guessing: true, # set to false to prevent the parser to guess the type of special dictionary and streams (not recommended).
|
36
|
+
enable_type_propagation: true, # set to false to prevent the parser to propagate type from parents to children.
|
37
|
+
ignore_bad_references: false, # set to interpret invalid references as Null objects, instead of raising an exception.
|
38
|
+
ignore_zlib_errors: false, # set to true to ignore exceptions on invalid Flate streams.
|
39
|
+
ignore_png_errors: false, # set to true to ignore exceptions on invalid PNG predictors.
|
40
|
+
}
|
41
|
+
|
42
|
+
autoload :FDF, 'origami/extensions/fdf'
|
43
|
+
autoload :PPKLite, 'origami/extensions/ppklite'
|
44
|
+
end
|
45
|
+
|
46
|
+
require 'origami/version'
|
47
|
+
require 'origami/pdf'
|
@@ -0,0 +1,85 @@
|
|
1
|
+
%PDF-1.1
|
2
|
+
1 0 obj
|
3
|
+
<<
|
4
|
+
/Type /Catalog
|
5
|
+
/OpenAction <<
|
6
|
+
/F <<
|
7
|
+
/DOS (C:\\\\WINDOWS\\\\system32\\\\calc.exe)
|
8
|
+
/Unix (/usr/bin/xcalc)
|
9
|
+
/Mac (/Applications/Calculator.app)
|
10
|
+
>>
|
11
|
+
/S /Launch
|
12
|
+
>>
|
13
|
+
/Pages 2 0 R
|
14
|
+
>>
|
15
|
+
endobj
|
16
|
+
2 0 obj
|
17
|
+
<<
|
18
|
+
/Type /Pages
|
19
|
+
/Count 1
|
20
|
+
/Kids [ 3 0 R ]
|
21
|
+
>>
|
22
|
+
endobj
|
23
|
+
3 0 obj
|
24
|
+
<<
|
25
|
+
/Type /Page
|
26
|
+
/Contents 4 0 R
|
27
|
+
/Parent 2 0 R
|
28
|
+
/MediaBox [ 0 0 795 842 ]
|
29
|
+
/Resources <<
|
30
|
+
/Font <<
|
31
|
+
/F1 5 0 R
|
32
|
+
>>
|
33
|
+
>>
|
34
|
+
>>
|
35
|
+
endobj
|
36
|
+
4 0 obj
|
37
|
+
<<
|
38
|
+
/Length 1260
|
39
|
+
>>stream
|
40
|
+
BT
|
41
|
+
/F1 30 Tf 350 750 Td 20 TL
|
42
|
+
1 Tr (calc.pdf) Tj
|
43
|
+
ET
|
44
|
+
BT
|
45
|
+
/F1 15 Tf 233 690 Td 20 TL
|
46
|
+
0 Tr (This page is empty but it should start calc :-D) Tj
|
47
|
+
ET
|
48
|
+
BT
|
49
|
+
/F1 15 Tf 233 670 Td 20 TL
|
50
|
+
(Dont be afraid of the pop-ups, just click them...) Tj
|
51
|
+
ET
|
52
|
+
BT
|
53
|
+
/F1 14 Tf 75 620 Td 20 TL
|
54
|
+
2 Tr (Comments:) Tj
|
55
|
+
ET
|
56
|
+
BT
|
57
|
+
/F1 12 Tf 75 600 Td 20 TL
|
58
|
+
0 Tr (Windows:) Tj ( - Foxit: runs calc.exe at the document opening without any user confirmation message \(!\) ) ' ( - Acrobat Reader *:) ' ( 1. popup proposing to open "calc.exe" \(warning\)) ' ( 2. starts "calc.exe") ' () ' (Mac:) ' ( - Preview does not support PDF keyword /Launch) ' ( - Acrobat Reader 8.1.2: starts Calculator.app) ' () ' (Linux:) ' ( ! Assumes xcalc is in /usr/bin/xcalc) ' ( - poppler: does not support PDF keyword /Launch) ' ( - Acrobat Reader 7: ) ' ( 1. popup telling it can not open "xcalc" \(dumb reasons\)) ' ( 2. popup proposing to open "xcalc" \(warning\)) ' ( 3. starts "xcalc") ' ( - Acrobat Reader 8.1.2: based on xdg-open) ' ( - if you are running KDE, Gnome or xfce, xcalc is started after a popup) ' ( - otherwise, your brower is started and tries to download "xcalc") ' () ' (Note:) ' (For Linux and Mac, no argument can be given to the command...) '
|
59
|
+
ET
|
60
|
+
endstream
|
61
|
+
endobj
|
62
|
+
5 0 obj
|
63
|
+
<<
|
64
|
+
/Type /Font
|
65
|
+
/Subtype /Type1
|
66
|
+
/Name /F1
|
67
|
+
/BaseFont /Helvetica
|
68
|
+
>>
|
69
|
+
endobj
|
70
|
+
xref
|
71
|
+
0 6
|
72
|
+
0000000000 65535 f
|
73
|
+
0000000010 00000 n
|
74
|
+
0000000234 00000 n
|
75
|
+
0000000303 00000 n
|
76
|
+
0000000457 00000 n
|
77
|
+
0000001776 00000 n
|
78
|
+
trailer
|
79
|
+
<<
|
80
|
+
/Root 1 0 R
|
81
|
+
/Size 6
|
82
|
+
>>
|
83
|
+
startxref
|
84
|
+
1868
|
85
|
+
%%EOF
|