PDF Printing

From SlackWiki
Jump to navigation Jump to search

PDF Printing

While many applications offer exporting output to portable document format (PDF) files, many others do not. It is easy to add a virtual PDF printer to CUPS by installing the cups-pdf package that is available at http://www.slackbuilds.org

This works well for single documents, but if you print multiple documents that are parsed as having the same name then the older documents will be overwritten. A solution to this is to run a script that watches for the creation of the PDF document in the spool directory (/var/spool/cups-pdf/<username> by default) and then moves the document to another directory (USERS_DIR). The script below (watch_for_spooled_pdf.sh kept in /home/xx/pdf_printing) also prepends a date and time string to keep the document names unique. A user xx should start this script as a background process i.e. /home/xx/pdf_printing/watch_for_spooled_pdf.sh &

#!/bin/sh

# Script to watch for creation of .pdf files by CUPS-PDF in spool directory
#  and move to users directory prepending the creation date and time.
#
# This script uses the -m option to inotifywait and should never exit.

SPOOL_DIR=/var/spool/cups-pdf/xx/
USERS_DIR=/home/xx/Desktop/PDF_Files/

inotifywait  -mq --timefmt '%Y%m%d %H%M%S' --format '%T %f' \
  -e close_write $SPOOL_DIR \
  | while read date time file; do
      mv $SPOOL_DIR${file} $USERS_DIR${date}${time}_${file}
    done

What if printed PDF documents should be combined into one large PDF document? A user could move the files to be combined to a new subdirectory and then run the script (combine.sh) shown below. This script creates a single PDF document with bookmark entries to each of the individual documents using the facilities in the pdfpages and hyperref packages of LaTeX. The page order is in the order of creation date and time.

#!/bin/bash

# Script to combine .pdf files into a single bookmarked .pdf file
# Intended to be run from within a directory containing files to be concatenated.
# The directory name is accepted as a parameter

# If the directory name has been passed, then make it the working directory.
if [[ $1 ]]; then cd $1; fi

# Filename for the .tex file that will be processed using pdflatex
OUTFILE=$PWD/out2.tex
# Starting values for bookmark creation
BKMK_TEXT=Contents
BKMK_ANCHOR=Beginning
BKMK_LEVEL=0
# Variable containing ASCII code for centre dot character
CENTREDOT=$(echo -e "\xB7")

# Function to sanitise filenames by:
#  Changing space characters to underscore characters
#  and changing all but last period characters to centre dot (0xB7)
#  Necessary for \includepdf to parse filenames correctly
sanitise_filenames () {
for FILENAME in *.[Pp][Dd][Ff] ; do
  PREFIX=${FILENAME%.*}
  SUFFIX=${FILENAME##*.}
  NEWPREFIX=${PREFIX// /_}
  NEWPREFIX=${NEWPREFIX//./$CENTREDOT}
  if [ "$PREFIX" != "$NEWPREFIX" ]; then
    mv "$FILENAME" "$NEWPREFIX.$SUFFIX"
  fi
done
return
}

# Function to create a bookmark entry
do_bookmark_entry () {
  BKMK_TEXT=${FILENAME%.*}
  BKMK_TEXT=${BKMK_TEXT:15}
  #  NB. Need to substitute 'space' for 'underscore' to be valid bookmark text.
  BKMK_TEXT=${BKMK_TEXT//_/ }
  # Create a unique anchor point based on date and time prepended to filename
  BKMK_ANCHOR=${FILENAME:0:14}
  echo "\pdfbookmark["$BKMK_LEVEL"]{"$BKMK_TEXT"}{"$BKMK_ANCHOR"}" >> $OUTFILE
return
}

# Function to add all pdf files in current directory to the output file
add_pdf_files () {
for FILENAME in *.[Pp][Dd][Ff] ; do
  do_bookmark_entry
  echo "\includepdf[pages=-,fitpaper]{"$PWD"/"$FILENAME"}" >> $OUTFILE
done
return
}

echo "\documentclass{article}" > $OUTFILE
echo "\usepackage{pdfpages}" >> $OUTFILE
echo "\usepackage[bookmarks,bookmarksopen,bookmarksopenlevel=1,pdfpagelayout={SinglePage},pdfview={Fit}]{hyperref}" >> $OUTFILE

echo "\begin{document}" >> $OUTFILE
echo "\pdfbookmark["$BKMK_LEVEL"]{"$BKMK_TEXT"}{"$BKMK_ANCHOR"}" >> $OUTFILE

sanitise_filenames
(( BKMK_LEVEL++ ))
for FNAME in * ; do
  if [ -d $FNAME ]; then
    FILENAME=$FNAME
    do_bookmark_entry
    cd $FNAME
    (( BKMK_LEVEL++ ))
    sanitise_filenames
    add_pdf_files
    (( BKMK_LEVEL-- ))
    cd ..
  elif [ -f $FNAME ] && [[ ${FNAME##*.} == [Pp][Dd][Ff] ]] ; then
    FILENAME=$FNAME
    do_bookmark_entry
    echo "\includepdf[pages=-,fitpaper]{"$PWD"/"$FILENAME"}" >> $OUTFILE
  fi
done

echo "\end{document}" >> $OUTFILE

# Now process the .tex file
# Do a second run so that bookmark entries are resolved
pdflatex $OUTFILE; pdflatex $OUTFILE
wait

# Rename the created .pdf file
mv ${OUTFILE%.*}.pdf $(date +%Y%m%d%H%M%S)_All.pdf

# Cleanup files left by pdflatex processing
rm ${OUTFILE%.*}*

# Cleanup trigger file if this script has been started from watch_for_combine_start.sh script
if [ -f combine.start ]; then rm combine.start; fi

# Remove this script
rm $0

As a convenience, the user could also have a second background process running the script (watch_for_combine_start.sh) shown below. This script watches for the creation of a 'trigger' file (combine.start) in the directory tree and then executes the 'combine.sh' script shown above. This is useful in a network environment, so that a Windows user manipulating the files via a Samba share can create the combined PDF file without having to log in to the Slackware Linux server.

#!/bin/sh

# Script to watch for creation of a trigger file in a directory tree.
#  This event causes another script to be copied into the tree subdirectory and run.
#  The tree subdirectory name is passed as a parameter.
#
# This script should never exit

WATCH_DIR=/home/xx/Desktop/PDF_Files
TRIGGER_FILE=combine.start
SCRIPT_FILE=/home/xx/pdf_printing/combine.sh

inotifywait  -mqr --timefmt '%Y%m%d %H%M%S' --format '%T %w %f' \
  -e close_write $WATCH_DIR \
  | while read date time path file; do
      if [ ${file} == $TRIGGER_FILE ]; then
        cp $SCRIPT_FILE ${path}
        sh ${path}/$(basename $SCRIPT_FILE) ${path} > /dev/null &
      fi
    done