Module: MergePDFs

Defined in:
lib/mergepdfs.rb

Overview

mergepdfs combines multiple PDF files into one output PDF file. Empty pages are added to ensure that each merged PDF file starts at an odd page number in the output file. When printed double-sided, pages of the merged PDF files are never printed on the same sheet.

Copyright © 2020 Huub de Beer <[email protected]>

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License along with this program. If not, see <www.gnu.org/licenses/>.

Defined Under Namespace

Classes: Error

Constant Summary collapse

VERSION =
"0.1.1"
RELEASE_DATE =
"2020-04-24"
VERSION_MESSAGE =
<<~END
    mergepdfs #{VERSION} (#{RELEASE_DATE})
    Copyright (C) Huub de Beer <[email protected]>
    mergepdfs is free software; mergepdfs is released under the AGPL 3.0
END
DEFAULT_OPTIONS =
{
    pdfs: [],
    continue_on_error: false,
    show_diagnostics: false
}
DEFAULT_OUTPUT_FILENAME =
"output.pdf"

Class Method Summary collapse

Class Method Details

.merge(output:, pdfs: [], continue_on_error: false, show_diagnostics: false) ⇒ Object

Merge multiple PDF files into a single PDF file, starting each PDF file at an odd page.

Parameters:

  • output (String)

    the filename to write the merged PDFs to

  • pdfs (Array<String>) (defaults to: [])

    the filenames of the PDF files to merge

  • continue_on_error (Boolean) (defaults to: false)

    if an input PDF file cannot be merged, continue merging the other input PDF files

  • show_diagnostics (Boolean) (defaults to: false)

    show more detailed error information



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/mergepdfs.rb', line 55

def self.merge(output:, pdfs: [], continue_on_error: false, show_diagnostics: false)
    merged_pdf = HexaPDF::Document.new

    pdfs.each do |pdf|
        # invariant: merged_pdf.pages.count.even?
        begin
            pdf_file = HexaPDF::Document.open(pdf)
            pdf_file.pages.each do |page| 
                merged_page = merged_pdf.import(page)
                merged_pdf.pages.add(merged_page)
            end
            merged_pdf.pages.add() if pdf_file.pages.count.odd?
        rescue => e
            warn "ERROR\t Unable to open or read '#{pdf}'. Make sure this file is readable, unencrypted, and a PDF file."
            warn e.full_message() if show_diagnostics
            return unless continue_on_error
        end
    end

    begin
        merged_pdf.write(output, optimize: true)
    rescue => e
        warn "ERROR\t Unable to write the merged PDF to the output file '#{output}'."
        warn e.full_message() if show_diagnostics
    end
end

.parse(args = ARGV) ⇒ Hash

Parse command-line parameters

Parameters:

  • args (Array<String>) (defaults to: ARGV)

    a list with command-line parameters

Returns:

  • (Hash)

    a map with options matching the parameters for #merge.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/mergepdfs.rb', line 87

def self.parse(args = ARGV)
    options = DEFAULT_OPTIONS

    begin
        OptionParser.new do |parser|
            parser.banner = "Usage: mergepdfs --output FILENAME [OPTIONS] file1.pdf file2.pdf ... fileN.pdf"

            parser.separator("");
            parser.separator("Required:")

            parser.on("-o FILENAME", "--output FILENAME",
                      "The name of the output PDF file. If no filename is specified,",
                      "'#{DEFAULT_OUTPUT_FILENAME}' is used by default.") do |output|
                options[:output] = output
            end

            parser.separator("");
            parser.separator("Optional:")

            parser.on("-c", "--continue-on-error", 
                      "When an error occurs merging a PDF file, mergepdfs tries",
                      "to continue merging the other PDF files. Default is false.") do
                options[:continue_on_error] = true
            end

            parser.on("-d", "--diagnostics",
                      "Show detailed error messages. Default is false.") do
                options[:show_diagnostics] = true
            end

            parser.separator("");
            parser.separator("Common:")

            parser.on_tail("-v", "--version", "Show the version.") do
                puts VERSION_MESSAGE
                exit
            end

            parser.on_tail("-h", "--help", "Show this help message.") do
                puts parser
                exit
            end
        end.parse!(args)

    rescue OptionParser::InvalidOption => e
        warn "ERROR\t #{e}."
        warn e.full_message if options[:show_diagnostics]
    rescue OptionParser::ParseError => e
        warn "ERROR\t Problem while parsing the command-line parameters: #{e}."
        warn e.full_message if options[:show_diagnostics]
    end

    if not options.key? :output
        options[:output] = DEFAULT_OUTPUT_FILENAME
        warn "ERROR\t No output filename specified. Defaulting to '#{DEFAULT_OUTPUT_FILENAME}'." if options[:show_diagnostics]
    end

    # The rest of the command-line options are treated as the input PDF
    # files. 
    options[:pdfs] = args
    options
end