lib/pdfmult.rb in pdfmult-1.3.0 vs lib/pdfmult.rb in pdfmult-1.3.1
- old
+ new
@@ -1,80 +1,42 @@
-#!/usr/bin/ruby -w
+#!/usr/bin/env ruby
# == Name
#
# pdfmult - put multiple copies of a PDF page on one page
#
-# == Synopsis
-#
-# pdfmult [options] file
-#
# == Description
#
# +pdfmult+ rearranges multiple copies of a PDF page (shrunken) on one page.
#
-# The paper size of the produced PDF file is A4,
-# the input file is also assumed to be in A4 format.
-# The input PDF file may consist of several pages.
-# If +pdfmult+ succeeds in obtaining the page count it will rearrange all pages,
-# if not, only the first page is processed
-# (unless the page count was specified via command line option).
+# == See also
#
-# +pdfmult+ uses +pdflatex+ with the +pdfpages+ package,
-# so both have to be installed on the system.
-# If the --latex option is used, though, +pdflatex+ is not run
-# and a LaTeX file is created instead of a PDF.
+# Use <tt>pdfmult --help</tt> to display a brief help message.
#
-# == Options
+# The full documentation for +pdfmult+ is available on the
+# project home page.
#
-# -n, --number:: Number of copies to put on one page: 2 (default), 4, 8, 9, 16.
-#
-# -f, --[no-]force:: Do not prompt before overwriting.
-#
-# -l, --latex:: Create a LaTeX file instead of a PDF file (default: infile_NUMBER.tex).
-#
-# -o, --output:: Output file (default: infile_NUMBER.pdf).
-# Use - to output to stdout.
-#
-# -p, --pages:: Number of pages to convert.
-# If given, +pdfmult+ does not try to obtain the page count from the source PDF.
-#
-# -s, --[no-]silent:: Do not output progress information.
-#
-# -h, --help:: Prints a brief help message and exits.
-#
-# -v, --version:: Prints a brief version information and exits.
-#
-# == Examples
-#
-# pdfmult sample.pdf # => sample_2.pdf (2 copies)
-# pdfmult -n 4 sample.pdf # => sample_4.pdf (4 copies)
-# pdfmult sample.pdf -o outfile.pdf # => outfile.pdf (2 copies)
-# pdfmult sample.pdf -p 3 # => processes 3 pages
-# pdfmult sample.pdf -o - | lpr # => sends output via stdout to print command
-#
# == Author
#
-# Copyright (C) 2011-2012 Marcus Stollsteimer
+# Copyright (C) 2011-2013 Marcus Stollsteimer
#
# License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
-#
-
require 'optparse'
require 'tempfile'
-require 'fileutils'
require 'open3'
+require 'erb'
# This module contains the classes for the +pdfmult+ tool.
module Pdfmult
PROGNAME = 'pdfmult'
- VERSION = '1.3.0'
- DATE = '2012-09-22'
+ VERSION = '1.3.1'
+ DATE = '2013-01-04'
HOMEPAGE = 'https://github.com/stomar/pdfmult/'
+ TAGLINE = 'puts multiple copies of a PDF page on one page'
- COPYRIGHT = "Copyright (C) 2011-2012 Marcus Stollsteimer.\n" +
+ COPYRIGHT = "Copyright (C) 2011-2013 Marcus Stollsteimer.\n" +
"License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n" +
"This is free software: you are free to change and redistribute it.\n" +
"There is NO WARRANTY, to the extent permitted by law."
PDFLATEX = '/usr/bin/pdflatex'
@@ -124,18 +86,18 @@
opt.separator 'Options'
opt.separator ''
# process --version and --help first,
# exit successfully (GNU Coding Standards)
- opt.on_tail('-h', '--help', 'Prints a brief help message and exits.') do
+ opt.on_tail('-h', '--help', 'Print a brief help message and exit.') do
puts opt_parser
puts "\nReport bugs on the #{PROGNAME} home page: <#{HOMEPAGE}>"
exit
end
opt.on_tail('-v', '--version',
- 'Prints a brief version information and exits.') do
+ 'Print a brief version information and exit.') do
puts "#{PROGNAME} #{VERSION}"
puts COPYRIGHT
exit
end
@@ -171,11 +133,11 @@
opt.separator ''
end
opt_parser.parse!(argv)
# only input file should be left in argv
- raise(ArgumentError, 'wrong number of arguments') if (argv.size != 1 || argv[0] == '')
+ raise(ArgumentError, 'wrong number of arguments') if (argv.size != 1 || argv[0].empty?)
options[:infile] = argv.pop
# set output file unless set by option
ext = options[:latex] ? 'tex' : 'pdf'
@@ -184,75 +146,90 @@
options
end
end
+ # Class for the page layout.
+ #
+ # Create an instance with Layout.new, specifying
+ # the number of pages to put on one page.
+ # Layout#geometry returns the geometry string.
+ class Layout
+
+ attr_reader :pages, :geometry
+
+ GEOMETRY = {
+ 2 => '2x1',
+ 4 => '2x2',
+ 8 => '4x2',
+ 9 => '3x3',
+ 16 => '4x4'
+ }
+
+ def initialize(pages)
+ @pages = pages
+ @geometry = GEOMETRY[pages]
+ end
+
+ def landscape?
+ ['2x1', '4x2'].include?(geometry)
+ end
+ end
+
# Class for the LaTeX document.
#
# Create an instance with LaTeXDocument.new, specifying
# the input file, the number of pages to put on one page,
# and the page count of the input file.
#
# The method +to_s+ returns the document as multiline string.
class LaTeXDocument
- attr_accessor :infile, :number, :page_count
+ TEMPLATE = %q(
+ \documentclass[<%= class_options %>]{article}
+ \usepackage{pdfpages}
+ \pagestyle{empty}
+ \setlength{\parindent}{0pt}
+ \begin{document}
+ % pages_strings.each do |pages|
+ \includepdf[pages={<%= pages %>},nup=<%= geometry %>]{<%= @pdffile %>}%
+ % end
+ \end{document}
+ ).gsub(/\A\n/,'').gsub(/^ +/, '')
- HEADER =
- "\\documentclass[CLASSOPTIONS]{article}\n" +
- "\\usepackage{pdfpages}\n" +
- "\\pagestyle{empty}\n" +
- "\\setlength{\\parindent}{0pt}\n" +
- "\\begin{document}%\n"
-
- CONTENT =
- "\\includepdf[pages={PAGES},nup=GEOMETRY]{FILENAME}%\n"
-
- FOOTER =
- "\\end{document}\n"
-
# Initializes a LaTeXDocument instance.
+ # Expects an argument hash with:
#
- # +infile+ - input file name
- # +number+ - number of pages to put on one page
- # +page_count+ - page count of the input file
- def initialize(infile, number, page_count)
- @infile = infile
- @number = number
- @page_count = page_count
+ # +:pdffile+ - filename of input pdf file
+ # +:layout+ - page layout
+ # +:page_count+ - page count of the input file
+ def initialize(args)
+ @pdffile = args[:pdffile]
+ @layout = args[:layout]
+ @page_count = args[:page_count]
end
def to_s
- class_options = 'a4paper'
- page_string = 'PAGE,' * (@number - 1) + 'PAGE' # 4 copies: e.g. 1,1,1,1
+ class_options = "a4paper"
+ class_options << ',landscape' if @layout.landscape?
+ latex = ERB.new(TEMPLATE, 0, '%<>')
- case @number
- when 2
- class_options << ',landscape'
- geometry = '2x1'
- when 4
- geometry = '2x2'
- when 8
- class_options << ',landscape'
- geometry = '4x2'
- when 9
- geometry = '3x3'
- when 16
- geometry = '4x4'
- end
+ latex.result(binding)
+ end
- content_template = CONTENT.gsub(/PAGES|GEOMETRY|FILENAME/,
- 'PAGES' => page_string,
- 'GEOMETRY' => geometry,
- 'FILENAME' => @infile)
+ private
- content = HEADER.gsub(/CLASSOPTIONS/, class_options)
- @page_count.times do |i|
- content << content_template.gsub(/PAGE/,"#{i+1}")
- end
+ def geometry
+ @layout.geometry
+ end
- content << FOOTER
+ # Returns an array of pages strings.
+ # For 4 copies and 2 pages: ["1,1,1,1", "2,2,2,2"].
+ def pages_strings
+ template = 'PAGE,' * (@layout.pages - 1) + 'PAGE'
+
+ Array.new(@page_count) {|i| template.gsub(/PAGE/, "#{i+1}") }
end
end
# A class for PDF meta data (up to now only used for the page count).
#
@@ -262,35 +239,37 @@
# else the attribute is set to +nil+.
class PDFInfo
PDFINFOCMD = '/usr/bin/pdfinfo'
- # Contains the page count of the input file, or nil.
+ # Returns the page count of the input file, or nil.
attr_reader :page_count
# This is the initialization method for the class.
#
# +file+ - file name of the PDF file
def initialize(file, options={})
- @page_count = nil
- infos = Hash.new
+ @file = file
+ @binary = options[:pdfinfocmd] || PDFINFOCMD # for unit tests
+ @infos = retrieve_infos
+ @page_count = @infos['Pages'] && @infos['Pages'].to_i
+ end
- binary = options[:pdfinfocmd] || PDFINFOCMD # only for unit tests
- command = "#{binary} #{file}"
- if Application.command_available?(command)
- infostring = `#{command}`
- infostring.each_line do |line|
- key, val = line.chomp.split(/\s*:\s*/, 2)
- infos[key] = val
- end
- value = infos['Pages']
- @page_count = value.to_i unless value.nil?
- end
+ private
+
+ # Tries to retrieve the PDF infos for the file; returns an info hash.
+ def retrieve_infos
+ command = "#{@binary} #{@file}"
+ return {} unless Application.command_available?(command)
+
+ info_array = `#{command}`.split(/\n/)
+
+ Hash[info_array.map {|line| line.split(/\s*:\s*/, 2) }]
end
# Returns true if default +pdfinfo+ system tool is available (for unit tests).
- def self.infocmd_available? # :nodoc:
+ def self.infocmd_available?
Application.command_available?("#{PDFINFOCMD} -v")
end
end
# The main program. It's run! method is called
@@ -331,52 +310,50 @@
overwrite_ok = ask("File `#{outfile}' already exists. Overwrite?")
exit unless overwrite_ok
end
# set page number (get PDF info if necessary)
- pages = options[:pages]
- pages ||= PDFInfo.new(infile).page_count
- pages ||= 1
+ pages = options[:pages] || PDFInfo.new(infile).page_count || 1
# create LaTeX document
- document = LaTeXDocument.new(infile, options[:number], pages)
+ args = {
+ :pdffile => infile,
+ :layout => Layout.new(options[:number]),
+ :page_count => pages
+ }
+ document = LaTeXDocument.new(args)
+ output = nil
if options[:latex]
- if use_stdout
- puts document.to_s
- else
- warn "Writing on #{outfile}." unless silent
- open(outfile, 'w') {|f| f.write(document.to_s) }
- end
+ output = document.to_s
else
Dir.mktmpdir('pdfmult') do |dir|
texfile = 'pdfmult.tex'
pdffile = 'pdfmult.pdf'
open("#{dir}/#{texfile}", 'w') {|f| f.write(document.to_s) }
command = "#{PDFLATEX} -output-directory #{dir} #{texfile}"
Open3.popen3(command) do |stdin, stdout, stderr|
stdout.each_line {|line| warn line.chomp } unless silent # redirect progress messages to stderr
stderr.read # make sure all streams are read (and command has finished)
end
- if use_stdout
- File.open("#{dir}/#{pdffile}") do |f|
- f.each_line {|line| puts line }
- end
- else
- warn "Writing on #{outfile}." unless silent
- FileUtils::mv("#{dir}/#{pdffile}", outfile)
- end
+ output = File.read("#{dir}/#{pdffile}")
end
end
+
+ # redirect stdout to output file
+ $stdout.reopen(outfile, 'w') unless use_stdout
+
+ warn "Writing on #{outfile}." unless (use_stdout || silent)
+ puts output
end
# Asks for yes or no (y/n).
#
# +question+ - string to be printed
#
# Returns +true+ if the answer is yes.
def self.ask(question) # :nodoc:
- while true
+ loop do
$stderr.print "#{question} [y/n] "
reply = $stdin.gets.chomp.downcase # $stdin: avoids gets / ARGV problem
return true if reply == 'y'
return false if reply == 'n'
warn "Please answer `y' or `n'."