hexapdf 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +56 -0
- data/CONTRIBUTERS +1 -1
- data/Rakefile +3 -3
- data/VERSION +1 -1
- data/examples/arc.rb +1 -1
- data/examples/graphics.rb +1 -1
- data/examples/hello_world.rb +1 -1
- data/examples/merging.rb +1 -1
- data/examples/show_char_bboxes.rb +1 -1
- data/examples/standard_pdf_fonts.rb +1 -1
- data/examples/truetype.rb +1 -1
- data/lib/hexapdf/cli.rb +14 -7
- data/lib/hexapdf/cli/extract.rb +1 -1
- data/lib/hexapdf/cli/info.rb +2 -2
- data/lib/hexapdf/cli/inspect.rb +4 -4
- data/lib/hexapdf/cli/modify.rb +151 -51
- data/lib/hexapdf/configuration.rb +1 -1
- data/lib/hexapdf/content/canvas.rb +1 -1
- data/lib/hexapdf/content/processor.rb +1 -1
- data/lib/hexapdf/dictionary.rb +6 -19
- data/lib/hexapdf/dictionary_fields.rb +1 -1
- data/lib/hexapdf/document.rb +23 -16
- data/lib/hexapdf/document/files.rb +130 -0
- data/lib/hexapdf/{font_utils.rb → document/fonts.rb} +40 -38
- data/lib/hexapdf/document/images.rb +117 -0
- data/lib/hexapdf/document/pages.rb +125 -0
- data/lib/hexapdf/encryption/aes.rb +1 -1
- data/lib/hexapdf/encryption/ruby_aes.rb +10 -10
- data/lib/hexapdf/encryption/standard_security_handler.rb +11 -8
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -6
- data/lib/hexapdf/font/cmap/writer.rb +5 -7
- data/lib/hexapdf/font/true_type.rb +4 -1
- data/lib/hexapdf/font/true_type/font.rb +8 -16
- data/lib/hexapdf/font/true_type/table.rb +5 -16
- data/lib/hexapdf/font/true_type/table/cmap.rb +2 -7
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +2 -6
- data/lib/hexapdf/font/true_type/table/directory.rb +0 -5
- data/lib/hexapdf/font/true_type/table/glyf.rb +3 -11
- data/lib/hexapdf/font/true_type/table/head.rb +0 -12
- data/lib/hexapdf/font/true_type/table/hhea.rb +0 -7
- data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -5
- data/lib/hexapdf/font/true_type/table/loca.rb +0 -4
- data/lib/hexapdf/font/true_type/table/maxp.rb +0 -8
- data/lib/hexapdf/font/true_type/table/name.rb +3 -17
- data/lib/hexapdf/font/true_type/table/os2.rb +0 -14
- data/lib/hexapdf/font/true_type/table/post.rb +0 -8
- data/lib/hexapdf/font/true_type_wrapper.rb +1 -1
- data/lib/hexapdf/font/type1.rb +2 -2
- data/lib/hexapdf/font/type1/font.rb +2 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +10 -1
- data/lib/hexapdf/font/type1_wrapper.rb +2 -1
- data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
- data/lib/hexapdf/font_loader/standard14.rb +1 -1
- data/lib/hexapdf/image_loader/jpeg.rb +1 -1
- data/lib/hexapdf/image_loader/pdf.rb +1 -1
- data/lib/hexapdf/image_loader/png.rb +2 -2
- data/lib/hexapdf/object.rb +18 -5
- data/lib/hexapdf/rectangle.rb +8 -1
- data/lib/hexapdf/revisions.rb +4 -2
- data/lib/hexapdf/serializer.rb +3 -3
- data/lib/hexapdf/stream.rb +3 -2
- data/lib/hexapdf/task/dereference.rb +4 -5
- data/lib/hexapdf/task/optimize.rb +6 -3
- data/lib/hexapdf/tokenizer.rb +3 -3
- data/lib/hexapdf/type/file_specification.rb +2 -2
- data/lib/hexapdf/type/form.rb +19 -0
- data/lib/hexapdf/type/page.rb +21 -6
- data/lib/hexapdf/type/page_tree_node.rb +27 -34
- data/lib/hexapdf/utils/bit_stream.rb +1 -1
- data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
- data/lib/hexapdf/version.rb +1 -1
- data/man/man1/hexapdf.1 +259 -187
- data/test/hexapdf/content/graphic_object/test_arc.rb +1 -1
- data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +1 -1
- data/test/hexapdf/content/graphic_object/test_solid_arc.rb +1 -1
- data/test/hexapdf/content/test_canvas.rb +1 -1
- data/test/hexapdf/document/test_files.rb +71 -0
- data/test/hexapdf/{test_font_utils.rb → document/test_fonts.rb} +1 -2
- data/test/hexapdf/document/test_images.rb +78 -0
- data/test/hexapdf/document/test_pages.rb +114 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +26 -5
- data/test/hexapdf/font/test_true_type_wrapper.rb +1 -1
- data/test/hexapdf/font/true_type/common.rb +0 -4
- data/test/hexapdf/font/true_type/table/test_cmap.rb +0 -6
- data/test/hexapdf/font/true_type/table/test_directory.rb +0 -5
- data/test/hexapdf/font/true_type/table/test_glyf.rb +5 -8
- data/test/hexapdf/font/true_type/table/test_head.rb +0 -20
- data/test/hexapdf/font/true_type/table/test_hhea.rb +0 -7
- data/test/hexapdf/font/true_type/table/test_hmtx.rb +2 -7
- data/test/hexapdf/font/true_type/table/test_loca.rb +4 -8
- data/test/hexapdf/font/true_type/table/test_maxp.rb +0 -7
- data/test/hexapdf/font/true_type/table/test_name.rb +0 -19
- data/test/hexapdf/font/true_type/table/test_os2.rb +0 -8
- data/test/hexapdf/font/true_type/table/test_post.rb +0 -13
- data/test/hexapdf/font/true_type/test_font.rb +14 -38
- data/test/hexapdf/font/true_type/test_table.rb +0 -9
- data/test/hexapdf/font/type1/test_font_metrics.rb +22 -0
- data/test/hexapdf/task/test_dereference.rb +5 -1
- data/test/hexapdf/task/test_optimize.rb +1 -1
- data/test/hexapdf/test_dictionary.rb +4 -0
- data/test/hexapdf/test_document.rb +0 -7
- data/test/hexapdf/test_importer.rb +4 -4
- data/test/hexapdf/test_object.rb +31 -9
- data/test/hexapdf/test_rectangle.rb +18 -0
- data/test/hexapdf/test_revisions.rb +7 -0
- data/test/hexapdf/test_serializer.rb +6 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/test_form.rb +12 -0
- data/test/hexapdf/type/test_page.rb +39 -20
- data/test/hexapdf/type/test_page_tree_node.rb +28 -21
- metadata +21 -9
- data/lib/hexapdf/document_utils.rb +0 -209
- data/test/hexapdf/test_document_utils.rb +0 -144
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 874a5d2e5a2408ceed91a9bc61df138248fbbcd2
|
4
|
+
data.tar.gz: 894e67110daae93c1479a5ced992a84dabc66cd1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d7c18191ea0d64992fc01b05904c3324cc1f32e98fe848bc931970b53f42d9ab6878d4e8c3730a570c607f677dfdaa8a8adebe7ab4c8e279cc13383124dee1b
|
7
|
+
data.tar.gz: 1e080dd694ad58c3256a647a98a9b03c439ae340311b037fad3b1bdbe436ffbed74fec0c00af3df68540ca32aa75336c328a317a74233428dcf16d8920cc8e75
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
## 0.2.0 - unreleased
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* PDF file merge ability to `hexapdf modify`, i.e. adding pages from other PDFs
|
6
|
+
* Page interleaving support to 'hexapdf modify'
|
7
|
+
* Step values in pages definitions for CLI commands
|
8
|
+
* Convenience class for working with pages through [HexaPDF::Document#pages]
|
9
|
+
with a more Ruby-like interface
|
10
|
+
* Method [HexaPDF::Type::Form#canvas]
|
11
|
+
* Method [HexaPDF::Type::Page#index]
|
12
|
+
* Validation for [HexaPDF::Rectangle] objects
|
13
|
+
* [HexaPDF::Font::Type1::FontMetrics#weight_class] for returning the numeric
|
14
|
+
weight
|
15
|
+
|
16
|
+
### Changed
|
17
|
+
|
18
|
+
* Refactor document utilities into own classes with a more Ruby-like interface;
|
19
|
+
concern fonts, images and files, now accessible through
|
20
|
+
[HexaPDF::Document#fonts], [HexaPDF::Document#images] and
|
21
|
+
[HexaPDF::Document#files]
|
22
|
+
* Validate nested collection values in [HexaPDF::Object]
|
23
|
+
* Allow [HexaPDF::Dictionary#[]] to always unwrap nil values
|
24
|
+
* Update [HexaPDF::Task::Optimize] to delete unused objects on `:compact`
|
25
|
+
* Allow [HexaPDF::Type::PageTreeNode#delete_page] to take a page object or a
|
26
|
+
page index
|
27
|
+
* Don't set /EFF key in encryption dictionary
|
28
|
+
* Better error handling for hexapdf CLI commands
|
29
|
+
* Show help output when no command is given for `hexapdf` CLI
|
30
|
+
* Set /FontWeight in [HexaPDF::Font::Type1Wrapper]
|
31
|
+
* Use kramdown's man page support for the `hexapdf` man page instead of ronn
|
32
|
+
|
33
|
+
### Removed
|
34
|
+
|
35
|
+
* Remove unneeded parts of TrueType implementation
|
36
|
+
|
37
|
+
### Fixed
|
38
|
+
|
39
|
+
* Problem with unnamed classes/modules on serialization
|
40
|
+
* Handle potentially indirect objects correctly in [HexaPDF::Object::deep_copy]
|
41
|
+
* [HexaPDF::Revisions#merge] for objects that appear in multiple revisions
|
42
|
+
* Output of `--pages` option of 'hexapdf inspect' command
|
43
|
+
* Infinite recursion problem in [HexaPDF::Task::Dereference]
|
44
|
+
* Problem with iteration over images in certain cases
|
45
|
+
* [HexaPDF::Type::Page#[]] with respect to inherited fields
|
46
|
+
* Problems with access permissions on encryption
|
47
|
+
* Encryption routine of standard security handler with respect to owner password
|
48
|
+
* Invalid check in validation of standard encryption dictionary
|
49
|
+
* 'hexapdf modify' command to support files with many pages
|
50
|
+
* Validation of encryption key for encryption revision 6
|
51
|
+
* Various parts of the API documentation
|
52
|
+
|
53
|
+
|
54
|
+
## 0.1.0 - 2016-10-26
|
55
|
+
|
56
|
+
* Initial release
|
data/CONTRIBUTERS
CHANGED
data/Rakefile
CHANGED
@@ -16,7 +16,7 @@ namespace :dev do
|
|
16
16
|
PKG_FILES = FileList.new([
|
17
17
|
'Rakefile',
|
18
18
|
'LICENSE', 'agpl-3.0.txt',
|
19
|
-
'README.md',
|
19
|
+
'README.md', 'CHANGELOG.md',
|
20
20
|
'VERSION', 'CONTRIBUTERS',
|
21
21
|
'bin/*',
|
22
22
|
'lib/**/*.rb',
|
@@ -29,7 +29,7 @@ namespace :dev do
|
|
29
29
|
CLOBBER << "man/man1/hexapdf.1"
|
30
30
|
file 'man/man1/hexapdf.1' => ['man/man1/hexapdf.1.md'] do
|
31
31
|
puts "Generating hexapdf man page"
|
32
|
-
system "
|
32
|
+
system "kramdown -o man man/man1/hexapdf.1.md > man/man1/hexapdf.1"
|
33
33
|
end
|
34
34
|
|
35
35
|
CLOBBER << "VERSION"
|
@@ -65,7 +65,7 @@ namespace :dev do
|
|
65
65
|
s.executables = ['hexapdf']
|
66
66
|
s.default_executable = 'hexapdf'
|
67
67
|
s.add_dependency('cmdparse', '~> 3.0', '>= 3.0.1')
|
68
|
-
s.add_development_dependency('
|
68
|
+
s.add_development_dependency('kramdown', '~> 1.0', '>= 1.13.0')
|
69
69
|
|
70
70
|
s.author = 'Thomas Leitner'
|
71
71
|
s.email = 't_leitner@gmx.at'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/examples/arc.rb
CHANGED
data/examples/graphics.rb
CHANGED
data/examples/hello_world.rb
CHANGED
data/examples/merging.rb
CHANGED
@@ -19,6 +19,6 @@ require 'hexapdf'
|
|
19
19
|
target = HexaPDF::Document.new
|
20
20
|
ARGV.each do |file|
|
21
21
|
pdf = HexaPDF::Document.open(file)
|
22
|
-
pdf.pages.
|
22
|
+
pdf.pages.each {|page| target.pages << target.import(page)}
|
23
23
|
end
|
24
24
|
target.write("merging.pdf", optimize: true)
|
@@ -47,7 +47,7 @@ class ShowTextProcessor < HexaPDF::Content::Processor
|
|
47
47
|
end
|
48
48
|
|
49
49
|
doc = HexaPDF::Document.open(ARGV.shift)
|
50
|
-
doc.pages.
|
50
|
+
doc.pages.each_with_index do |page, index|
|
51
51
|
puts "Processing page #{index + 1}"
|
52
52
|
processor = ShowTextProcessor.new(page)
|
53
53
|
page.process_contents(processor)
|
@@ -26,7 +26,7 @@ doc = HexaPDF::Document.new
|
|
26
26
|
|
27
27
|
HexaPDF::FontLoader::Standard14::MAPPING.each do |font_name, mapping|
|
28
28
|
mapping.each_key do |variant|
|
29
|
-
canvas = doc.pages.
|
29
|
+
canvas = doc.pages.add.canvas
|
30
30
|
canvas.font("Helvetica", size: 14)
|
31
31
|
canvas.text("#{font_name} #{variant != :none ? variant : ''}", at: [100, 800])
|
32
32
|
|
data/examples/truetype.rb
CHANGED
@@ -27,7 +27,7 @@ max_gid = wrapper.wrapped_font[:maxp].num_glyphs
|
|
27
27
|
|
28
28
|
255.times do |page|
|
29
29
|
break unless page * 256 < wrapper.wrapped_font[:maxp].num_glyphs
|
30
|
-
canvas = doc.pages.
|
30
|
+
canvas = doc.pages.add.canvas
|
31
31
|
canvas.font("Helvetica", size: 10)
|
32
32
|
canvas.text("Font: #{wrapper.wrapped_font.full_name}", at: [50, 825])
|
33
33
|
|
data/lib/hexapdf/cli.rb
CHANGED
@@ -49,6 +49,9 @@ module HexaPDF
|
|
49
49
|
# Runs the CLI application.
|
50
50
|
def self.run(args = ARGV)
|
51
51
|
Application.new.parse(args)
|
52
|
+
rescue => e
|
53
|
+
$stderr.puts "An error occurred: #{e.message}"
|
54
|
+
exit(1)
|
52
55
|
end
|
53
56
|
|
54
57
|
# The CmdParse::CommandParser class that is used for running the CLI application.
|
@@ -66,8 +69,13 @@ module HexaPDF
|
|
66
69
|
add_command(CmdParse::VersionCommand.new)
|
67
70
|
end
|
68
71
|
|
69
|
-
|
70
|
-
|
72
|
+
def parse(argv = ARGV) #:nodoc:
|
73
|
+
ARGV.unshift('help') if ARGV.empty?
|
74
|
+
super
|
75
|
+
end
|
76
|
+
|
77
|
+
PAGE_NUMBER_SPEC = "([1-9]\\d*|e)".freeze #:nodoc:
|
78
|
+
ROTATE_MAP = {'l' => -90, 'r' => 90, 'd' => 180, 'n' => :none}.freeze #:nodoc:
|
71
79
|
|
72
80
|
# Parses the pages specification string and returns an array of tuples containing a page
|
73
81
|
# number and a rotation value (either -90, 90, 180 or :none).
|
@@ -78,13 +86,12 @@ module HexaPDF
|
|
78
86
|
case str
|
79
87
|
when /\A#{PAGE_NUMBER_SPEC}(l|r|d|n)?\z/o
|
80
88
|
arr << [($1 == 'e' ? count : str.to_i) - 1, ROTATE_MAP[$2]]
|
81
|
-
when /\A#{PAGE_NUMBER_SPEC}-#{PAGE_NUMBER_SPEC}(l|r|d|n)?\z/
|
89
|
+
when /\A#{PAGE_NUMBER_SPEC}-#{PAGE_NUMBER_SPEC}(?:\/([1-9]\d*))?(l|r|d|n)?\z/
|
82
90
|
start_nr = ($1 == 'e' ? count : $1.to_i) - 1
|
83
91
|
end_nr = ($2 == 'e' ? count : $2.to_i) - 1
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
end
|
92
|
+
step = ($3 ? $3.to_i : 1) * (start_nr > end_nr ? -1 : 1)
|
93
|
+
rotation = ROTATE_MAP[$4]
|
94
|
+
start_nr.step(to: end_nr, by: step) {|n| arr << [n, rotation]}
|
88
95
|
else
|
89
96
|
raise OptionParser::InvalidArgument, "invalid page range format: #{str}"
|
90
97
|
end
|
data/lib/hexapdf/cli/extract.rb
CHANGED
@@ -119,7 +119,7 @@ module HexaPDF
|
|
119
119
|
|
120
120
|
# Iterates over all embedded files.
|
121
121
|
def each_file(doc, &block) # :yields: obj, index
|
122
|
-
doc.
|
122
|
+
doc.files.each(search: @search).select(&:embedded_file?).each_with_index(&block)
|
123
123
|
end
|
124
124
|
|
125
125
|
end
|
data/lib/hexapdf/cli/info.rb
CHANGED
@@ -68,7 +68,7 @@ module HexaPDF
|
|
68
68
|
private
|
69
69
|
|
70
70
|
INFO_KEYS = [:Title, :Author, :Subject, :Keywords, :Creator, :Producer, #:nodoc:
|
71
|
-
:CreationDate, :ModDate]
|
71
|
+
:CreationDate, :ModDate].freeze
|
72
72
|
|
73
73
|
COLUMN_WIDTH = 20 #:nodoc:
|
74
74
|
|
@@ -95,7 +95,7 @@ module HexaPDF
|
|
95
95
|
output_line("Encrypted", "yes (no or wrong password given)")
|
96
96
|
end
|
97
97
|
|
98
|
-
output_line("Pages", doc.pages.
|
98
|
+
output_line("Pages", doc.pages.count.to_s)
|
99
99
|
output_line("Version", doc.version)
|
100
100
|
end
|
101
101
|
rescue HexaPDF::EncryptionError => e
|
data/lib/hexapdf/cli/inspect.rb
CHANGED
@@ -113,15 +113,15 @@ module HexaPDF
|
|
113
113
|
end
|
114
114
|
|
115
115
|
def do_page_count(doc) #:nodoc:
|
116
|
-
puts doc.pages.
|
116
|
+
puts doc.pages.count
|
117
117
|
end
|
118
118
|
|
119
119
|
def do_pages(doc) #:nodoc:
|
120
|
-
pages = command_parser.parse_pages_specification(@param, doc.pages.
|
120
|
+
pages = command_parser.parse_pages_specification(@param, doc.pages.count)
|
121
121
|
pages.each do |index, _|
|
122
|
-
page = doc.pages
|
122
|
+
page = doc.pages[index]
|
123
123
|
str = "page #{index + 1} (#{page.oid},#{page.gen}): "
|
124
|
-
Array(page[:Contents]).
|
124
|
+
str << Array(page[:Contents]).map {|c| "#{c.oid},#{c.gen}"}.join(" ")
|
125
125
|
puts str
|
126
126
|
end
|
127
127
|
end
|
data/lib/hexapdf/cli/modify.rb
CHANGED
@@ -39,28 +39,58 @@ module HexaPDF
|
|
39
39
|
|
40
40
|
# Modifies a PDF file:
|
41
41
|
#
|
42
|
-
# *
|
42
|
+
# * Adds pages from other PDF files.
|
43
|
+
# * Decrypts or encrypts the resulting output PDF file.
|
43
44
|
# * Generates or deletes object and cross-reference streams.
|
44
|
-
# * Optimizes
|
45
|
+
# * Optimizes the output PDF by merging the revisions of a PDF file and removes unused entries.
|
45
46
|
#
|
46
47
|
# See: HexaPDF::Task::Optimize
|
47
48
|
class Modify < CmdParse::Command
|
48
49
|
|
50
|
+
InputSpec = Struct.new(:file, :pages, :password) #:nodoc:
|
51
|
+
|
49
52
|
def initialize #:nodoc:
|
50
53
|
super('modify', takes_commands: false)
|
51
54
|
short_desc("Modify a PDF file")
|
52
55
|
long_desc(<<-EOF.gsub!(/^ */, ''))
|
53
|
-
This command modifies a PDF file. It can be used to
|
54
|
-
|
56
|
+
This command modifies a PDF file. It can be used to select pages that should appear in
|
57
|
+
the output file and to add pages from other PDF files. The output file can be
|
58
|
+
encrypted/decrypted and optimized in various ways.
|
59
|
+
|
60
|
+
The first input file is the primary file which gets modified, so meta data like file
|
61
|
+
information, outlines, etc. are taken from it. Alternatively, it is possible to start
|
62
|
+
with an empty PDF file by using --empty. The order of the options specifying the files
|
63
|
+
is important as they are used in that order.
|
64
|
+
|
65
|
+
Also note that the --password and --pages options apply to the last preceeding input file.
|
55
66
|
EOF
|
56
67
|
|
57
|
-
options.
|
58
|
-
|
59
|
-
|
68
|
+
options.separator("")
|
69
|
+
options.separator("Input file(s) related options")
|
70
|
+
options.on("-f", "--file FILE", "Input file, can be specified multiple times") do |file|
|
71
|
+
@files << InputSpec.new(file, '1-e')
|
72
|
+
end
|
73
|
+
options.on("-p", "--password PASSWORD", String, "The password for decrypting the last " \
|
74
|
+
"specified input file (use - for reading from standard input)") do |pwd|
|
75
|
+
raise OptionParser::InvalidArgument, "(No prior input file specified)" if @files.empty?
|
76
|
+
pwd = (pwd == '-' ? command_parser.read_password("#{@files.last.file} password") : pwd)
|
77
|
+
@files.last.password = pwd
|
60
78
|
end
|
61
|
-
options.on("--pages PAGES", "The pages
|
62
|
-
|
79
|
+
options.on("-i", "--pages PAGES", "The pages of the last specified input file that " \
|
80
|
+
"should be used (default: 1-e)") do |pages|
|
81
|
+
raise OptionParser::InvalidArgument, "(No prior input file specified)" if @files.empty?
|
82
|
+
@files.last.pages = pages
|
63
83
|
end
|
84
|
+
options.on("-e", "--empty", "Use an empty file as the first input file") do
|
85
|
+
@initial_empty = true
|
86
|
+
end
|
87
|
+
options.on("--[no-]interleave", "Interleave the pages from the input files (default: " \
|
88
|
+
"false)") do |c|
|
89
|
+
@interleave = c
|
90
|
+
end
|
91
|
+
|
92
|
+
options.separator("")
|
93
|
+
options.separator("Output file related options")
|
64
94
|
options.on("--embed FILE", String, "Embed the file into the output file (can be used " \
|
65
95
|
"multiple times)") do |file|
|
66
96
|
@embed_files << file
|
@@ -83,9 +113,10 @@ module HexaPDF
|
|
83
113
|
"preserve)") do |streams|
|
84
114
|
@streams = streams
|
85
115
|
end
|
86
|
-
|
87
|
-
|
88
|
-
|
116
|
+
options.on("--[no-]compress-pages", "Recompress page content streams (may take a long " \
|
117
|
+
"time; default: no)") do |c|
|
118
|
+
@compress_pages = c
|
119
|
+
end
|
89
120
|
options.on("--decrypt", "Remove any encryption") do
|
90
121
|
@encryption = :remove
|
91
122
|
end
|
@@ -93,12 +124,12 @@ module HexaPDF
|
|
93
124
|
@encryption = :add
|
94
125
|
end
|
95
126
|
options.on("--owner-password PASSWORD", String, "The owner password to be set on the " \
|
96
|
-
"output file
|
127
|
+
"output file (use - for reading from standard input)") do |pwd|
|
97
128
|
@encryption = :add
|
98
129
|
@enc_owner_pwd = (pwd == '-' ? command_parser.read_password("Owner password") : pwd)
|
99
130
|
end
|
100
131
|
options.on("--user-password PASSWORD", String, "The user password to be set on the " \
|
101
|
-
"output file
|
132
|
+
"output file (use - for reading from standard input)") do |pwd|
|
102
133
|
@encryption = :add
|
103
134
|
@enc_user_pwd = (pwd == '-' ? command_parser.read_password("User password") : pwd)
|
104
135
|
end
|
@@ -121,19 +152,23 @@ module HexaPDF
|
|
121
152
|
options.on("--permissions PERMS", Array,
|
122
153
|
"Comma separated list of permissions to be set on the output file. Possible " \
|
123
154
|
"values: #{syms.join(', ')}") do |perms|
|
124
|
-
perms.
|
125
|
-
unless syms.include?(perm)
|
155
|
+
perms.map! do |perm|
|
156
|
+
unless syms.include?(perm.to_sym)
|
126
157
|
raise OptionParser::InvalidArgument, "#{perm} (invalid permission name)"
|
127
158
|
end
|
159
|
+
perm.to_sym
|
128
160
|
end
|
129
161
|
@encryption = :add
|
130
162
|
@enc_permissions = perms
|
131
163
|
end
|
132
164
|
|
133
|
-
@
|
134
|
-
@
|
165
|
+
@files = []
|
166
|
+
@initial_empty = false
|
167
|
+
@interleave = false
|
168
|
+
|
135
169
|
@embed_files = []
|
136
170
|
@compact = true
|
171
|
+
@compress_pages = false
|
137
172
|
@object_streams = :preserve
|
138
173
|
@xref_streams = :preserve
|
139
174
|
@streams = :preserve
|
@@ -146,58 +181,123 @@ module HexaPDF
|
|
146
181
|
@enc_permissions = []
|
147
182
|
end
|
148
183
|
|
149
|
-
def execute(
|
150
|
-
|
151
|
-
|
152
|
-
|
184
|
+
def execute(output_file) #:nodoc:
|
185
|
+
if !@initial_empty && @files.empty?
|
186
|
+
error = OptionParser::ParseError.new("At least one --file FILE or --empty is needed")
|
187
|
+
error.reason = "Missing argument"
|
188
|
+
raise error
|
153
189
|
end
|
154
190
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
191
|
+
# Create PDF documents for each input file
|
192
|
+
cache = {}
|
193
|
+
@files.each do |spec|
|
194
|
+
cache[spec.file] ||= HexaPDF::Document.new(io: File.open(spec.file),
|
195
|
+
decryption_opts: {password: spec.password})
|
196
|
+
spec.file = cache[spec.file]
|
197
|
+
end
|
161
198
|
|
162
|
-
|
199
|
+
# Assemble pages
|
200
|
+
target = (@initial_empty ? HexaPDF::Document.new : @files.first.file)
|
201
|
+
page_tree = target.add(Type: :Pages)
|
202
|
+
import_pages(page_tree)
|
203
|
+
target.catalog[:Pages] = page_tree
|
163
204
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
205
|
+
# Remove potentially imported but unused pages and page tree nodes
|
206
|
+
retained = target.pages.each_with_object({}) {|page, h| h[page.data] = true}
|
207
|
+
retained[target.pages.root.data] = true
|
208
|
+
target.each(current: false) do |obj|
|
209
|
+
next unless obj.kind_of?(HexaPDF::Dictionary)
|
210
|
+
if (obj.type == :Pages || obj.type == :Page) && !retained.key?(obj.data)
|
211
|
+
target.delete(obj)
|
170
212
|
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Embed the given files
|
216
|
+
@embed_files.each {|file| target.files.add(file, embed: true)}
|
217
|
+
|
218
|
+
# Optimize the PDF file
|
219
|
+
target.task(:optimize, compact: @compact, object_streams: @object_streams,
|
220
|
+
xref_streams: @xref_streams, compress_pages: @compress_pages)
|
171
221
|
|
172
|
-
|
222
|
+
# Update stream filters
|
223
|
+
handle_streams(target) unless @streams == :preserve
|
224
|
+
|
225
|
+
# Encrypt, decrypt or do nothing
|
226
|
+
if @encryption == :add
|
227
|
+
target.encrypt(algorithm: @enc_algorithm, key_length: @enc_key_length,
|
228
|
+
force_V4: @enc_force_v4, permissions: @enc_permissions,
|
229
|
+
owner_password: @enc_owner_pwd, user_password: @enc_user_pwd)
|
230
|
+
elsif @encryption == :remove
|
231
|
+
target.encrypt(name: nil)
|
173
232
|
end
|
233
|
+
|
234
|
+
target.write(output_file)
|
174
235
|
rescue HexaPDF::Error => e
|
175
|
-
$stderr.puts "
|
236
|
+
$stderr.puts "Processing error : #{e.message}"
|
176
237
|
exit(1)
|
177
238
|
end
|
178
239
|
|
240
|
+
def usage_arguments #:nodoc:
|
241
|
+
"{--file IN_FILE | --empty} OUT_FILE"
|
242
|
+
end
|
243
|
+
|
179
244
|
private
|
180
245
|
|
181
|
-
#
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
246
|
+
# Imports the pages of the document as specified with the --pages option to the given page
|
247
|
+
# tree.
|
248
|
+
def import_pages(page_tree)
|
249
|
+
@files.each do |s|
|
250
|
+
page_list = s.file.pages.to_a
|
251
|
+
s.pages = command_parser.parse_pages_specification(s.pages, s.file.pages.count)
|
252
|
+
s.pages.each do |arr|
|
253
|
+
arr[0] = page_list[arr[0]]
|
254
|
+
arr[1] = arr[0].value[:Rotate] || :none unless arr[1]
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
if @interleave
|
259
|
+
max_pages_per_file = 0
|
260
|
+
all = @files.each_with_index.map do |spec, findex|
|
261
|
+
list = []
|
262
|
+
spec.pages.each {|index, rotation| list << [spec.file, findex, index, rotation]}
|
263
|
+
max_pages_per_file = list.size if list.size > max_pages_per_file
|
264
|
+
list
|
192
265
|
end
|
193
|
-
|
266
|
+
first, *rest = *all
|
267
|
+
first[max_pages_per_file - 1] ||= nil
|
268
|
+
first.zip(*rest) do |slice|
|
269
|
+
slice.each do |source, findex, page, rotation|
|
270
|
+
next unless source
|
271
|
+
import_page(page_tree, findex, page, rotation)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
else
|
275
|
+
@files.each_with_index do |s, findex|
|
276
|
+
s.pages.each {|page, rotation| import_page(page_tree, findex, page, rotation)}
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# Import the page with the given +rotation+ into the page tree.
|
282
|
+
def import_page(page_tree, source_index, page, rotation)
|
283
|
+
if page_tree.document == page.document
|
284
|
+
page.value.update(page.copy_inherited_values)
|
285
|
+
page = page.deep_copy unless source_index == 0
|
286
|
+
else
|
287
|
+
page = page_tree.document.import(page).deep_copy
|
288
|
+
end
|
289
|
+
if rotation == :none
|
290
|
+
page.delete(:Rotate)
|
291
|
+
elsif rotation.kind_of?(Integer)
|
292
|
+
page[:Rotate] = ((page[:Rotate] || 0) + rotation) % 360
|
194
293
|
end
|
195
|
-
|
294
|
+
page_tree.document.add(page)
|
295
|
+
page_tree.add_page(page)
|
196
296
|
end
|
197
297
|
|
198
298
|
IGNORED_FILTERS = { #:nodoc:
|
199
299
|
CCITTFaxDecode: true, JBIG2Decode: true, DCTDecode: true, JPXDecode: true, Crypt: true
|
200
|
-
}
|
300
|
+
}.freeze
|
201
301
|
|
202
302
|
# Applies the chosen stream mode to all streams.
|
203
303
|
def handle_streams(doc)
|