origamindee 3.0.0 → 4.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 +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +4 -2
- data/bin/shell/console.rb +128 -128
- data/lib/origami/graphics/instruction.rb +1 -1
- data/lib/origami/page.rb +130 -34
- data/lib/origami/signature.rb +11 -10
- data/lib/origami/version.rb +3 -1
- data/lib/origami.rb +1 -0
- data/test/dataset/3_pages.pdf +0 -0
- data/test/dataset/image_only.pdf +0 -0
- data/test/dataset/invoice.pdf +0 -0
- data/test/test_actions.rb +1 -1
- data/test/test_pages.rb +25 -2
- data/test/test_pdf_parse.rb +3 -10
- data/test/test_pdf_sign.rb +44 -19
- data/test/test_xrefs.rb +1 -1
- metadata +28 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 45413662e5fb14c50c4dd206522be8e187950c6119b3f8a3a1c2632f363d797d
|
4
|
+
data.tar.gz: 6a016143c0898b649e1b386af4daf117e3c023f7acb414f7a48dbc578ec0afdc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3612b2edae8776c43acb2715a38e16e47a0416d83b11568136b053ad4f9137f1d827b371d26a25861dfdd12fcd92af871ae02c8ba714ce915fcd0191f6134bf2
|
7
|
+
data.tar.gz: 8bacd88b9cb1020e1986aaafe75520f1ec993d299b3dbfb98e70f0041609e57c954a8ca8a14752e3cd6b2091507282570c15f263c9edfe590509846288774bff
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
4.0.0
|
2
|
+
-----
|
3
|
+
* Add running tests on Ruby 3.4
|
4
|
+
* Drop support for Ruby < 3.0.0
|
5
|
+
* Accept Elliptic Curve (EC) private key
|
6
|
+
|
7
|
+
3.1.0
|
8
|
+
-----
|
9
|
+
* Add page deletion methods
|
10
|
+
* Add running tests on Ruby 3.2
|
11
|
+
|
1
12
|
3.0.0
|
2
13
|
-----
|
3
14
|
* Replaced Colorize with Rainbow for LGPL compatibility
|
data/README.md
CHANGED
@@ -27,7 +27,9 @@ Origami is able to parse PDF, FDF and PPKLite (Adobe certificate store) files.
|
|
27
27
|
|
28
28
|
Requirements
|
29
29
|
------------
|
30
|
-
The following Ruby versions are tested and supported:
|
30
|
+
The following Ruby versions are tested and supported: 3.0, 3.1, 3.2, 3.3, 3.4
|
31
|
+
|
32
|
+
(It could maybe, possibly, in some cases run on Ruby 2.7, but no guarantees. Update your stack.)
|
31
33
|
|
32
34
|
Some optional features require additional gems:
|
33
35
|
|
@@ -114,7 +116,7 @@ It was therefore replaced by `Rainbow` which has similar functionality, and is l
|
|
114
116
|
|
115
117
|
Furthermore, we are now in a better position to fix any problems related to PDF parsing that are encountered by our users.
|
116
118
|
|
117
|
-
As such it is our intention to support functionalities within the scope of our client library.
|
119
|
+
As such it is our intention to support functionalities within the scope of our Ruby client library.
|
118
120
|
|
119
121
|
**We do not claim to be an official successor to Origami.**
|
120
122
|
|
data/bin/shell/console.rb
CHANGED
@@ -1,128 +1,128 @@
|
|
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
|
-
require 'tempfile'
|
22
|
-
require 'hexdump'
|
23
|
-
require 'colorize'
|
24
|
-
|
25
|
-
String.disable_colorization(false)
|
26
|
-
|
27
|
-
module Origami
|
28
|
-
module Object
|
29
|
-
def inspect
|
30
|
-
to_s
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
class Stream
|
35
|
-
def edit(editor = ENV['EDITOR'])
|
36
|
-
Tempfile.open("origami") do |tmpfile|
|
37
|
-
tmpfile.write(self.data)
|
38
|
-
tmpfile.flush
|
39
|
-
|
40
|
-
Process.wait Kernel.spawn "#{editor} #{tmpfile.path}"
|
41
|
-
|
42
|
-
self.data = File.read(tmpfile.path)
|
43
|
-
tmpfile.unlink
|
44
|
-
end
|
45
|
-
|
46
|
-
true
|
47
|
-
end
|
48
|
-
|
49
|
-
def inspect
|
50
|
-
self.data.hexdump
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
class Page < Dictionary
|
55
|
-
def edit
|
56
|
-
each_content_stream do |stream|
|
57
|
-
stream.edit
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
class PDF
|
63
|
-
if defined?(PDF::JavaScript::Engine)
|
64
|
-
class JavaScript::Engine
|
65
|
-
def shell
|
66
|
-
loop do
|
67
|
-
print "js > ".magenta
|
68
|
-
break if (line = gets).nil?
|
69
|
-
|
70
|
-
begin
|
71
|
-
puts exec(line)
|
72
|
-
rescue V8::JSError => e
|
73
|
-
puts "Error: #{e.message}"
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
class Revision
|
81
|
-
def to_s
|
82
|
-
puts "---------- Body ----------".white.bold
|
83
|
-
@body.each_value do |obj|
|
84
|
-
print "#{obj.reference.to_s.rjust(8,' ')}".ljust(10).magenta
|
85
|
-
puts "#{obj.type}".yellow
|
86
|
-
end
|
87
|
-
|
88
|
-
puts "---------- Trailer ---------".white.bold
|
89
|
-
if not @trailer.dictionary
|
90
|
-
puts " [x] No trailer found.".blue
|
91
|
-
else
|
92
|
-
@trailer.dictionary.each_pair do |entry, value|
|
93
|
-
print " [*] ".magenta
|
94
|
-
print "#{entry}: ".yellow
|
95
|
-
puts "#{value}".red
|
96
|
-
end
|
97
|
-
|
98
|
-
print " [+] ".magenta
|
99
|
-
print "startxref: ".yellow
|
100
|
-
puts "#{@trailer.startxref}".red
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def inspect
|
105
|
-
to_s
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def to_s
|
110
|
-
puts
|
111
|
-
|
112
|
-
puts "---------- Header ----------".white.bold
|
113
|
-
print " [+] ".magenta
|
114
|
-
print "Version: ".yellow
|
115
|
-
puts "#{@header.major_version}.#{@header.minor_version}".red
|
116
|
-
|
117
|
-
@revisions.each do |revision|
|
118
|
-
revision.to_s
|
119
|
-
end
|
120
|
-
puts
|
121
|
-
end
|
122
|
-
|
123
|
-
def inspect
|
124
|
-
to_s
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
end
|
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
|
+
require 'tempfile'
|
22
|
+
require 'hexdump'
|
23
|
+
require 'colorize'
|
24
|
+
|
25
|
+
String.disable_colorization(false)
|
26
|
+
|
27
|
+
module Origami
|
28
|
+
module Object
|
29
|
+
def inspect
|
30
|
+
to_s
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Stream
|
35
|
+
def edit(editor = ENV['EDITOR'])
|
36
|
+
Tempfile.open("origami") do |tmpfile|
|
37
|
+
tmpfile.write(self.data)
|
38
|
+
tmpfile.flush
|
39
|
+
|
40
|
+
Process.wait Kernel.spawn "#{editor} #{tmpfile.path}"
|
41
|
+
|
42
|
+
self.data = File.read(tmpfile.path)
|
43
|
+
tmpfile.unlink
|
44
|
+
end
|
45
|
+
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
def inspect
|
50
|
+
self.data.hexdump
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Page < Dictionary
|
55
|
+
def edit
|
56
|
+
each_content_stream do |stream|
|
57
|
+
stream.edit
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class PDF
|
63
|
+
if defined?(PDF::JavaScript::Engine)
|
64
|
+
class JavaScript::Engine
|
65
|
+
def shell
|
66
|
+
loop do
|
67
|
+
print "js > ".magenta
|
68
|
+
break if (line = gets).nil?
|
69
|
+
|
70
|
+
begin
|
71
|
+
puts exec(line)
|
72
|
+
rescue V8::JSError => e
|
73
|
+
puts "Error: #{e.message}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class Revision
|
81
|
+
def to_s
|
82
|
+
puts "---------- Body ----------".white.bold
|
83
|
+
@body.each_value do |obj|
|
84
|
+
print "#{obj.reference.to_s.rjust(8,' ')}".ljust(10).magenta
|
85
|
+
puts "#{obj.type}".yellow
|
86
|
+
end
|
87
|
+
|
88
|
+
puts "---------- Trailer ---------".white.bold
|
89
|
+
if not @trailer.dictionary
|
90
|
+
puts " [x] No trailer found.".blue
|
91
|
+
else
|
92
|
+
@trailer.dictionary.each_pair do |entry, value|
|
93
|
+
print " [*] ".magenta
|
94
|
+
print "#{entry}: ".yellow
|
95
|
+
puts "#{value}".red
|
96
|
+
end
|
97
|
+
|
98
|
+
print " [+] ".magenta
|
99
|
+
print "startxref: ".yellow
|
100
|
+
puts "#{@trailer.startxref}".red
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def inspect
|
105
|
+
to_s
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_s
|
110
|
+
puts
|
111
|
+
|
112
|
+
puts "---------- Header ----------".white.bold
|
113
|
+
print " [+] ".magenta
|
114
|
+
print "Version: ".yellow
|
115
|
+
puts "#{@header.major_version}.#{@header.minor_version}".red
|
116
|
+
|
117
|
+
@revisions.each do |revision|
|
118
|
+
revision.to_s
|
119
|
+
end
|
120
|
+
puts
|
121
|
+
end
|
122
|
+
|
123
|
+
def inspect
|
124
|
+
to_s
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
data/lib/origami/page.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
=begin
|
2
4
|
|
3
5
|
This file is part of Origami, PDF manipulation framework for Ruby
|
@@ -57,6 +59,28 @@ module Origami
|
|
57
59
|
self
|
58
60
|
end
|
59
61
|
|
62
|
+
#
|
63
|
+
# Deletes a page at position _index_ from the document.
|
64
|
+
# _index_:: Page index (starting from one).
|
65
|
+
#
|
66
|
+
def delete_page_at(index)
|
67
|
+
init_page_tree
|
68
|
+
|
69
|
+
self.Catalog.Pages.delete_page_at(index)
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Deletes a page at position _index_ from the document.
|
75
|
+
# _index_:: Page index (starting from one).
|
76
|
+
#
|
77
|
+
def delete_pages_at(delete_idx)
|
78
|
+
init_page_tree
|
79
|
+
|
80
|
+
self.Catalog.Pages.delete_pages_at(delete_idx)
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
60
84
|
#
|
61
85
|
# Returns an Enumerator of Page
|
62
86
|
#
|
@@ -107,7 +131,7 @@ module Origami
|
|
107
131
|
end
|
108
132
|
|
109
133
|
unless self.Catalog.Pages.is_a?(PageTreeNode)
|
110
|
-
raise InvalidPageTreeError,
|
134
|
+
raise InvalidPageTreeError, 'Root page node is not a PageTreeNode'
|
111
135
|
end
|
112
136
|
end
|
113
137
|
end
|
@@ -250,11 +274,11 @@ module Origami
|
|
250
274
|
field EXTGSTATE, :Type => Dictionary
|
251
275
|
field COLORSPACE, :Type => Dictionary
|
252
276
|
field PATTERN, :Type => Dictionary
|
253
|
-
field SHADING, :Type => Dictionary, :Version =>
|
277
|
+
field SHADING, :Type => Dictionary, :Version => '1.3'
|
254
278
|
field XOBJECT, :Type => Dictionary
|
255
279
|
field FONT, :Type => Dictionary
|
256
280
|
field :ProcSet, :Type => Array.of(Name)
|
257
|
-
field PROPERTIES, :Type => Dictionary, :Version =>
|
281
|
+
field PROPERTIES, :Type => Dictionary, :Version => '1.2'
|
258
282
|
|
259
283
|
def pre_build
|
260
284
|
add_font(Font::Type1::Standard::Helvetica.new.pre_build) unless self.Font
|
@@ -294,11 +318,11 @@ module Origami
|
|
294
318
|
# Inserts a page into the node at a specified position (starting from 1).
|
295
319
|
#
|
296
320
|
def insert_page(n, page)
|
297
|
-
raise IndexError,
|
321
|
+
raise IndexError, 'Page numbers are referenced starting from 1' if n < 1
|
298
322
|
|
299
323
|
kids = self.Kids
|
300
324
|
unless kids.is_a?(Array)
|
301
|
-
raise InvalidPageTreeError,
|
325
|
+
raise InvalidPageTreeError, 'Kids must be an Array'
|
302
326
|
end
|
303
327
|
|
304
328
|
count = 0
|
@@ -323,15 +347,87 @@ module Origami
|
|
323
347
|
return self
|
324
348
|
end
|
325
349
|
else
|
326
|
-
raise InvalidPageTreeError,
|
350
|
+
raise InvalidPageTreeError, 'not a Page or PageTreeNode'
|
327
351
|
end
|
328
352
|
end
|
329
353
|
|
330
|
-
raise IndexError,
|
354
|
+
raise IndexError, 'Out of order page index' unless count + 1 == n
|
331
355
|
|
332
356
|
self.append_page(page)
|
333
357
|
end
|
334
358
|
|
359
|
+
#
|
360
|
+
# Delete a page in the node at the specified position (starting from 1).
|
361
|
+
#
|
362
|
+
def delete_page_at(n)
|
363
|
+
raise IndexError, 'Page numbers are referenced starting from 1' if n < 1
|
364
|
+
|
365
|
+
kids = self.Kids
|
366
|
+
unless kids.is_a?(Array)
|
367
|
+
raise InvalidPageTreeError, 'Kids must be an Array'
|
368
|
+
end
|
369
|
+
|
370
|
+
count = 0
|
371
|
+
kids.each_with_index do |kid, index|
|
372
|
+
node = kid.solve
|
373
|
+
|
374
|
+
case node
|
375
|
+
when Page
|
376
|
+
count = count + 1
|
377
|
+
if count == n
|
378
|
+
kids.delete_at(index)
|
379
|
+
self.Count -= 1
|
380
|
+
return self
|
381
|
+
end
|
382
|
+
|
383
|
+
when PageTreeNode
|
384
|
+
count = count + node.Count
|
385
|
+
if count >= n
|
386
|
+
node.delete_page_at(n - count + node.Count)
|
387
|
+
self.Count -= 1
|
388
|
+
return self
|
389
|
+
end
|
390
|
+
else
|
391
|
+
raise InvalidPageTreeError, 'not a Page or PageTreeNode'
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
raise IndexError, 'Out of order page index' unless count + 1 == n
|
396
|
+
end
|
397
|
+
|
398
|
+
#
|
399
|
+
# Delete a set of page in the node at the specified positions (starting from 0).
|
400
|
+
#
|
401
|
+
def delete_pages_at(delete_idx, offset = 0)
|
402
|
+
kids = self.Kids
|
403
|
+
unless kids.is_a?(Array)
|
404
|
+
raise InvalidPageTreeError, 'Kids must be an Array'
|
405
|
+
end
|
406
|
+
|
407
|
+
deleted = 0
|
408
|
+
kids.delete_if.with_index do |kid, idx|
|
409
|
+
node = kid.solve
|
410
|
+
idx = idx + offset
|
411
|
+
|
412
|
+
case node
|
413
|
+
when Page
|
414
|
+
if delete_idx.include?(idx)
|
415
|
+
deleted += 1
|
416
|
+
true
|
417
|
+
else
|
418
|
+
false
|
419
|
+
end
|
420
|
+
when PageTreeNode
|
421
|
+
deleted += node.delete_pages_at(delete_idx, idx)
|
422
|
+
false
|
423
|
+
else
|
424
|
+
raise InvalidPageTreeError, 'not a Page or PageTreeNode'
|
425
|
+
end
|
426
|
+
end
|
427
|
+
self.Count -= deleted
|
428
|
+
deleted
|
429
|
+
end
|
430
|
+
|
335
431
|
#
|
336
432
|
# Returns an Array of Pages inheriting this tree node.
|
337
433
|
#
|
@@ -346,11 +442,11 @@ module Origami
|
|
346
442
|
return enum_for(__method__) { self.Count.to_i } unless block_given?
|
347
443
|
|
348
444
|
if browsed_nodes.any?{|node| node.equal?(self)}
|
349
|
-
raise InvalidPageTreeError,
|
445
|
+
raise InvalidPageTreeError, 'Cyclic tree graph detected'
|
350
446
|
end
|
351
447
|
|
352
448
|
unless self.Kids.is_a?(Array)
|
353
|
-
raise InvalidPageTreeError,
|
449
|
+
raise InvalidPageTreeError, 'Kids must be an Array'
|
354
450
|
end
|
355
451
|
|
356
452
|
browsed_nodes.push(self)
|
@@ -363,7 +459,7 @@ module Origami
|
|
363
459
|
when PageTreeNode then node.each_page(browsed_nodes: browsed_nodes, &block)
|
364
460
|
when Page then yield(node)
|
365
461
|
else
|
366
|
-
raise InvalidPageTreeError,
|
462
|
+
raise InvalidPageTreeError, 'not a Page or PageTreeNode'
|
367
463
|
end
|
368
464
|
end
|
369
465
|
end
|
@@ -375,10 +471,10 @@ module Origami
|
|
375
471
|
# Get the n-th Page object in this node, starting from 1.
|
376
472
|
#
|
377
473
|
def get_page(n)
|
378
|
-
raise IndexError,
|
379
|
-
raise IndexError,
|
474
|
+
raise IndexError, 'Page numbers are referenced starting from 1' if n < 1
|
475
|
+
raise IndexError, 'Page not found' if n > self.Count.to_i
|
380
476
|
|
381
|
-
self.each_page.lazy.drop(n - 1).first or raise IndexError,
|
477
|
+
self.each_page.lazy.drop(n - 1).first or raise IndexError, 'Page not found'
|
382
478
|
end
|
383
479
|
|
384
480
|
#
|
@@ -481,8 +577,8 @@ module Origami
|
|
481
577
|
class AdditionalActions < Dictionary
|
482
578
|
include StandardObject
|
483
579
|
|
484
|
-
field :O, :Type => Dictionary, :Version =>
|
485
|
-
field :C, :Type => Dictionary, :Version =>
|
580
|
+
field :O, :Type => Dictionary, :Version => '1.2' # Page Open
|
581
|
+
field :C, :Type => Dictionary, :Version => '1.2' # Page Close
|
486
582
|
end
|
487
583
|
|
488
584
|
module Format
|
@@ -513,34 +609,34 @@ module Origami
|
|
513
609
|
|
514
610
|
field :Type, :Type => Name, :Default => :Page, :Required => true
|
515
611
|
field :Parent, :Type => PageTreeNode, :Required => true
|
516
|
-
field :LastModified, :Type => String, :Version =>
|
612
|
+
field :LastModified, :Type => String, :Version => '1.3'
|
517
613
|
field :Resources, :Type => Resources, :Required => true
|
518
614
|
field :MediaBox, :Type => Rectangle, :Default => Format::A4, :Required => true
|
519
615
|
field :CropBox, :Type => Rectangle
|
520
|
-
field :BleedBox, :Type => Rectangle, :Version =>
|
521
|
-
field :TrimBox, :Type => Rectangle, :Version =>
|
522
|
-
field :ArtBox, :Type => Rectangle, :Version =>
|
523
|
-
field :BoxColorInfo, :Type => BoxColorInformation, :Version =>
|
616
|
+
field :BleedBox, :Type => Rectangle, :Version => '1.3'
|
617
|
+
field :TrimBox, :Type => Rectangle, :Version => '1.3'
|
618
|
+
field :ArtBox, :Type => Rectangle, :Version => '1.3'
|
619
|
+
field :BoxColorInfo, :Type => BoxColorInformation, :Version => '1.4'
|
524
620
|
field :Contents, :Type => [ ContentStream, Array.of(ContentStream) ]
|
525
621
|
field :Rotate, :Type => Integer, :Default => 0
|
526
|
-
field :Group, :Type => Dictionary, :Version =>
|
622
|
+
field :Group, :Type => Dictionary, :Version => '1.4'
|
527
623
|
field :Thumb, :Type => Graphics::ImageXObject
|
528
|
-
field :B, :Type => Array, :Version =>
|
529
|
-
field :Dur, :Type => Integer, :Version =>
|
530
|
-
field :Trans, :Type => Dictionary, :Version =>
|
624
|
+
field :B, :Type => Array, :Version => '1.1'
|
625
|
+
field :Dur, :Type => Integer, :Version => '1.1'
|
626
|
+
field :Trans, :Type => Dictionary, :Version => '1.1'
|
531
627
|
field :Annots, :Type => Array.of(Annotation)
|
532
|
-
field :AA, :Type => AdditionalActions, :Version =>
|
533
|
-
field :Metadata, :Type => MetadataStream, :Version =>
|
534
|
-
field :PieceInfo, :Type => Dictionary, :Version =>
|
535
|
-
field :StructParents, :Type => Integer, :Version =>
|
628
|
+
field :AA, :Type => AdditionalActions, :Version => '1.2'
|
629
|
+
field :Metadata, :Type => MetadataStream, :Version => '1.4'
|
630
|
+
field :PieceInfo, :Type => Dictionary, :Version => '1.2'
|
631
|
+
field :StructParents, :Type => Integer, :Version => '1.3'
|
536
632
|
field :ID, :Type => String
|
537
633
|
field :PZ, :Type => Number
|
538
|
-
field :SeparationInfo, :Type => Dictionary, :Version =>
|
539
|
-
field :Tabs, :Type => Name, :Version =>
|
540
|
-
field :TemplateAssociated, :Type => Name, :Version =>
|
541
|
-
field :PresSteps, :Type => NavigationNode, :Version =>
|
542
|
-
field :UserUnit, :Type => Number, :Default => 1.0, :Version =>
|
543
|
-
field :VP, :Type => Dictionary, :Version =>
|
634
|
+
field :SeparationInfo, :Type => Dictionary, :Version => '1.3'
|
635
|
+
field :Tabs, :Type => Name, :Version => '1.5'
|
636
|
+
field :TemplateAssociated, :Type => Name, :Version => '1.5'
|
637
|
+
field :PresSteps, :Type => NavigationNode, :Version => '1.5'
|
638
|
+
field :UserUnit, :Type => Number, :Default => 1.0, :Version => '1.6'
|
639
|
+
field :VP, :Type => Dictionary, :Version => '1.6'
|
544
640
|
|
545
641
|
def initialize(hash = {}, parser = nil)
|
546
642
|
super(hash, parser)
|
data/lib/origami/signature.rb
CHANGED
@@ -70,7 +70,7 @@ module Origami
|
|
70
70
|
#
|
71
71
|
# Sign the document with the given key and x509 certificate.
|
72
72
|
# _certificate_:: The X509 certificate containing the public key.
|
73
|
-
# _key_:: The private key associated with the certificate.
|
73
|
+
# _key_:: The RSA or EC private key associated with the certificate.
|
74
74
|
# _method_:: The PDF signature identifier.
|
75
75
|
# _ca_:: Optional CA certificates used to sign the user certificate.
|
76
76
|
# _annotation_:: Annotation associated with the signature.
|
@@ -89,19 +89,19 @@ module Origami
|
|
89
89
|
reason: nil)
|
90
90
|
|
91
91
|
unless certificate.is_a?(OpenSSL::X509::Certificate)
|
92
|
-
raise TypeError,
|
92
|
+
raise TypeError, 'A OpenSSL::X509::Certificate object must be passed.'
|
93
93
|
end
|
94
94
|
|
95
|
-
unless key.is_a?(OpenSSL::PKey::RSA)
|
96
|
-
raise TypeError,
|
95
|
+
unless (key.is_a?(OpenSSL::PKey::RSA) || key.is_a?(OpenSSL::PKey::EC))
|
96
|
+
raise TypeError, 'An OpenSSL::PKey::RSA or OpenSSL::PKey::EC object must be passed.'
|
97
97
|
end
|
98
98
|
|
99
99
|
unless ca.is_a?(::Array)
|
100
|
-
raise TypeError,
|
100
|
+
raise TypeError, 'Expected an Array of CA certificates.'
|
101
101
|
end
|
102
102
|
|
103
103
|
unless annotation.nil? or annotation.is_a?(Annotation::Widget::Signature)
|
104
|
-
raise TypeError,
|
104
|
+
raise TypeError, 'Expected a Annotation::Widget::Signature object.'
|
105
105
|
end
|
106
106
|
|
107
107
|
#
|
@@ -125,7 +125,8 @@ module Origami
|
|
125
125
|
InteractiveForm::SigFlags::SIGNATURES_EXIST | InteractiveForm::SigFlags::APPEND_ONLY
|
126
126
|
|
127
127
|
digsig.Type = :Sig
|
128
|
-
|
128
|
+
placeholder_size = Signature::required_size(method, certificate, key, ca) + 128
|
129
|
+
digsig.Contents = HexaString.new("\x00" * placeholder_size)
|
129
130
|
digsig.Filter = :"Adobe.PPKLite"
|
130
131
|
digsig.SubFilter = Name.new(method)
|
131
132
|
digsig.ByteRange = [0, 0, 0, 0]
|
@@ -327,7 +328,7 @@ module Origami
|
|
327
328
|
r1.end != start_sig or
|
328
329
|
r2.begin != end_sig
|
329
330
|
|
330
|
-
raise SignatureError,
|
331
|
+
raise SignatureError, 'Invalid signature byte range'
|
331
332
|
end
|
332
333
|
|
333
334
|
self.original_data[r1] + self.original_data[r2]
|
@@ -589,7 +590,7 @@ module Origami
|
|
589
590
|
byte_range = self.ByteRange
|
590
591
|
|
591
592
|
unless byte_range.is_a?(Array) and byte_range.length == 4 and byte_range.all? {|i| i.is_a?(Integer) }
|
592
|
-
raise SignatureError,
|
593
|
+
raise SignatureError, 'Invalid ByteRange field value'
|
593
594
|
end
|
594
595
|
|
595
596
|
byte_range.map(&:to_i).each_slice(2).map do |start, length|
|
@@ -608,7 +609,7 @@ module Origami
|
|
608
609
|
|
609
610
|
chain = self.Cert
|
610
611
|
unless chain.is_a?(String) or (chain.is_a?(Array) and chain.all?{|cert| cert.is_a?(String)})
|
611
|
-
return SignatureError,
|
612
|
+
return SignatureError, 'Invalid embedded certificate chain'
|
612
613
|
end
|
613
614
|
|
614
615
|
[ chain ].flatten.map! {|str| OpenSSL::X509::Certificate.new(str) }
|
data/lib/origami/version.rb
CHANGED
data/lib/origami.rb
CHANGED
Binary file
|
Binary file
|
Binary file
|
data/test/test_actions.rb
CHANGED
data/test/test_pages.rb
CHANGED
@@ -51,10 +51,33 @@ class TestPages < Minitest::Test
|
|
51
51
|
assert_raises(IndexError) { @target.insert_page(1000, Page.new) }
|
52
52
|
end
|
53
53
|
|
54
|
-
def
|
54
|
+
def test_example_write_delete_page
|
55
55
|
@target.append_page
|
56
|
-
@target.pages.
|
56
|
+
@target.pages.last.write 'Hello, page 1 world!', size: 30
|
57
|
+
@target.append_page
|
58
|
+
@target.pages.last.write 'Hello, page 2 world!', size: 30
|
59
|
+
@target.save(@output)
|
60
|
+
assert_equal @target.Catalog.Pages.Count, 2
|
61
|
+
|
62
|
+
@target.delete_page_at(1)
|
57
63
|
@target.save(@output)
|
58
64
|
assert_equal @target.Catalog.Pages.Count, 1
|
59
65
|
end
|
66
|
+
|
67
|
+
def test_delete_pages_generated
|
68
|
+
to_append = [Page.new, Page.new, Page.new, PageTreeNode.new, Page.new, Page.new, Page.new]
|
69
|
+
to_append.each { |page| @target.append_page(page) }
|
70
|
+
to_del = [0,1]
|
71
|
+
assert_equal @target.Catalog.Pages.Count, to_append.length
|
72
|
+
@target.delete_pages_at(to_del)
|
73
|
+
assert_equal @target.Catalog.Pages.Count, to_append.length - to_del.length
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_delete_pages_parsed
|
77
|
+
file = File.join(__dir__, 'dataset', '3_pages.pdf')
|
78
|
+
pdf = PDF.read(file, ignore_errors: false, verbosity: Parser::VERBOSE_QUIET)
|
79
|
+
assert_equal pdf.Catalog.Pages.Count, 3
|
80
|
+
pdf.delete_pages_at([0,2])
|
81
|
+
assert_equal pdf.Catalog.Pages.Count, 1
|
82
|
+
end
|
60
83
|
end
|
data/test/test_pdf_parse.rb
CHANGED
@@ -1,18 +1,11 @@
|
|
1
1
|
require 'minitest/autorun'
|
2
2
|
|
3
3
|
class TestPDFParser < Minitest::Test
|
4
|
-
def setup
|
5
|
-
@files =
|
6
|
-
%w{
|
7
|
-
dataset/empty.pdf
|
8
|
-
dataset/calc.pdf
|
9
|
-
dataset/crypto.pdf
|
10
|
-
}
|
11
|
-
end
|
12
4
|
|
13
5
|
def test_parse_pdf
|
14
|
-
|
15
|
-
|
6
|
+
dir = File.join(__dir__, 'dataset')
|
7
|
+
Dir.children(dir).each do | filename |
|
8
|
+
pdf = PDF.read(File.join(dir, filename), ignore_errors: false, verbosity: Parser::VERBOSE_QUIET)
|
16
9
|
|
17
10
|
assert_instance_of PDF, pdf
|
18
11
|
|
data/test/test_pdf_sign.rb
CHANGED
@@ -4,9 +4,7 @@ require 'openssl'
|
|
4
4
|
|
5
5
|
class TestSign < Minitest::Test
|
6
6
|
|
7
|
-
def create_self_signed_ca_certificate(
|
8
|
-
key = OpenSSL::PKey::RSA.new key_size
|
9
|
-
|
7
|
+
def create_self_signed_ca_certificate(key, expires)
|
10
8
|
name = OpenSSL::X509::Name.parse 'CN=origami/DC=example'
|
11
9
|
|
12
10
|
cert = OpenSSL::X509::Certificate.new
|
@@ -14,8 +12,7 @@ class TestSign < Minitest::Test
|
|
14
12
|
cert.serial = 0
|
15
13
|
cert.not_before = Time.now
|
16
14
|
cert.not_after = Time.now + expires
|
17
|
-
|
18
|
-
cert.public_key = key.public_key
|
15
|
+
cert.public_key = key
|
19
16
|
cert.subject = name
|
20
17
|
|
21
18
|
extension_factory = OpenSSL::X509::ExtensionFactory.new
|
@@ -29,12 +26,28 @@ class TestSign < Minitest::Test
|
|
29
26
|
cert.issuer = name
|
30
27
|
cert.sign key, OpenSSL::Digest::SHA256.new
|
31
28
|
|
32
|
-
|
29
|
+
cert
|
30
|
+
end
|
31
|
+
|
32
|
+
def ec_test_data(curve_name)
|
33
|
+
key = OpenSSL::PKey::EC.generate(curve_name)
|
34
|
+
other_key = OpenSSL::PKey::EC.generate(curve_name)
|
35
|
+
cert = create_self_signed_ca_certificate(key, 3600)
|
36
|
+
other_cert = create_self_signed_ca_certificate(other_key, 3600)
|
37
|
+
[ cert, key, other_cert ]
|
38
|
+
end
|
39
|
+
|
40
|
+
def rsa_test_data(key_size)
|
41
|
+
key = OpenSSL::PKey::RSA.new(key_size)
|
42
|
+
other_key = OpenSSL::PKey::RSA.new(key_size)
|
43
|
+
cert = create_self_signed_ca_certificate(key, 3600)
|
44
|
+
other_cert = create_self_signed_ca_certificate(other_key, 3600)
|
45
|
+
[ cert, key, other_cert ]
|
33
46
|
end
|
34
47
|
|
35
48
|
def setup
|
36
|
-
@
|
37
|
-
@
|
49
|
+
@rsa_1024_data = rsa_test_data(1024)
|
50
|
+
@ec_prime256v1_data = ec_test_data('prime256v1')
|
38
51
|
end
|
39
52
|
|
40
53
|
def setup_document_with_annotation
|
@@ -51,10 +64,10 @@ class TestSign < Minitest::Test
|
|
51
64
|
[ document, annotation ]
|
52
65
|
end
|
53
66
|
|
54
|
-
def sign_document_with_method(method)
|
67
|
+
def sign_document_with_method(method, cert, key, other_cert)
|
55
68
|
document, annotation = setup_document_with_annotation
|
56
69
|
|
57
|
-
document.sign(
|
70
|
+
document.sign(cert, key,
|
58
71
|
method: method,
|
59
72
|
annotation: annotation,
|
60
73
|
issuer: "Guillaume Delugré",
|
@@ -73,25 +86,37 @@ class TestSign < Minitest::Test
|
|
73
86
|
|
74
87
|
refute document.verify
|
75
88
|
assert document.verify(allow_self_signed: true)
|
76
|
-
assert document.verify(trusted_certs: [
|
77
|
-
refute document.verify(trusted_certs: [
|
89
|
+
assert document.verify(trusted_certs: [cert])
|
90
|
+
refute document.verify(trusted_certs: [other_cert])
|
78
91
|
|
79
92
|
result = document.verify do |ctx|
|
80
|
-
ctx.error == OpenSSL::X509::V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT and ctx.current_cert.to_pem ==
|
93
|
+
ctx.error == OpenSSL::X509::V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT and ctx.current_cert.to_pem == cert.to_pem
|
81
94
|
end
|
82
95
|
|
83
96
|
assert result
|
84
97
|
end
|
85
98
|
|
86
|
-
def
|
87
|
-
sign_document_with_method(Signature::PKCS7_SHA1)
|
99
|
+
def test_rsa_sign_pkcs7_sha1
|
100
|
+
sign_document_with_method(Signature::PKCS7_SHA1, *@rsa_1024_data)
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_rsa_sign_pkcs7_detached
|
104
|
+
sign_document_with_method(Signature::PKCS7_DETACHED, *@rsa_1024_data)
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_rsa_sign_x509_sha1
|
108
|
+
sign_document_with_method(Signature::PKCS1_RSA_SHA1, *@rsa_1024_data)
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_ec_sign_pkcs7_sha1
|
112
|
+
sign_document_with_method(Signature::PKCS7_SHA1, *@ec_prime256v1_data)
|
88
113
|
end
|
89
114
|
|
90
|
-
def
|
91
|
-
sign_document_with_method(Signature::PKCS7_DETACHED)
|
115
|
+
def test_ec_sign_pkcs7_detached
|
116
|
+
sign_document_with_method(Signature::PKCS7_DETACHED, *@ec_prime256v1_data)
|
92
117
|
end
|
93
118
|
|
94
|
-
def
|
95
|
-
sign_document_with_method(Signature::PKCS1_RSA_SHA1)
|
119
|
+
def test_ec_sign_x509_sha1
|
120
|
+
sign_document_with_method(Signature::PKCS1_RSA_SHA1, *@ec_prime256v1_data)
|
96
121
|
end
|
97
122
|
end
|
data/test/test_xrefs.rb
CHANGED
metadata
CHANGED
@@ -1,16 +1,30 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: origamindee
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Guillaume Delugré
|
8
8
|
- Mindee, SA
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2025-02-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: base64
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 0.1.0
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 0.1.0
|
14
28
|
- !ruby/object:Gem::Dependency
|
15
29
|
name: rainbow
|
16
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -82,23 +96,23 @@ dependencies:
|
|
82
96
|
- !ruby/object:Gem::Version
|
83
97
|
version: '12.3'
|
84
98
|
- !ruby/object:Gem::Dependency
|
85
|
-
name:
|
99
|
+
name: yard
|
86
100
|
requirement: !ruby/object:Gem::Requirement
|
87
101
|
requirements:
|
88
102
|
- - "~>"
|
89
103
|
- !ruby/object:Gem::Version
|
90
|
-
version: '
|
104
|
+
version: '0.9'
|
91
105
|
type: :development
|
92
106
|
prerelease: false
|
93
107
|
version_requirements: !ruby/object:Gem::Requirement
|
94
108
|
requirements:
|
95
109
|
- - "~>"
|
96
110
|
- !ruby/object:Gem::Version
|
97
|
-
version: '
|
111
|
+
version: '0.9'
|
98
112
|
description: Mindee's fork of Origami, a pure Ruby library to parse, modify and generate
|
99
113
|
PDF documents.
|
100
114
|
email:
|
101
|
-
-
|
115
|
+
- opensource@mindee.co
|
102
116
|
executables:
|
103
117
|
- pdfsh
|
104
118
|
- pdf2pdfa
|
@@ -232,9 +246,12 @@ files:
|
|
232
246
|
- lib/origami/xfa/xfdf.rb
|
233
247
|
- lib/origami/xfa/xmpmeta.rb
|
234
248
|
- lib/origami/xreftable.rb
|
249
|
+
- test/dataset/3_pages.pdf
|
235
250
|
- test/dataset/calc.pdf
|
236
251
|
- test/dataset/crypto.pdf
|
237
252
|
- test/dataset/empty.pdf
|
253
|
+
- test/dataset/image_only.pdf
|
254
|
+
- test/dataset/invoice.pdf
|
238
255
|
- test/test_actions.rb
|
239
256
|
- test/test_annotations.rb
|
240
257
|
- test/test_forms.rb
|
@@ -257,7 +274,7 @@ metadata:
|
|
257
274
|
source_code_uri: https://github.com/mindee/origamindee
|
258
275
|
changelog_uri: https://github.com/mindee/origamindee/blob/main/CHANGELOG.md
|
259
276
|
rubygems_mfa_required: 'true'
|
260
|
-
post_install_message:
|
277
|
+
post_install_message:
|
261
278
|
rdoc_options: []
|
262
279
|
require_paths:
|
263
280
|
- lib
|
@@ -265,15 +282,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
265
282
|
requirements:
|
266
283
|
- - ">="
|
267
284
|
- !ruby/object:Gem::Version
|
268
|
-
version: '2.
|
285
|
+
version: '2.7'
|
269
286
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
270
287
|
requirements:
|
271
288
|
- - ">="
|
272
289
|
- !ruby/object:Gem::Version
|
273
290
|
version: '0'
|
274
291
|
requirements: []
|
275
|
-
rubygems_version: 3.
|
276
|
-
signing_key:
|
292
|
+
rubygems_version: 3.2.33
|
293
|
+
signing_key:
|
277
294
|
specification_version: 4
|
278
295
|
summary: Ruby framework to manipulate PDF documents
|
279
296
|
test_files:
|