Advertisement
Guest User

pdfBatesStamp.sh

a guest
Feb 17th, 2014
1,303
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Bash 32.67 KB | None | 0 0
  1. #!/bin/bash -
  2. #
  3. # "Bates-stamp" a PDF file with text (only; images aren't supported).  Uses
  4. # ghostscript (ps2pdf) and pdfdk.
  5. #
  6. # The output (Bates-stamped) file is put in the same directory, with "_BATES"
  7. # appended to its name, thusly:
  8. #     pdfBatesStamp.sh <FILE>.pdf ==> <FILE>_BATES.pdf
  9. #
  10. # Usage:
  11. #     pdfBatesStamp.sh <FILE>.pdf [PREFIX(def=<FILE>)] [STARTNUM(def=1)]
  12. #     pdfBatesStamp.sh <FILE>.pdf BATESCONFIG=<bates_config_filename>
  13. #
  14. # The <FILE>.pdf name must end in ".pdf".  You can make many more settings
  15. # inline below (you can also set PREFIX and STARTNUM there too if you want).
  16. # The first invocation format above is for the most common case (e.g., for legal
  17. # use).  In the second invocation format, the <bates_config_filename> file can
  18. # contain any of the inline user-settings (below, from PREFIX to EXTRAS,
  19. # inclusive), and they will overwrite the inline settings.  In this way, you can
  20. # write/store special config file settings for special PDFs, without needing to
  21. # modify the inline settings each time.  Runs at ~3 pages/sec.
  22. #
  23. # Copyright (C) 2014 Walter Tuvell.  Contributed to the Public Domain.
  24.  
  25.  
  26. ################################################################################
  27. #################### USERS: SPECIFY YOUR CONFIG PARAMS HERE ####################
  28. ################################################################################
  29.  
  30. # These default settings are tuned for an unspecified PREFIX and STARTNUM,
  31. # yielding a Bates stamp consisting of: the FILE's name; followed 6 spaces;
  32. # followed by the Bates/page number; in the SouthWest corner; with colors I
  33. # like.  You can specify any FONT installed on the machine, according to
  34. # fontconfig (see "fc-list" command); the 35 PostScript standard fonts always
  35. # work; if the specified FONT can't be found, defaults to PostScript Courier.
  36. # Recall that PostScript point = 1/72".
  37. PREFIX=                  # if present, this gets first priority; else command-line; else <FILE>name
  38. STARTNUM=                # if present, this gets first priority; else command-line; 1 {int>=0; 0 means no Bates/page number}
  39. SUFFIX=                  # follows the Bates/page number; you can include spaces if you want, even leading/trailing ones
  40. NUMPAD=0                 # pad Bates/page number with leading 0's {int>=0}
  41. SPACEPAD=6               # number of spaces between PREFIX and Bates/page number {int>=0}
  42. LOCATION=SouthWest       # NorthWest, North, NorthEast, SouthWest, South, SouthEast,
  43.                          #   WestNorth, West,  WestSouth, EastNorth, East,  EastSouth
  44. FONT=Helvetica-Bold      # "perfect" font for Bates-stamp (a PostScript standard font)
  45. FONTSIZE=10.0            # "perfect" size for Bates-stamp; pts {float>0.0}
  46. FONTCOLOR=128/0/0        # maroon = rgb(128,0,0) {int 0..255}
  47. MARGIN=21/21/21/21       # indent of stamp from page edge(s); pts left/bot/right/top {float>0.0}
  48. HIGHLIGHT=-1             # 0.0 means no highlight; <0.0 means fill; >0.0 means outline thickness {float}
  49. HLCOLOR=240/230/140      # (light)khaki = rgb(240,230,140) {int 0..255}
  50. HLTWEAK=0/0/0/0          # highlight tweaks for special cases; pts left/bot/right/top {float>=0.0}
  51. # The following EXTRAS define additional stamps (i.e., beyond the basic Bates
  52. # stamp, above).  If specified (i.e., its PREFIX is non-empty), these can have
  53. # their own settings; if empty, their settings inherit from the main stamp's
  54. # settings.  Each line of EXTRAS, if present at all, must contain all 9 entries
  55. # (some possibly empty); the locations of EXTRAS are not allowed to collide (for
  56. # example, NorthWest and WestNorth).  The maximum number of EXTRAS is 7, giving
  57. # a total of 8 printing positions, counting the main Bates stamp itself.  Note
  58. # that MARGIN is global (cannot be changed via EXTRAS).
  59. EXTRAS=(
  60.   # PREFIX  STARTNUM  SUFFIX  NUMPAD  SPACEPAD  LOCATION  FONT  FONTSIZE  FONTCOLOR  HIGHLIGHT  HLCOLOR  HLTWEAK
  61.     ""      ""        ""      ""      ""        ""        ""    ""        ""         ""         ""       ""
  62. )
  63.  
  64. # EXAMPLES:
  65.  
  66. # 1. Sampler.
  67. # In the following EXTRAS Sampler, note that ZapfChancery and Symbol don't
  68. # follow the "normal font rules", which causes "bounding box overflow" unless we
  69. # do some tweaking.  HLTWEAK specifies the additional amount by which to modify
  70. # the highlight box.  Positive makes the highlight box bigger in the direction
  71. # specified, negative makes it smaller.  Another reason to use HLTWEAK is if
  72. # want to fine-tune the way the highlight fits around the stamp, esp. if you use
  73. # an outline of large thickness (>> 1.0).  HLTWEAK only adjusts the highlight
  74. # box, not the stamp itself; the stamp remains in its regular place, at
  75. # distance(s) MARGIN from the closest edge(s) of the page.
  76. #EXTRAS=(
  77. #    "The NorthWest"  "11"  "nw"  "1"  "7"  "NorthWest"  "Times-Roman"                "18"    "0/255/0"      "-1"   "0/0/255"      ""
  78. #    "The North"      "22"  "n"   "2"  "6"  "North"      "ZapfChancery-MediumItalic"  "20"    "0/0/255"      "1.0"  "0/255/0"      "0/3/0/0"
  79. #    "The EastNorth"  "33"  "en"  "3"  "5"  "EastNorth"  "Bookman-Demi"               "16"    "255/0/0"      ""     ""             ""
  80. #    "The East"       "44"  "e"   "4"  "4"  "East"       "Palatino-BoldItalic"        "17.5"  "255/255/255"  "-1"   "0/0/0"        ""
  81. #    "The SouthEast"  "55"  "se"  "5"  "3"  "SouthEast"  "NewCenturySchlBk-Roman"     ""      ""             ""     ""             "0/21/21/0"
  82. #    "The South"      "66"  "s"   "6"  "2"  "South"      "Symbol"                     "28"    "255/255/0"    "0.1"  "128/128/128"  "0/4.5/0/0"
  83. #    "The West"       "77"  "w"   "7"  "1"  "West"       "Courier-Bold"               "24"    "0/255/255"    "5"    "224/224/224"  "3/3/3/3"
  84. #)
  85.  
  86. # 2. Legal.
  87. # In legal practice, lawyers typically use settings similar to the following:
  88. # PREFIX=                # specified on command-line; party name (no spaces, all-caps)
  89. # STARTNUM=              # specified on command-line; 1 initially (updated for subsequent filings)
  90. # SUFFIX=                # no suffix
  91. # NUMPAD=6               # anticipating < 1,000,000 pages
  92. # SPACEPAD=0             # no spaces in the Bates stamps
  93. # LOCATION=SouthEast     # lower-right corner is the most common
  94. # FONT=Times-Roman       # very conservative; one of the 35 PostScript standard fonts
  95. # FONTSIZE=12            # nice and big
  96. # FONTCOLOR=0/0/0        # black = rgb(0,0,0)
  97. # MARGIN=6/6/6/6         # small, trying to avoid overwriting anything on the page
  98. # HIGHLIGHT=0            # no highlight
  99. # HLCOLOR=255/255/255    # white = rgb(255,255,255), but irrelevant here because HIGHLIGHT=0
  100. # HLTWEAK=0/0/0/0        # no highlight tweaks needed or desired
  101. # EXTRAS=(
  102. #     "CONFIDENTIAL"   "SouthWest"   ""   ""   ""   ""   ""   ""
  103. # )
  104.  
  105.  
  106. ################################################################################
  107. ##################### USERS: DON'T WRITE BELOW THIS LINE!! #####################
  108. ################################################################################
  109.  
  110. # Process args.
  111.  
  112. [ $# -ge 1 -a $# -le 3 ] || { echo "*** USAGE: batesStamp.sh FILE.pdf [PREFIX(def=FILE)] [STARTNUM(def=1)]"; exit 99; }
  113.  
  114. INFILENAME="$1"
  115. [[ "$INFILENAME" =~ ^.*\.pdf$ ]] || { echo "*** Filename must end in \".pdf\""; exit 99; }
  116. [ -e "$INFILENAME" ]  || { echo "*** Can't find file \"$INFILENAME\""; exit 99; }
  117. INFILENAME=$(readlink -f "$INFILENAME")  # full/canonical filename
  118. INFILEDIR=$(dirname "$INFILENAME")
  119. INFILEBASE=$(basename "$INFILENAME")
  120. INFILECORE=$(echo "$INFILEBASE" | sed -e 's/^\(.*\)\.pdf$/\1/' -)
  121.  
  122. OUTFILENAME="$INFILEDIR/${INFILECORE}_BATES.pdf"
  123.  
  124. # Check for 1st/2nd invocation format.
  125. # In the 1st invocation format, the ranking of priority for PREFIX and STARTNUM is:
  126. #   1. Inline in this file (above).
  127. #   2. Command-line.
  128. #   3. Default: PREFIX=<FILE>name; STARTNUM=1.
  129. if [ $# -eq 3 ]; then
  130.     cmdlinePREFIX="$2"
  131.     cmdlineSTARTNUM="$3"
  132. elif [ $# -eq 2 ]; then
  133.     if [[ "$2" =~ BATESCONFIG=.* ]]; then
  134.         # 2nd invocation format.
  135.         BATESCONFIGFILE=$(echo "$2" | sed -e 's/BATESCONFIG=\(.*\)/\1/')
  136.         source "$BATESCONFIGFILE"
  137.     else
  138.         cmdlinePREFIX="$2"
  139.         cmdlineSTARTNUM=""
  140.     fi
  141. else
  142.     cmdlinePREFIX=""
  143.     cmdlineSTARTNUM=""
  144. fi
  145. PREFIX=${PREFIX:-$cmdlinePREFIX}
  146. PREFIX=${PREFIX:-$INFILECORE}
  147. [ -z "$PREFIX" ] && PREFIX="$INFILECORE"
  148. STARTNUM=${STARTNUM:-$cmdlineSTARTNUM}
  149. STARTNUM=${STARTNUM:-1}  # def = 1
  150. [ "$STARTNUM" -ge 0 ] || { echo "*** STARTNUM must be >= 0"; exit 99; }
  151.  
  152. # Check MARGIN.
  153. MARGIN=$(echo $MARGIN | tr "/" " ")
  154. [ $(echo "$MARGIN" | wc -w) -eq 4 ] || { echo "*** Bad MARGIN \"$MARGIN\""; exit 99; }
  155. for MARG in $MARGIN; do
  156.     [ $(echo "scale=0; ($MARG)>0.0" | bc -q) -eq 1 ] || { echo "*** Bad MARGIN \"$MARGIN\""; exit 99; }
  157. done
  158.  
  159. # Check SUFFIX.
  160. :  # nothing to check (SUFFIX can be any string)
  161.  
  162. # Check NUMPAD.
  163. [ "$NUMPAD" -ge 0 ] || { echo "*** Bad NUMPAD \"$NUMPAD\""; exit 99; }
  164.  
  165. # Check SPACEPAD, and append that number of space chars to PREFIX.
  166. [ "$SPACEPAD" -ge 0 ] || { echo "*** Bad SPACEPAD \"$SPACEPAD\""; exit 99; }
  167.  
  168. # Prepare to check location; preparation needed because we want
  169. # to keep track of "position", for collision detection, below.
  170. LOCATIONS="NorthWest North NorthEast SouthWest South SouthEast
  171.           WestNorth West  WestSouth EastNorth East  EastSouth"
  172. POSITIONS="UpperLeft Upper UpperRight Right LowerRight Lower LowerLeft Left"
  173. declare -A POSITIONS_USED  # Bash associative array
  174. for POS in $POSITIONS; do
  175.     POSITIONS_USED[$POS]=0
  176. done
  177. function positionUsed {
  178.     case $1 in
  179.         NorthWest|WestNorth) POSITIONS_USED[UpperLeft]=$((POSITIONS_USED[UpperLeft]+1)) ;;
  180.         North)               POSITIONS_USED[Upper]=$((POSITIONS_USED[Upper]+1)) ;;
  181.         NorthEast|EastNorth) POSITIONS_USED[UpperRight]=$((POSITIONS_USED[UpperRight]+1)) ;;
  182.         East)                POSITIONS_USED[Right]=$((POSITIONS_USED[Right]+1)) ;;
  183.         SouthEast|EastSouth) POSITIONS_USED[LowerRight]=$((POSITIONS_USED[LowerRight]+1)) ;;
  184.         South)               POSITIONS_USED[Lower]=$((POSITIONS_USED[Lower]+1)) ;;
  185.         SouthWest|WestSouth) POSITIONS_USED[LowerLeft]=$((POSITIONS_USED[LowerLeft]+1)) ;;
  186.         West)                POSITIONS_USED[Left]=$((POSITIONS_USED[Left]+1)) ;;
  187.         *)                   echo "*** NOT REACHED"; exit 99 ;;
  188.     esac
  189. }
  190.  
  191. # Now we can check location.
  192. OK=NOTOK
  193. for LOC in $LOCATIONS; do
  194.     [ "$LOCATION" == $LOC ] && { OK=OK; break; }
  195. done
  196. [ $OK == OK ] || { echo "*** Can't find LOCATION=\"$LOCATION\""; exit 99; }
  197. # And record the location's position.
  198. positionUsed $LOCATION
  199.  
  200. # No need to check font -- just default to Courier if specified font not found.
  201. # The PostScript standard fonts.
  202. #STDFONTS="
  203. #    AvantGarde-Book         AvantGarde-BookOblique        AvantGarde-Demi                AvantGarde-DemiOblique
  204. #    Bookman-Demi            Bookman-DemiItalic            Bookman-Light                  Bookman-LightItalic
  205. #    Courier                 Courier-Bold                  Courier-BoldOblique            Courier-Oblique
  206. #    Helvetica               Helvetica-Bold                Helvetica-BoldOblique          Helvetica-Oblique
  207. #    Helvetica-Narrow        Helvetica-Narrow-Bold         Helvetica-Narrow-BoldOblique   Helvetica-Narrow-Oblique
  208. #    NewCenturySchlBk-Bold   NewCenturySchlBk-BoldItalic   NewCenturySchlBk-Italic        NewCenturySchlBk-Roman
  209. #    Palatino-Bold           Palatino-BoldItalic           Palatino-Italic                Palatino-Roman
  210. #    Times-Bold              Times-BoldItalic              Times-Italic                   Times-Roman
  211. #    Symbol                  ZapfChancery-MediumItalic     ZapfDingbats
  212. #"
  213. #function checkFont {
  214. #    OK=NOTOK
  215. #    for F in $STDFONTS; do
  216. #        [ "$1" == $F ] && { OK=OK; break; }
  217. #    done
  218. #    [ $OK == OK ] || { echo "*** Bad FONT \"$1\" (not one of the 35 standard PostScript fonts)"; exit 99; }
  219. #}
  220. #checkFont "$FONT"
  221.  
  222.  
  223. # Check FONTSIZE.
  224. function checkFontsize {
  225.     [ $(echo "scale=9; ($1)>0.0" | bc -q) -eq 1 ] || { echo "*** Bad FONTSIZE \"$1\""; exit 99; }
  226. }
  227. checkFontsize "$FONTSIZE"
  228.  
  229. # Map RGB colors from 3-byte 0..255 standard) to PostScript 3-number 0.0..1.0.
  230. function mapRGB {
  231.     echo "scale=9; ($1)/255.0" | bc -q
  232. }
  233. function mapColor {
  234.     RGBCOLOR="$1"
  235.     TMPCOLOR=($(echo $RGBCOLOR | tr "/" " "))
  236.     [ $(echo ${TMPCOLOR[*]} | wc -w) -eq 3 ] || { echo "*** Malformed COLOR"; exit 99; }
  237.     for I in 0 1 2; do
  238.         [ 0 -le ${TMPCOLOR[I]} -a ${TMPCOLOR[I]} -le 255 ] || { echo "*** Bad COLOR \"${TMPCOLOR[I]}\""; exit 99; }
  239.     done
  240.     echo "$(mapRGB ${TMPCOLOR[0]}) $(mapRGB ${TMPCOLOR[1]}) $(mapRGB ${TMPCOLOR[2]})"
  241. }
  242.  
  243. FONTCOLOR="$(mapColor $FONTCOLOR)"
  244. HLCOLOR="$(mapColor $HLCOLOR)"
  245.  
  246. # Check HIGHLIGHT.
  247. function checkHighlight {
  248.     [ $(echo "scale=9; ($1)==0.0 || ($1)*($1)>0.0" | bc -q) -eq 1 ] || { echo "*** Bad HIGHLIGHT \"$1\""; exit 99; }
  249. }
  250. checkHighlight "$HIGHLIGHT"
  251.  
  252. # Separate HLTWEAK into 4 pieces.
  253. function mapHlTweak {
  254.     echo "$1" | tr "/" " "
  255. }
  256. HLTWEAK="$(mapHlTweak $HLTWEAK)"
  257.  
  258. # Handle EXTRAS.
  259. SIZEEXTRAS=${#EXTRAS[*]}
  260. SIZEENTRY=12
  261. [ $((SIZEEXTRAS % SIZEENTRY)) -eq 0 ] || { echo "*** Malformed EXTRAS"; exit 99; }
  262. NUMEXTRAS=$((SIZEEXTRAS / SIZEENTRY))
  263. [ $NUMEXTRAS -le 7 ] || { echo "*** Too many EXTRAS"; exit 99; }
  264. #NUMEXTRAS_1=$((NUMEXTRAS-1))
  265. for (( N=0; N<NUMEXTRAS; N+=1 )); do
  266.     # EXTRA PREFIX.
  267.     if [ -z "${EXTRAS[N*SIZEENTRY+0]}" ]; then
  268.         continue  # if PREFIX is empty, ignore other entries
  269.     fi
  270.     # EXTRA STARTNUM.
  271.     [ -z "${EXTRAS[N*SIZEENTRY+1]}" ] && EXTRAS[N*SIZEENTRY+1]=$STARTNUM  # inherit
  272.     [ -z "${EXTRAS[N*SIZEENTRY+1]}" ] && EXTRAS[N*SIZEENTRY+1]=1  # def = 1
  273.     [ "${EXTRAS[N*SIZEENTRY+1]}" -ge 0 ] || { echo "*** EXTRA STARTNUM must be >= 0"; exit 99; }
  274.     # EXTRA SUFFIX.
  275.     [ -z "${EXTRAS[N*SIZEENTRY+2]}" ] && EXTRAS[N*SIZEENTRY+2]="$SUFFIX"  # inherit
  276.     # EXTRA NUMPAD.
  277.     [ -z "${EXTRAS[N*SIZEENTRY+3]}" ] && EXTRAS[N*SIZEENTRY+3]=$NUMPAD  # inherit
  278.     [ "${EXTRAS[N*SIZEENTRY+3]}" -ge 0 ] || { echo "*** Bad EXTRA NUMPAD \"${EXTRAS[N*SIZEENTRY+3]}\""; exit 99; }
  279.     # EXTRA SPACEPAD.
  280.     [ -z "${EXTRAS[N*SIZEENTRY+4]}" ] && EXTRAS[N*SIZEENTRY+4]=$SPACEPAD  # inherit
  281.     [ "${EXTRAS[N*SIZEENTRY+4]}" -ge 0 ] || { echo "*** Bad EXTRA SPACEPAD \"${EXTRAS[N*SIZEENTRY+4]}\""; exit 99; }
  282.     # EXTRA LOCATION.  (No inheritance of LOCATION.)
  283.     OK=NOTOK
  284.     for LOC in $LOCATIONS; do
  285.         [ "${EXTRAS[N*SIZEENTRY+5]}" == $LOC ] && { OK=OK; break; }
  286.     done
  287.     [ $OK == OK ] || { echo "*** Can't find EXTRA LOCATION=\"${EXTRAS[N*SIZEENTRY+5]}\""; exit 99; }
  288.     positionUsed ${EXTRAS[N*SIZEENTRY+5]}
  289.     # EXTRA FONT.
  290.     if [ -z "${EXTRAS[N*SIZEENTRY+6]}" ]; then
  291.         EXTRAS[N*SIZEENTRY+6]="$FONT"  # inherit
  292.     else
  293.         : #checkFont "${EXTRAS[N*SIZEENTRY+6]}"
  294.     fi
  295.     # EXTRA FONTSIZE.
  296.     if [ -z "${EXTRAS[N*SIZEENTRY+7]}" ]; then
  297.         EXTRAS[N*SIZEENTRY+7]="$FONTSIZE"  # inherit
  298.     else
  299.         checkFontsize "${EXTRAS[N*SIZEENTRY+7]}"
  300.     fi
  301.     # EXTRA FONTCOLOR.
  302.     if [ -z "${EXTRAS[N*SIZEENTRY+8]}" ]; then
  303.         EXTRAS[N*SIZEENTRY+8]="$FONTCOLOR"  # inherit
  304.     else
  305.         EXTRAS[N*SIZEENTRY+8]="$(mapColor ${EXTRAS[N*SIZEENTRY+8]})"
  306.     fi
  307.     # EXTRA HIGHLIGHT.
  308.     if [ -z "${EXTRAS[N*SIZEENTRY+9]}" ]; then
  309.         EXTRAS[N*SIZEENTRY+9]="$HIGHLIGHT"  # inherit
  310.     else
  311.         checkHighlight "${EXTRAS[N*SIZEENTRY+9]}"
  312.     fi
  313.     # EXTRA HLCOLOR.
  314.     if [ -z "${EXTRAS[N*SIZEENTRY+10]}" ]; then
  315.         EXTRAS[N*SIZEENTRY+10]="$HLCOLOR"  # inherit
  316.     else
  317.         EXTRAS[N*SIZEENTRY+10]="$(mapColor ${EXTRAS[N*SIZEENTRY+10]})"
  318.     fi
  319.     # EXTRA HLTWEAK.
  320.     if [ -z "${EXTRAS[N*SIZEENTRY+11]}" ]; then
  321.         EXTRAS[N*SIZEENTRY+11]="$HLTWEAK"  # inherit
  322.     else
  323.         EXTRAS[N*SIZEENTRY+11]="$(mapHlTweak ${EXTRAS[N*SIZEENTRY+11]})"
  324.     fi
  325. done
  326.  
  327. # Check to make sure no 2 locations collide in any position.
  328. for POS in $POSITIONS; do
  329.     [ ${POSITIONS_USED[$POS]} -ge 2 ] && { echo "*** Location collision at $POS"; exit 99; }
  330. done
  331.  
  332. # PDFTK doesn't seem to have a way to discover page geometry (PDWIDTH & PGHEIGHT,
  333. # in PostScript points), so here are helper(s) to do the job.
  334. # We try both ImageMagick "identify" and Poppler "pdfindo" (randomly), if
  335. # they're installed; else we just do it by hand (by grep'ing the PDF file).
  336. function tryIDENTIFY {
  337.     IDENTIFY=($(identify $1 2>/dev/null))  # bash array
  338.     if [ $? -eq 0 ]; then
  339.         GEOMETRY=($(echo ${IDENTIFY[2]} | tr "x" " "))  # bash array -- $IDENTIFY[2] is of the form PGWIDTHxPGHEIGHT
  340.         PGWIDTH=${GEOMETRY[0]}; PGHEIGHT=${GEOMETRY[1]}
  341.         return 0
  342.     else
  343.         return 99
  344.     fi
  345. }
  346. function tryPDFINFO {
  347.     PDFINFO=($(pdfinfo -meta $1 | grep '^Page size:' |  \
  348.              sed -e 's/^Page size: *\([0-9][0-9]*\) x \([0-9][0-9]*\) pts.*$/\1 \2/'))  # bash array
  349.     if [ $? -eq 0 ]; then
  350.         PGWIDTH=${PDFINFO[0]}; PGHEIGHT=${PDFINFO[1]}
  351.         return 0
  352.     else
  353.         return 99
  354.     fi
  355. }
  356. function tryGREPSED {
  357.     MEDIABOXLINE=$(grep -a /MediaBox $1)
  358.     [ $(echo $MEDIABOXLINE | wc -l) -eq 1 ] || return 99
  359.     GREPSED=($(echo "$MEDIABOXLINE" |  \
  360.                sed -e 's#/.*/MediaBox\[ *\([\.0-9-]*\) *\([\.0-9-]*\) *\([\.0-9-]*\) *\([\.0-9-]*\).*#\1 \2 \3 \4#' |  \
  361.                sed -e 's#^<<##'))  # bash array
  362.     [ $? -eq 0 ] || return 99
  363.     PGWIDTH=$(echo "scale=9; (${GREPSED[2]})-(${GREPSED[0]})" | bc -q)
  364.     PGHEIGHT=$(echo "scale=9; (${GREPSED[3]})-(${GREPSED[1]})" | bc -q)
  365.     return 0
  366. }
  367. function getGEOMETRY {
  368.     PGWIDTH=-1; PGHEIGHT=-1
  369.     case $((RANDOM%2)) in
  370.         0) tryIDENTIFY $1
  371.            EXITCODE=$?
  372.            [ $EXITCODE -ne 0 ] && tryPDFINFO $1
  373.            EXITCODE=$?
  374.            ;;
  375.         1) tryPDFINFO $1
  376.            EXITCODE=$?
  377.            [ $EXITCODE -ne 0 ] && tryIDENTIFY $1
  378.            EXITCODE=$?
  379.            ;;
  380.         *) echo "*** NOT REACHED"; exit 99 ;;
  381.     esac
  382.     [ $EXITCODE -ne 0 ] && { tryGREPSED $1; EXITCODE=$?; }
  383.     [ $EXITCODE -ne 0 ] && { echo "*** Can't find page size"; exit 99; }
  384.     [ $(echo "scale=0; ($PGWIDTH)>0.0" | bc -q)  -eq 1 -a  \
  385.       $(echo "scale=0; ($PGHEIGHT)>0.0" | bc -q) -eq 1    ] ||  \
  386.         { echo "*** Can't find page size"; exit 99; }
  387. }
  388.  
  389. # Trivial helpers.
  390. function mult10 {
  391.     echo "scale=9; ($1)*10.0" | bc -q | sed -e 's/\..*$//'  # truncate/round
  392. }
  393. function pt2in {
  394.     echo "scale=3; ($1)/72.0" | bc -q
  395. }
  396. function fileSize {
  397.     wc -c "$1" | cut -d' ' -f1
  398. }
  399. function pctIncrease {
  400.     echo "scale = 9; (($2)/($1))*100.0" | bc -q |  sed -e 's/^\([0-9]*\.[0-9][0-9]\)[0-9]*$/\1/'  # truncate/round
  401. }
  402. function format3d {
  403.     LC_ALL= printf "%'.3d\n" "$1"
  404. }
  405. function format3f {
  406.     LC_ALL= printf "%'.3f\n" "$1"
  407. }
  408.  
  409. # Non-trivial helper: Create text of stamp.
  410. function makeSTAMP {
  411.     tmpPREFIX="$1"
  412.     [ -z "$tmpPREFIX" ] && { echo ""; return; }  # hack!
  413.     tmpSTARTNUM="$2"
  414.     tmpSUFFIX="$3"
  415.     tmpNUMPAD="$4"
  416.     tmpSPACEPAD="$5"
  417.     tmpNUM="$6"
  418.     for I in $(seq 1 $tmpSPACEPAD); do
  419.         tmpPREFIX+=" "
  420.     done
  421.     if [ $tmpSTARTNUM -eq 0 ]; then
  422.         tmpNUMFIX=""
  423.     else
  424.         tmpNUMFIX="$(LC_ALL=C printf %.${tmpNUMPAD}d $((tmpSTARTNUM+tmpNUM-1)))"
  425.     fi
  426.     echo "${tmpPREFIX}${tmpNUMFIX}${tmpSUFFIX}"
  427. }
  428.  
  429. # Do all work in temp dir.
  430. function cleanup {
  431.     rm -rf "$TEMPDIR"
  432. }
  433. trap cleanup EXIT
  434. ORIGDIR="$PWD"
  435. TEMPDIR=$(mktemp -p /tmp -d)
  436. #cp "$INFILENAME" $TEMPDIR/orig.pdf  # a known name, so we don't have name-collisions
  437. ln -s "$INFILENAME" $TEMPDIR/orig.pdf  # a known name, so we don't have name-collisions
  438. cd $TEMPDIR
  439.  
  440.  
  441. # Main loop.
  442. #
  443. # Since the pages of the original may be of different size/orientation, so
  444. # we must process each page individually.  To do that, we use pdftk burst.
  445. pdftk orig.pdf burst output burst%d.pdf
  446. [ $? -eq 0 ] || { echo "*** Can't do \"pdftk burst\""; exit 99; }
  447. #rm -f orig.pdf  -- don't bother, it'll be deleted later
  448. NUMPAGES=$(echo burst*.pdf | wc -w)
  449. # Alternatively, we could have done the following (but we need to do "pdftk burst" anyway):
  450. # NUMPAGES=$(pdftk orig.pdf dump_data output 2>/dev/null | grep NumberOfPages | cut -d" " -f 2)
  451.  
  452. INFILESIZE=$(fileSize $INFILENAME)
  453. echo "Input File = \"$INFILENAME\"  (origSize=$(format3d $INFILESIZE))"
  454.  
  455. # Now loop through each page.
  456. for (( NUM=1; NUM<=$NUMPAGES; NUM+=1 )); do
  457.  
  458.     # Get page's GEOMETRY (width & height, in pts).
  459.     # This is the only thing we use the individual/burst pages for.
  460.     getGEOMETRY burst$NUM.pdf  # this sets PGWIDTH & PGHEIGHT.
  461.  
  462.     # Cobble together STAMP.
  463.     STAMP=$(makeSTAMP "$PREFIX" $STARTNUM "$SUFFIX" $NUMPAD $SPACEPAD $NUM)
  464.     # Progress report, using VT100 terminal escape.
  465.     printf "\33[2K\r$NUM/$NUMPAGES"
  466.  
  467.     # The main trick: hand-coded PostScript program.
  468.     PSPROGRAM="
  469.  
  470.        %STACK: stamp(text) location margin[L|B|R|T] font fontsize fontcolor[R|G|B] highlight bgcolor[R|G|B] tweak[L|B|R|T]
  471.        /doStamp {
  472.            % Pick up 19 args from the stack.
  473.            /theTweakT     exch def
  474.            /theTweakR     exch def
  475.            /theTweakB     exch def
  476.            /theTweakL     exch def
  477.            /theBgColorB   exch def
  478.            /theBgColorG   exch def
  479.            /theBgColorR   exch def
  480.            /theHighlight  exch def
  481.            /theFontColorB exch def
  482.            /theFontColorG exch def
  483.            /theFontColorR exch def
  484.            /theFontSize   exch def
  485.            /theFont       exch def
  486.            /theMarginT    exch def
  487.            /theMarginR    exch def
  488.            /theMarginB    exch def
  489.            /theMarginL    exch def
  490.            /theLocation   exch def
  491.            /theStamp      exch def
  492.  
  493.            % Set up font for this stamp.
  494.            theFont findfont theFontSize scalefont setfont
  495.  
  496.            % Get string metrics.  Need TWO calculations to get it right (WTF)!!
  497.            /strHeightWidth {  %STACK: string => height width
  498.                % 1. Use stringwidth to get width, even with leading/trailing
  499.                % spaces (but this doesn't give correct height).
  500.                dup  %STACK: string string
  501.                stringwidth  %STACK: string width height
  502.                pop  %STACK: string width
  503.                % 2. Use well-known PostScript idiom to get height (but this
  504.                % doesn't give correct width with leading/trailing spaces).
  505.                exch  %STACK: width string
  506.                gsave
  507.                  newpath 0 0 moveto  % start at origin, for simplicity
  508.                  false charpath  flattenpath  pathbbox  % string on stack is consumed by this line
  509.                grestore  %STACK: width llx lly urx(~width) ury=height
  510.                4 2 roll  %STACK: width urx height llx lly
  511.                pop pop   %STACK: width urx height
  512.                2 1 roll  %STACK: width height urx
  513.                pop       %STACK: width height
  514.            } def
  515.            theStamp strHeightWidth
  516.            /stampHeight exch def  /stampWidth exch def
  517.  
  518.            % Set up corners of stamp in standard position (modified by location and margins, below).
  519.            /llx 0   def                 /lly 0   def                  % lower-left corner of stamp
  520.            /lrx llx stampWidth add def  /lry lly def                  % lower-right corner of stamp
  521.            /ulx llx def                 /uly lly stampHeight add def  % upper-left corner of stamp
  522.            /urx lrx def                 /ury uly def                  % upper-right corner of stamp
  523.  
  524.            gsave  % needed for multiple stamps (main Bates stamp plus the EXTRAS)
  525.  
  526.            % Translate lower-left corner, and rotate, according to LOCATION and MARGIN.
  527.            theLocation (NorthWest) eq { theMarginL
  528.                                         $PGHEIGHT stampHeight sub theMarginT sub
  529.                                         translate                                 } if
  530.            theLocation (North)     eq { $PGWIDTH 2.0 div stampWidth 2.0 div sub
  531.                                         $PGHEIGHT stampHeight sub theMarginT sub
  532.                                         translate                                 } if
  533.            theLocation (NorthEast) eq { $PGWIDTH stampWidth sub theMarginR sub
  534.                                         $PGHEIGHT stampHeight sub theMarginT sub
  535.                                         translate                                 } if
  536.            theLocation (SouthWest) eq { theMarginL
  537.                                         theMarginB
  538.                                         translate                                 } if
  539.            theLocation (South)     eq { $PGWIDTH 2.0 div stampWidth 2.0 div sub
  540.                                         theMarginB
  541.                                         translate                                 } if
  542.            theLocation (SouthEast) eq { $PGWIDTH stampWidth sub theMarginR sub
  543.                                         theMarginB
  544.                                         translate                                 } if
  545.            theLocation (WestNorth) eq { stampHeight theMarginL add
  546.                                         $PGHEIGHT stampWidth sub theMarginT sub
  547.                                         translate  90.0 rotate                    } if
  548.            theLocation (West)      eq { stampHeight theMarginL add
  549.                                         $PGHEIGHT 2.0 div stampWidth 2.0 div sub
  550.                                         translate  90.0 rotate                    } if
  551.            theLocation (WestSouth) eq { stampHeight theMarginL add
  552.                                         theMarginB
  553.                                         translate  90.0 rotate                    } if
  554.            theLocation (EastNorth) eq { $PGWIDTH stampHeight sub theMarginR sub
  555.                                         $PGHEIGHT stampWidth sub theMarginT sub
  556.                                         translate  90.0 rotate                    } if
  557.            theLocation (East)      eq { $PGWIDTH stampHeight sub theMarginR sub
  558.                                         $PGHEIGHT 2.0 div stampWidth 2.0 div sub
  559.                                         translate  90.0 rotate                    } if
  560.            theLocation (EastSouth) eq { $PGWIDTH stampHeight sub theMarginR sub
  561.                                         theMarginB
  562.                                         translate  90.0 rotate                    } if
  563.  
  564.            % Now that we're all set up, we can paint our stamp & highlight.
  565.  
  566.            % First draw the highlight.
  567.            theHighlight 0.0 ne {
  568.                % Define highlight metrics, to perfectly surround the text of the stamp, with
  569.                % default (1/6)*FONTSIZE bump-out for good fit (user can always tweak via HLTWEAK).
  570.                /hlBump theFontSize 6.0 div def
  571.                /llxH llx hlBump sub def  /llyH lly hlBump sub def  % lower-left corner of highlight
  572.                /lrxH lrx hlBump add def  /lryH lry hlBump sub def  % lower-right corner of highlight
  573.                /ulxH ulx hlBump sub def  /ulyH uly hlBump add def  % upper-left corner of highlight
  574.                /urxH urx hlBump add def  /uryH ury hlBump add def  % upper-right corner of highlight
  575.                % Modify highlight for tweaks.
  576.                /llxH llxH theTweakL sub def  /llyH llyH theTweakB sub def
  577.                /lrxH lrxH theTweakR add def  /lryH lryH theTweakB sub def
  578.                /ulxH ulxH theTweakL sub def  /ulyH ulyH theTweakT add def
  579.                /urxH urxH theTweakR add def  /uryH uryH theTweakT add def
  580.                theBgColorR theBgColorG theBgColorB  setrgbcolor
  581.                newpath  % define path for highlight fill/stroke
  582.                  llxH llyH moveto
  583.                  lrxH lryH lineto
  584.                  urxH uryH lineto
  585.                  ulxH ulyH lineto
  586.                  closepath
  587.                  theHighlight 0.0 lt {
  588.                      fill
  589.                  } {
  590.                      theHighlight setlinewidth  stroke
  591.                  } ifelse
  592.            } if
  593.  
  594.            % Then draw the text of the stamp on top of the highlight.
  595.            theFontColorR theFontColorG theFontColorB  setrgbcolor
  596.            llx lly moveto
  597.            theStamp show
  598.  
  599.            grestore  % needed for multiple stamps
  600.  
  601.        } def
  602.  
  603.        % Do the deed.
  604.        % Always do regular Bates-stamp.
  605.        true {
  606.            ($STAMP)
  607.            ($LOCATION) $MARGIN
  608.            ($FONT)     $FONTSIZE  $FONTCOLOR
  609.            $HIGHLIGHT  $HLCOLOR   $HLTWEAK
  610.            doStamp
  611.        } if
  612.        % Then do the EXTRAS, if any.
  613.        $NUMEXTRAS 1 ge (${EXTRAS[0*SIZEENTRY+0]}) () ne and {
  614.            ("$(makeSTAMP "${EXTRAS[0*SIZEENTRY+0]}" ${EXTRAS[0*SIZEENTRY+1]} "${EXTRAS[0*SIZEENTRY+2]}" ${EXTRAS[0*SIZEENTRY+3]} ${EXTRAS[0*SIZEENTRY+4]} $NUM)")
  615.            (${EXTRAS[0*SIZEENTRY+5]}) $MARGIN
  616.            (${EXTRAS[0*SIZEENTRY+6]}) ${EXTRAS[0*SIZEENTRY+7]}  ${EXTRAS[0*SIZEENTRY+8]}
  617.             ${EXTRAS[0*SIZEENTRY+9]}  ${EXTRAS[0*SIZEENTRY+10]} ${EXTRAS[0*SIZEENTRY+11]}
  618.            doStamp
  619.        } if
  620.        $NUMEXTRAS 2 ge (${EXTRAS[1*SIZEENTRY+0]}) () ne and {
  621.            ("$(makeSTAMP "${EXTRAS[1*SIZEENTRY+0]}" ${EXTRAS[1*SIZEENTRY+1]} "${EXTRAS[1*SIZEENTRY+2]}" ${EXTRAS[1*SIZEENTRY+3]} ${EXTRAS[1*SIZEENTRY+4]} $NUM)")
  622.            (${EXTRAS[1*SIZEENTRY+5]}) $MARGIN
  623.            (${EXTRAS[1*SIZEENTRY+6]}) ${EXTRAS[1*SIZEENTRY+7]}  ${EXTRAS[1*SIZEENTRY+8]}
  624.             ${EXTRAS[1*SIZEENTRY+9]}  ${EXTRAS[1*SIZEENTRY+10]} ${EXTRAS[1*SIZEENTRY+11]}
  625.            doStamp
  626.        } if
  627.        $NUMEXTRAS 3 ge (${EXTRAS[2*SIZEENTRY+0]}) () ne and {
  628.            ("$(makeSTAMP "${EXTRAS[2*SIZEENTRY+0]}" ${EXTRAS[2*SIZEENTRY+1]} "${EXTRAS[2*SIZEENTRY+2]}" ${EXTRAS[2*SIZEENTRY+3]} ${EXTRAS[2*SIZEENTRY+4]} $NUM)")
  629.            (${EXTRAS[2*SIZEENTRY+5]}) $MARGIN
  630.            (${EXTRAS[2*SIZEENTRY+6]}) ${EXTRAS[2*SIZEENTRY+7]}  ${EXTRAS[2*SIZEENTRY+8]}
  631.             ${EXTRAS[2*SIZEENTRY+9]}  ${EXTRAS[2*SIZEENTRY+10]} ${EXTRAS[2*SIZEENTRY+11]}
  632.            doStamp
  633.        } if
  634.        $NUMEXTRAS 4 ge (${EXTRAS[3*SIZEENTRY+0]}) () ne and {
  635.            ("$(makeSTAMP "${EXTRAS[3*SIZEENTRY+0]}" ${EXTRAS[3*SIZEENTRY+1]} "${EXTRAS[3*SIZEENTRY+2]}" ${EXTRAS[3*SIZEENTRY+3]} ${EXTRAS[3*SIZEENTRY+4]} $NUM)")
  636.            (${EXTRAS[3*SIZEENTRY+5]}) $MARGIN
  637.            (${EXTRAS[3*SIZEENTRY+6]}) ${EXTRAS[3*SIZEENTRY+7]}  ${EXTRAS[3*SIZEENTRY+8]}
  638.             ${EXTRAS[3*SIZEENTRY+9]}  ${EXTRAS[3*SIZEENTRY+10]} ${EXTRAS[3*SIZEENTRY+11]}
  639.            doStamp
  640.        } if
  641.        $NUMEXTRAS 5 ge (${EXTRAS[4*SIZEENTRY+0]}) () ne and {
  642.            ("$(makeSTAMP "${EXTRAS[4*SIZEENTRY+0]}" ${EXTRAS[4*SIZEENTRY+1]} "${EXTRAS[4*SIZEENTRY+2]}" ${EXTRAS[4*SIZEENTRY+3]} ${EXTRAS[4*SIZEENTRY+4]} $NUM)")
  643.            (${EXTRAS[4*SIZEENTRY+5]}) $MARGIN
  644.            (${EXTRAS[4*SIZEENTRY+6]}) ${EXTRAS[4*SIZEENTRY+7]}  ${EXTRAS[4*SIZEENTRY+8]}
  645.             ${EXTRAS[4*SIZEENTRY+9]}  ${EXTRAS[4*SIZEENTRY+10]} ${EXTRAS[4*SIZEENTRY+11]}
  646.            doStamp
  647.        } if
  648.        $NUMEXTRAS 6 ge (${EXTRAS[5*SIZEENTRY+0]}) () ne and {
  649.            ("$(makeSTAMP "${EXTRAS[5*SIZEENTRY+0]}" ${EXTRAS[5*SIZEENTRY+1]} "${EXTRAS[5*SIZEENTRY+2]}" ${EXTRAS[5*SIZEENTRY+3]} ${EXTRAS[5*SIZEENTRY+4]} $NUM)")
  650.            (${EXTRAS[5*SIZEENTRY+5]}) $MARGIN
  651.            (${EXTRAS[5*SIZEENTRY+6]}) ${EXTRAS[5*SIZEENTRY+7]}  ${EXTRAS[5*SIZEENTRY+8]}
  652.             ${EXTRAS[5*SIZEENTRY+9]}  ${EXTRAS[5*SIZEENTRY+10]} ${EXTRAS[5*SIZEENTRY+11]}
  653.            doStamp
  654.        } if
  655.        $NUMEXTRAS 7 ge (${EXTRAS[6*SIZEENTRY+0]}) () ne and {
  656.            ("$(makeSTAMP "${EXTRAS[6*SIZEENTRY+0]}" ${EXTRAS[6*SIZEENTRY+1]} "${EXTRAS[6*SIZEENTRY+2]}" ${EXTRAS[6*SIZEENTRY+3]} ${EXTRAS[6*SIZEENTRY+4]} $NUM)")
  657.            (${EXTRAS[6*SIZEENTRY+5]}) $MARGIN
  658.            (${EXTRAS[6*SIZEENTRY+6]}) ${EXTRAS[6*SIZEENTRY+7]}  ${EXTRAS[6*SIZEENTRY+8]}
  659.             ${EXTRAS[6*SIZEENTRY+9]}  ${EXTRAS[6*SIZEENTRY+10]} ${EXTRAS[6*SIZEENTRY+11]}
  660.            doStamp
  661.        } if
  662.  
  663.        %showpage  -- unnecessary
  664.  
  665.    "
  666.  
  667.     echo "$PSPROGRAM" |  \
  668.         ps2pdf -dBATCH -g$(mult10 $PGWIDTH)x$(mult10 $PGHEIGHT) - stamp$NUM.pdf
  669.  
  670. done
  671.  
  672. printf "\33[2K\r"  # clear the report line
  673.  
  674. # Final steps.
  675. STAMPLIST=$(ls -v stamp*.pdf)  # force correct ordering
  676. # Concatenate the stamp pages, to create one big multi-stamp PDF,
  677. # and pipe that to the PdfTk multi-stamp operation.
  678. pdftk $STAMPLIST cat output - |  \
  679.     pdftk orig.pdf multistamp - output "$OUTFILENAME"
  680. OUTFILESIZE=$(fileSize "$OUTFILENAME")
  681.  
  682. # Done!
  683. INCREASESIZE=$((OUTFILESIZE-INFILESIZE))
  684. PCTINCREASE=$(pctIncrease $INFILESIZE $OUTFILESIZE)
  685. echo "Output file = \"$OUTFILENAME\"  (batesSize=$(format3d $OUTFILESIZE); diffSizes=$(format3d ${INCREASESIZE})=${PCTINCREASE}%)"
  686.  
  687.  
  688. ################################################################################
  689. #################################### DONE! #####################################
  690. ################################################################################
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement