#!/usr/bin/python ################################################################# ## This script copies a postscript file from input to output, ## adding a rubber-stamp-like overlay showing the text specified ## in the command-line arguments. ## ## Copyright (c) 2006 David LaRose, dlr@cs.cmu.edu ## All rights reserved. ## ## Permission is granted to use this software for any ethical ## purpose. ## ## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND ## CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES. ################################################################# import string import sys import optparse ## Define a static strings for usage, etc. s_version = '%prog 1.1' s_usage = """%prog [options] Copies a postscript file from input to output, adding a rubber-stamp-like overlay showing the specified text. If outputFile is omitted, or is set to the single character '-', then output will be written to stdout. If both outputFile and inputFile are omitted, or if inputFile is set to the single character '-', then input will be read from stdin. Use the option --help to see a list of available options.""" ## Define static strings containing postscript code to draw the ## rubber stamp image s_introComment = "% ========= Beginning of code inserted by psStamp =========" s_outroComment = "% ========= End of code inserted by psStamp =========" s_prologString = """% operator dlr_textBoundingBox: string dlr_textBoundingBox llx lly urx ury % Returns the lower-left and upper-right corners of the user-coordinate % bounding box of the stacked string, assuming it will be rendered with % the current font settings. /dlr_textBoundingBox { gsave % Save graphics state. % Align with device coordinates so that pathbbox will work as expected. [1 0 0 1 0 0] setmatrix % Pretend we're going to draw the stacked text. newpath 0 0 moveto % Start drawing at origin. % Instead of drawing, just calculate its bounding box. true charpath % Convert string on stack to path. flattenpath pathbbox % Get bounding box for the stacked path. grestore } def % operator dlr_stampOutline: string dlr_stampOutline - % Draws an approximately rectangular stroke arouund the area that the % stacked string will render to. Many rubber stamps have a border % which surrounds the entire stamp, and this routine simulates that % border. /dlr_stampOutline { % Make a dict for local variables, and push it on the dictionary stack. 6 dict begin % Get the bounding box of the text in user coords. dlr_textBoundingBox % Capture the results of dlr_textBoundingBox into local variables. /bbUpperRightY exch def /bbUpperRightX exch def /bbLowerLeftY exch def /bbLowerLeftX exch def % The bounding box is right on the edges of the letters. % Calculate 10% of the height of the bounding box, so that we % can draw a box that's comfortably larger than the printed string. /bboxHeight bbUpperRightY bbLowerLeftY sub def /bufferSize bboxHeight 0.20 mul def % Modify the bounding box corners. /upperRightY bbUpperRightY bufferSize add def /upperRightX bbUpperRightX bufferSize add def /lowerLeftY bbLowerLeftY bufferSize sub def /lowerLeftX bbLowerLeftX bufferSize sub def % % Draw bounding box. % newpath % lowerLeftX lowerLeftY moveto % upperRightX lowerLeftY lineto % upperRightX upperRightY lineto % lowerLeftX upperRightY lineto % % lowerLeftX lowerLeftY lineto % closepath % stroke % Draw bounding box, with nice rounded corners. newpath lowerLeftX bbLowerLeftY moveto lowerLeftX lowerLeftY lowerLeftX lowerLeftY bbLowerLeftX lowerLeftY curveto bbUpperRightX lowerLeftY lineto upperRightX lowerLeftY upperRightX lowerLeftY upperRightX bbLowerLeftY curveto upperRightX bbUpperRightY lineto upperRightX upperRightY upperRightX upperRightY bbUpperRightX upperRightY curveto bbLowerLeftX upperRightY lineto lowerLeftX upperRightY lowerLeftX upperRightY lowerLeftX bbUpperRightY curveto closepath stroke % Pop our local dict off the stack end } def % operator dlr_stamp: string angle xcoord ycoord stamp - % Draws the string in a way that looks like a rubber stamp % at xcoord, ycoord, with the image rotated by angle degrees. /dlr_stamp { gsave % Save graphics state. % Move to the position specified on stack. translate % Rotate by the angle specified on stack. rotate % Copy the input string, since we'll use it twice below. dup dlr_stampOutline % Now draw an outline of the text newpath 0 0 moveto % Start drawing at origin. true charpath % Convert string on stack to path. stroke % And stroke it. grestore } def """ s_showpageString0 = """ /dlr_savedShowpage /showpage load def /dlr_StampDelivered 0 def /showpage { dlr_StampDelivered 0 eq { /dlr_StampDelivered 1 def gsave initgraphics """ s_stampLinesComment = " % dlr_stamp commands go here" s_showpageString1 = """ grestore } if dlr_savedShowpage } def """ ## Here's a convenience function to help with copying lines from input ## to output. def copyUntil(inputFile, outputFile, markerString): while 1: inputLine = inputFile.readline() if inputLine == '': return False # end if outputFile.write(inputLine) if (string.find(inputLine, markerString) != -1): return True # end if # end while # end def ## Now the main program. if __name__ == '__main__': ## Define command line options. argParser = optparse.OptionParser(version=s_version, usage=s_usage) argParser.add_option( '-c', '--color', action='store', type='string', dest='colorString', default='0.65/0.1/0.25', metavar='', help=('RGB specification for stamp ink color. This must have the form ' + '//, where each number is in the range ' + '[0.0 - 1.0]')) argParser.add_option( '-f', '--font', action='store', type='string', # dest='font', default='BitstreamVeraSans-Bold-ISOLatin1', dest='font', default='Sans-Bold', metavar='', help=('Font in which to render text. Note that this must be a valid ' + 'postscript font. We do not check the validity of this argument')) argParser.add_option( '-r', '--rotation', action='store', type='float', dest='rotation', default=-7.5, metavar='[Num]', help='Rotation angle of stamp in degrees counterclockwise') argParser.add_option( '-s', '--fontsize', action='store', type='int', dest='fontsize', default=35, metavar='[Num]', help='Font size in points') argParser.add_option( '-w', '--linewidth', action='store', type='float', dest='linewidth', default=1.5, metavar='[Num]', help='line width to use for rendering') argParser.add_option( '-x', '--xposition', action='store', type='float', dest='xPosition', default=2.5, metavar='[Num]', help='X position of stamp in inches from bottom left corner') argParser.add_option( '-y', '--yposition', action='store', type='float', dest='yPosition', default=1.5, metavar='[Num]', help='Y position of stamp in inches from bottom left corner') ## Parse command line. (options, args) = argParser.parse_args() if len(args) != 3: print argParser.get_usage() sys.exit(65) # end if stampText = args[0] inputFileName = args[1] outputFileName = args[2] ## Sort out the RGB color try: rgbColor = map(float, string.split(options.colorString, '/')) if len(rgbColor) != 3: raise ValueError # end if except: sys.stderr.write('Error: Invalid color string: ' + options.colorString + '\n') sys.exit(65) # end try ## Open files if necessary. if inputFileName == '-': inputFile = sys.stdin else: inputFile = open(inputFileName, 'r') # end if if outputFileName == '-': outputFile = sys.stdout else: outputFile = open(outputFileName, 'w') # end if ## Format up the PS commands to print the stamp. Note that PS points ## are 72 per inch. fontAndColorCommand = ( """ %% Set up font, line color, and line width. /%s findfont %d scalefont setfont %f %f %f setrgbcolor %f setlinewidth""" % (options.font, options.fontsize, rgbColor[0], rgbColor[1], rgbColor[2], options.linewidth)) stampCommand = ( """ (%s) %f %f %f dlr_stamp""" % (stampText, options.rotation, 72 * options.xPosition, 72 * options.yPosition)) ## The file parsing is really like a little state machine, but it's ## simple enough that we just implement the states explicitly here. ## State 1: Waiting for prolog copyUntil(inputFile, outputFile, '%%BeginProlog') ## States 2 & 3: Checking to see if our function defs have already ## been inserted in this file, and inserting them if not. If ## they're already there, we simply add our new commands in the ## middle of the existing code. inputLine = inputFile.readline() if string.find(inputLine, s_introComment) == -1: outputFile.write(s_introComment + '\n\n') outputFile.write(s_prologString) outputFile.write(s_showpageString0) outputFile.write(s_stampLinesComment + '\n') outputFile.write(fontAndColorCommand + '\n') outputFile.write(stampCommand + '\n') outputFile.write(s_showpageString1) outputFile.write('\n' + s_outroComment + '\n\n') else: outputFile.write(inputLine) copyUntil(inputFile, outputFile, s_stampLinesComment) outputFile.write(fontAndColorCommand + '\n') outputFile.write(stampCommand + '\n') copyUntil(inputFile, outputFile, s_outroComment) # end if ## State 4: Copy the rest of the file. outputFile.write(inputFile.read()) ## Clean up. outputFile.close() inputFile.close() sys.exit(0) # end if