Automator Watermark PDF Workflow
Seems with every major upgrade, Apple breaks the Automator Watermark PDF Workflow.
Can someone test this workflow in Yosemite so I know if the problem I'm having is with OS X 10.10.
OS X Yosemite (10.10)
Seems with every major upgrade, Apple breaks the Automator Watermark PDF Workflow.
Can someone test this workflow in Yosemite so I know if the problem I'm having is with OS X 10.10.
OS X Yosemite (10.10)
Hello
Under 10.6.8, Watermark PDF Documents.action/Contents/Resources/tool.py works happily without any errors when invoked as follows with a.pdf and watermark.png in ~/desktop/test/ :
#!/bin/bash py='/System/Library/Automator/Watermark PDF Documents.action/Contents/Resources/tool.py' cd ~/desktop/test || exit args=( --input a.pdf --output a_wm.pdf --verbose --over --xOffset 0.0 --yOffset -150.0 --angle 300.0 --scale 0.3 --opacity 0.1 watermark.png ) "$py" "${args[@]}"
If the said error – ValueError: depythonifying 'pointer', got 'str' – is raised at the line:
provider = CGDataProviderCreateWithFilename(imagePath)
I'd think it is because imagePath is not a C string pointer which the function expects but a CFStringRef or something which is implicitly converted from python string. Since I don't get this error with pyobjc 2.2b3 & python 2.6 under 10.6.8, it is caused by something introduced in later versions.
Anyway, the statement in question may be replaced with the following statements if it helps:
url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, imagePath, len(imagePath), False) provider = CGDataProviderCreateWithURL(url)
I modified the tool.py with these changes along with other minor fixes and run it successfully under 10.6.8. I'm not sure at all whether it works under later OSes as well. And even if it does, editing tool.py will require the Automator action to be re-codesigned.
Here's the revised tool.py.
#!/usr/bin/python # Watermark each page in a PDF document import sys #, os import getopt import math from Quartz.CoreGraphics import * from Quartz.ImageIO import * def drawWatermark(ctx, image, xOffset, yOffset, angle, scale, opacity): if image: imageWidth = CGImageGetWidth(image) imageHeight = CGImageGetHeight(image) imageBox = CGRectMake(0, 0, imageWidth, imageHeight) CGContextSaveGState(ctx) CGContextSetAlpha(ctx, opacity) CGContextTranslateCTM(ctx, xOffset, yOffset) CGContextScaleCTM(ctx, scale, scale) CGContextTranslateCTM(ctx, imageWidth / 2, imageHeight / 2) CGContextRotateCTM(ctx, angle * math.pi / 180) CGContextTranslateCTM(ctx, -imageWidth / 2, -imageHeight / 2) CGContextDrawImage(ctx, imageBox, image) CGContextRestoreGState(ctx) def createImage(imagePath): image = None # provider = CGDataProviderCreateWithFilename(imagePath) # FIXED: replaced by the following CGDataProviderCreateWithURL() url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, imagePath, len(imagePath), False) provider = CGDataProviderCreateWithURL(url) if provider: imageSrc = CGImageSourceCreateWithDataProvider(provider, None) if imageSrc: image = CGImageSourceCreateImageAtIndex(imageSrc, 0, None) if not image: print "Cannot import the image from file %s" % imagePath return image def watermark(inputFile, watermarkFiles, outputFile, under, xOffset, yOffset, angle, scale, opacity, verbose): images = map(createImage, watermarkFiles) ctx = CGPDFContextCreateWithURL(CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, outputFile, len(outputFile), False), None, None) if ctx: pdf = CGPDFDocumentCreateWithURL(CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, inputFile, len(inputFile), False)) if pdf: for i in range(1, CGPDFDocumentGetNumberOfPages(pdf) + 1): image = images[i % len(images) - 1] page = CGPDFDocumentGetPage(pdf, i) if page: mediaBox = CGPDFPageGetBoxRect(page, kCGPDFMediaBox) if CGRectIsEmpty(mediaBox): mediaBox = None CGContextBeginPage(ctx, mediaBox) if under: drawWatermark(ctx, image, xOffset, yOffset, angle, scale, opacity) CGContextDrawPDFPage(ctx, page) if not under: drawWatermark(ctx, image, xOffset, yOffset, angle, scale, opacity) CGContextEndPage(ctx) del pdf CGPDFContextClose(ctx) del ctx def main(argv): verbose = False readFilename = None writeFilename = None under = False xOffset = 0.0 # FIXED: changed to float value yOffset = 0.0 # FIXED: changed to float value angle = 0.0 # FIXED: changed to float value scale = 1.0 # FIXED: added opacity = 1.0 # Parse the command line options try: options, args = getopt.getopt(argv, "vutx:y:a:p:s:i:o:", ["verbose", "under", "over", "xOffset=", "yOffset=", "angle=", "opacity=", "scale=", "input=", "output=", ]) except getopt.GetoptError: usage() sys.exit(2) for option, arg in options: print option, arg if option in ("-i", "--input") : if verbose: print "Reading pages from %s." % (arg) readFilename = arg elif option in ("-o", "--output") : if verbose: print "Setting %s as the output." % (arg) writeFilename = arg elif option in ("-v", "--verbose") : print "Verbose mode enabled." verbose = True elif option in ("-u", "--under"): print "watermark under PDF" under = True elif option in ("-t", "--over"): # FIXED: changed to "-t" from "t" print "watermark over PDF" under = False elif option in ("-x", "--xOffset"): xOffset = float(arg) elif option in ("-y", "--yOffset"): yOffset = float(arg) elif option in ("-a", "--angle"): angle = -float(arg) elif option in ("-s", "--scale"): scale = float(arg) elif option in ("-p", "--opacity"): opacity = float(arg) else: print "Unknown argument: %s" % (option) if (len(args) > 0): watermark(readFilename, args, writeFilename, under, xOffset, yOffset, angle, scale, opacity, verbose); else: shutil.copyfile(readFilename, writeFilename); def usage(): print "Usage: watermark --input <file> --output <file> <watermark files>..." if __name__ == "__main__": print sys.argv main(sys.argv[1:])
All the best,
H
Hello
Under 10.6.8, Watermark PDF Documents.action/Contents/Resources/tool.py works happily without any errors when invoked as follows with a.pdf and watermark.png in ~/desktop/test/ :
#!/bin/bash py='/System/Library/Automator/Watermark PDF Documents.action/Contents/Resources/tool.py' cd ~/desktop/test || exit args=( --input a.pdf --output a_wm.pdf --verbose --over --xOffset 0.0 --yOffset -150.0 --angle 300.0 --scale 0.3 --opacity 0.1 watermark.png ) "$py" "${args[@]}"
If the said error – ValueError: depythonifying 'pointer', got 'str' – is raised at the line:
provider = CGDataProviderCreateWithFilename(imagePath)
I'd think it is because imagePath is not a C string pointer which the function expects but a CFStringRef or something which is implicitly converted from python string. Since I don't get this error with pyobjc 2.2b3 & python 2.6 under 10.6.8, it is caused by something introduced in later versions.
Anyway, the statement in question may be replaced with the following statements if it helps:
url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, imagePath, len(imagePath), False) provider = CGDataProviderCreateWithURL(url)
I modified the tool.py with these changes along with other minor fixes and run it successfully under 10.6.8. I'm not sure at all whether it works under later OSes as well. And even if it does, editing tool.py will require the Automator action to be re-codesigned.
Here's the revised tool.py.
#!/usr/bin/python # Watermark each page in a PDF document import sys #, os import getopt import math from Quartz.CoreGraphics import * from Quartz.ImageIO import * def drawWatermark(ctx, image, xOffset, yOffset, angle, scale, opacity): if image: imageWidth = CGImageGetWidth(image) imageHeight = CGImageGetHeight(image) imageBox = CGRectMake(0, 0, imageWidth, imageHeight) CGContextSaveGState(ctx) CGContextSetAlpha(ctx, opacity) CGContextTranslateCTM(ctx, xOffset, yOffset) CGContextScaleCTM(ctx, scale, scale) CGContextTranslateCTM(ctx, imageWidth / 2, imageHeight / 2) CGContextRotateCTM(ctx, angle * math.pi / 180) CGContextTranslateCTM(ctx, -imageWidth / 2, -imageHeight / 2) CGContextDrawImage(ctx, imageBox, image) CGContextRestoreGState(ctx) def createImage(imagePath): image = None # provider = CGDataProviderCreateWithFilename(imagePath) # FIXED: replaced by the following CGDataProviderCreateWithURL() url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, imagePath, len(imagePath), False) provider = CGDataProviderCreateWithURL(url) if provider: imageSrc = CGImageSourceCreateWithDataProvider(provider, None) if imageSrc: image = CGImageSourceCreateImageAtIndex(imageSrc, 0, None) if not image: print "Cannot import the image from file %s" % imagePath return image def watermark(inputFile, watermarkFiles, outputFile, under, xOffset, yOffset, angle, scale, opacity, verbose): images = map(createImage, watermarkFiles) ctx = CGPDFContextCreateWithURL(CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, outputFile, len(outputFile), False), None, None) if ctx: pdf = CGPDFDocumentCreateWithURL(CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, inputFile, len(inputFile), False)) if pdf: for i in range(1, CGPDFDocumentGetNumberOfPages(pdf) + 1): image = images[i % len(images) - 1] page = CGPDFDocumentGetPage(pdf, i) if page: mediaBox = CGPDFPageGetBoxRect(page, kCGPDFMediaBox) if CGRectIsEmpty(mediaBox): mediaBox = None CGContextBeginPage(ctx, mediaBox) if under: drawWatermark(ctx, image, xOffset, yOffset, angle, scale, opacity) CGContextDrawPDFPage(ctx, page) if not under: drawWatermark(ctx, image, xOffset, yOffset, angle, scale, opacity) CGContextEndPage(ctx) del pdf CGPDFContextClose(ctx) del ctx def main(argv): verbose = False readFilename = None writeFilename = None under = False xOffset = 0.0 # FIXED: changed to float value yOffset = 0.0 # FIXED: changed to float value angle = 0.0 # FIXED: changed to float value scale = 1.0 # FIXED: added opacity = 1.0 # Parse the command line options try: options, args = getopt.getopt(argv, "vutx:y:a:p:s:i:o:", ["verbose", "under", "over", "xOffset=", "yOffset=", "angle=", "opacity=", "scale=", "input=", "output=", ]) except getopt.GetoptError: usage() sys.exit(2) for option, arg in options: print option, arg if option in ("-i", "--input") : if verbose: print "Reading pages from %s." % (arg) readFilename = arg elif option in ("-o", "--output") : if verbose: print "Setting %s as the output." % (arg) writeFilename = arg elif option in ("-v", "--verbose") : print "Verbose mode enabled." verbose = True elif option in ("-u", "--under"): print "watermark under PDF" under = True elif option in ("-t", "--over"): # FIXED: changed to "-t" from "t" print "watermark over PDF" under = False elif option in ("-x", "--xOffset"): xOffset = float(arg) elif option in ("-y", "--yOffset"): yOffset = float(arg) elif option in ("-a", "--angle"): angle = -float(arg) elif option in ("-s", "--scale"): scale = float(arg) elif option in ("-p", "--opacity"): opacity = float(arg) else: print "Unknown argument: %s" % (option) if (len(args) > 0): watermark(readFilename, args, writeFilename, under, xOffset, yOffset, angle, scale, opacity, verbose); else: shutil.copyfile(readFilename, writeFilename); def usage(): print "Usage: watermark --input <file> --output <file> <watermark files>..." if __name__ == "__main__": print sys.argv main(sys.argv[1:])
All the best,
H
Tony,
Got the PyObjC ConfidentialStamper.py watermark utility to work, with some tweaks. Now have 45 degree Draft stamp on every page. Created a new letter-size stamp at 300 dpi with 50% transparency, and transparent background. Used this as the PDF stamp file. Here is the result when run from the Terminal. Hopefully, this image will not disappear into the host vortex.
Here is the syntax clean PyObjC source code, with two new constants that produced this result.
#!/usr/bin/python
"""
Add a watermark to all pages in a PDF document
"""
import sys
#import math
import os
from Quartz import *
from Foundation import *
STAMPFILE = 'Draft45-50.pdf'
ANGLE = -270.0
def usage(name):
print >> sys.stderr, "Usage %s [inputfile]" % (name,)
class MyPDFData (object):
pdfDoc = None
mediaRect = None
# This is a simple function to create a CFURLRef from
# a path to a file. The path can be relative to the
# current directory or an absolute path.
def createURL(path):
return CFURLCreateFromFileSystemRepresentation(None, path,
len(path), False)
# For the supplied URL and media box, create a PDF context
# that creates a PDF file at that URL and uses supplied rect
# as its document media box.
def myCreatePDFContext(url, mediaBox):
dict = {}
dict[kCGPDFContextCreator] = "PDF Stamper Application"
pdfContext = CGPDFContextCreateWithURL(url, mediaBox, dict)
return pdfContext
# For a URL corresponding to an existing PDF document on disk,
# create a CGPDFDocumentRef and obtain the media box of the first
# page.
def myCreatePDFSourceDocument(url):
myPDFData = MyPDFData()
myPDFData.pdfDoc = CGPDFDocumentCreateWithURL(url)
if myPDFData.pdfDoc is not None:
# NOTE: the original code uses CGPDFDocumentGetMediaBox, but that
# API is deprecated and doesn't work in Leopard.
page = CGPDFDocumentGetPage(myPDFData.pdfDoc, 1)
myPDFData.mediaRect = CGPDFPageGetBoxRect(page, kCGPDFMediaBox)
# Make the media rect origin at 0,0.
myPDFData.mediaRect.origin.x = myPDFData.mediaRect.origin.y = 0.0
return myPDFData
# Draw the source PDF document into the context and then draw the stamp
# PDF document on top of it. When drawing the stamp on top, place it
# along the diagonal from the lower left corner to the upper right
# corner and center its media rect to the center of that diagonal.
def StampWithPDFDocument(context, sourcePDFDoc, stampFileDoc, stampMediaRect):
numPages = CGPDFDocumentGetNumberOfPages(sourcePDFDoc)
# Loop over document pages and stamp each one appropriately.
for i in range(1, numPages+1):
# Use the page rectangle of each page from the source to compute
# the destination media box for each page and the location of
# the stamp.
# NOTE: the original code uses CGPDFDocumentGetMediaBox, but that
# API is deprecated and doesn't work in Leopard.
page = CGPDFDocumentGetPage(sourcePDFDoc, i)
pageRect = CGPDFPageGetBoxRect(page, kCGPDFMediaBox)
CGContextBeginPage(context, pageRect)
CGContextSaveGState(context)
# Clip to the media box of the page.
CGContextClipToRect(context, pageRect)
# First draw the content of the source document.
CGContextDrawPDFDocument(context, pageRect, sourcePDFDoc, i)
# Translate to center of destination rect, that is the center of
# the media box of content to draw on top of.
CGContextTranslateCTM(context,
pageRect.size.width/2, pageRect.size.height/2)
# Compute angle of the diagonal across the destination page.
# angle = math.atan(pageRect.size.height/pageRect.size.width)
#angle = math.acos(pageRect.size.height/pageRect.size.width)
# Rotate by an amount so that drawn content goes along a diagonal
# axis across the page.
#CGContextRotateCTM(context, angle)
CGContextRotateCTM(context, ANGLE)
# Move the origin so that the media box of the PDF to stamp
# is centered around center point of destination.
CGContextTranslateCTM(context, -stampMediaRect.size.width/2,
-stampMediaRect.size.height/2)
# Now draw the document to stamp with on top of original content.
CGContextDrawPDFDocument(context, stampMediaRect, stampFileDoc, 1)
CGContextRestoreGState(context)
CGContextEndPage(context)
# From an input PDF document and a PDF document whose contents you
# want to draw on top of the other, create a new PDF document
# containing all the pages of the input document with the first page
# of the "stamping" overlayed.
def createStampedFileWithFile(inURL, stampURL, outURL):
sourceFileData = myCreatePDFSourceDocument(inURL)
if sourceFileData.pdfDoc is None:
print >> sys.stderr, "Can't create PDFDocumentRef for source input file!"
return
stampFileData = myCreatePDFSourceDocument(stampURL)
if stampFileData.pdfDoc is None:
CGPDFDocumentRelease(sourceFileData.pdfDoc)
print >>sys.stderr, "Can't create PDFDocumentRef for file to stamp with!"
return
pdfContext = myCreatePDFContext(outURL, sourceFileData.mediaRect)
if pdfContext is None:
print >>sys.stderr, "Can't create PDFContext for output file!"
return
StampWithPDFDocument(pdfContext, sourceFileData.pdfDoc,
stampFileData.pdfDoc, stampFileData.mediaRect)
def main(args=None):
if args is None:
args = sys.argv
suffix = ".watermarked.pdf"
stampFileName = os.path.join(os.path.dirname(__file__), STAMPFILE)
if len(args) != 2:
usage(args[0])
return 1
inputFileName = args[1]
outputFileName = os.path.splitext(inputFileName)[0] + suffix
inURL = createURL(inputFileName)
if inURL is None:
print >>sys.stderr, "Couldn't create URL for input file!"
return 1
outURL = createURL(outputFileName)
if outURL is None:
print >>sys.stderr, "Couldn't create URL for output file!"
return 1
stampURL = createURL(stampFileName)
if stampURL is None:
print >>sys.stderr, "Couldn't create URL for stamping file!"
return 1
createStampedFileWithFile(inURL, stampURL, outURL)
return 0
if __name__ == "__main__":
sys.exit(main())Hiroto,
In Terminal on 10.10, line 45 in watermark is bombing with 'TypeError: Object of type 'NoneType' has no len(). That is the only error, so far.
ctx = CGPDFContextCreateWithURL(CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, outputFile, len(outputFile), False), None, None)VikingOSX wrote:
Used Hiroto's updated Python script. Used the Waternark.png that you posted. Used your corrected command-line settings. There is no watermark produced, and the len(args) returns 1.
len(args) sounds like a bad flag — I may still have typo's in my corrected post . Make sure that there are two dashes before the flags for the long args (i.e. —output), or just use the shortcut (i.e, -o)
I now have this in an Automator Run Shell Script:
~/Library/Scripts/Watermark.py \
--under \
--xOffset 15 \
--yOffset 55 \
--angle 0 \
--scale 0.75 \
--opacity 0.5 \
--input "$1" \
--output "${1%.*} (COPY).${1##*.}" \
~/Pictures/Other\ Images/Watermark\(COPY\).pngIt works! here is how i did it.
I created a tool_2.py just to don't mess my native watermark with the code that you guys shared.
Then fixed some indentation erros, since python doesn't like miss indentations and was giving errors. here is my file:
Copy this file to your folder at /System/Library/Automator/Watermark\ PDF\ Documents.action/Contents/Resources
Now the watermark workflow so it acts as a print service and show in every document you print:
The file is here but you will need to create your own since it uses my local folders, its attached so you can open and copy things up
Basically it have this shell script:
for f in "$@"
do
/System/Library/Automator/Watermark\ PDF\ Documents.action/Contents/Resources/tool_2.py --input "$f" --output /Users/tiagorodrigues/Downloads/print.pdf --v --over --xOffset 0.0 --yOffset 0.0 --angle 0 --scale 0.24 --opacity 1 /Users/tiagorodrigues/Library/Mobile\ Documents/com\~apple\~CloudDocs/Deepend/Watermark/Smuky\ Papel\ de\ Carta\ V6.png
done
https://www.dropbox.com/sh/56ee5d0xqldjnyb/AAAsYUye18XVTGHiF_pM1LzDa?dl=0
Create a new automation for you with the correct folders and the desired watermark file and fix the scale value if you need to make it the correct size.
Hope you can do it this way.
Thanks.
This does work. When I then add the Set PDF Metadata and Get PDF Metadata and create image steps it does not work. If I run the Set PDF Metadata, Get PDF Metadata and create image as a separate workflow and then run your workflow, it does create a watermark. I will run them as separate steps but would be nice to have it in one workflow
In tool.py, add a scale = 100 assignment just after the angle variable in def main(argv) — that solves the variable referenced before assignment issue.
I use this syntax in the Terminal:
tool.py -i ~/input.pdf ~/watermark.png -o ~/output.pdf -t -a45 -p.5
I believe there is a PyObjC metadata issue with CGDataProviderCreateWithFilename(imagePath). It consistently bombs with ValueError: depythonifying 'pointer', got 'str'. I have read on stackoverflow, and elsewhere that there may be a problem with the PyObjC implementation defaulting to string, when it should be defaulting to a void pointer for this method. The cure is either on Apple's shoulders, or would require a significant reading investment in BridgeSupport metadata modifications. That ends my curiosity.
def createImage(imagePath):
image = None
provider = CGDataProviderCreateWithFilename(imagePath)
if provider:
imageSrc = CGImageSourceCreateWithDataProvider(provider, None)
if imageSrc:
image = CGImageSourceCreateImageAtIndex(imageSrc, 0, None)
if not image:
print "Cannot import the image from file %s" % imagePath
return image
Hello VikingOSX
As far as I can tell, I had to adjust the values of scale, xOffset and yOffset to let the watermark image (word "Draft") be overlaid correctly on the base pdf. I got these values by trial and error using preview pane of the Automator action (Watermark PDF Documents.action). By the way, the angle parameter is not in radian but degree.
My test watermark.png is generated from Pages 4 document using huge 144 point font, which surely reflects in the specific parameter values I have posted. So I think you may try other scales and offsets or simply try a colour-filled page for watermark, for scale and offsets shouldn't matter much for a whole page to be overlaid somehow.
Good luck,
H
EDIT: fixed typo
I may agree with you. 😠Can't get it to work as drag/drop application, with a rename after the watermark — or as a Printer plugin as below.
I create a Print plugin in Automator. Two actions:
Image: draft.jpg (will not allow you to select a draft.pdf with the chooser, but you can drag/drop one in the well - doesn't work either)
Print > PDF > myDraft
I just created an Automator watermark application. I used a .png (and .jpg) image for my watermark, and chose the Draw watermark over PDF option. A very low opacity (0.09) value was used. The workflow consisted of the following:
With this result:
The only observation is that the watermark was placed correctly, but behind the text, not over it as selected in the watermark action. Also, the final PDF was not overwritten as requested, but made a generational foo 2.pdf name. Neither an issue for me. Tested on OS X 10.11.2.
I also have mac mini with OS X 10.7.5. The Workflow works fine on 10.7.5, but on that system, when I pull the tool.py and run from the command line (on OS X 10.7.5) I get the "UnboundLocalError: local variable 'scale' referenced before assignment" that you're getting (and the same error on OS X 10.10), so Automator is doing something to set the "scale" variable before running tool.py.
tool.py on 10.7.5 and 10.10 are essentially the same (Apple fixed a t flag to -t in 10.10), so I don't think that the problem with 10.10 is the python script.
Copy Hiroto's script into a text editor, save as Watermark.py, then make executable (chmod u+x Watermark.py)
Copy the COPY.PNG file I posted above to the Desktop (right-click, "Save image to the Desktop)
Here's the command I used:
~/Desktop/Watermark.py —under —xOffset 15 --yOffset 55 --angle 0 --scale 0.75 --opacity 0.5 --input "Filename.PDF" —output "FilenameCOPY.PDF" ~/Desktop/Watermark(COPY).pngThat worked - thanks!
Unfortunately the direct call of tool.py didn't work. Neither did ./tool.py - it only worked with the full path given. Little bit annoying since I can't copy the whole directory with the .py & workflow script to another computer with a different user without re-editing that stuff.
My pleasure! And feel free to adapt it to your project, for it is trivial sample code with nothing special but series of framework function calls. Only that I have accommodated the code to tool.py located at (under OS X 10.6.8):
/System/Library/Automator/Watermark PDF Documents.action/Contents/Resources/tool.py
so if you feel uneasy or uncertain about using the code as is, arrange it as you see fit. 😉
Regarding PyObjC, it is part of standard installation of OS X 10.5 through 10.11 if I'm not mistaken. Under OS X 10.6.8, for instance, it is installed in the following directories.
/System/Library/Frameworks/Python.framework/Versions/2.6/Extras/lib/python/PyObjC /System/Library/Frameworks/Python.framework/Versions/2.5/Extras/lib/python/PyObjC
Regarding PyObjC performance, unfortunately, Python's importing PyObjC modules is blooming slow as compared to Ruby's importing RubyCocoa modules which is so quick and almost instant. In recent PyObjC's implementation, however, module importing performance can be improved reportedly either by specifying symbols selectively as VikingOSX described or by importing module in separate name space by means of 'as' qualifier of import statement, such as:
import Quartz.CoreGraphics as CG
and qualify symbols with CG prefixed such as:
CG.CGPDFContextCreateWithURL(url)
(Sadly, these techniques gain nothing under OS X 10.6.8 I'm using...)
Anyway, I'd employ the following variation for faster importing pyobjc modules, where I use single OSX name space for simplicity. Framework's symbols are well distinguished by framework-specific names, so there should be no problem, any more than 'from Framework import *'.
#!/usr/bin/python # coding: utf-8 import math import CoreFoundation as OSX import Quartz.CoreGraphics as OSX import CoreText as OSX def drawWatermarkText(ctx, line, xOffset, yOffset, angle, scale, opacity): # CGContextRef ctx # CTLineRef line # float xOffset, yOffset, angle ([degree]), scale, opacity ([0.0, 1.0]) if line: rect = OSX.CTLineGetImageBounds(line, ctx) imageWidth = rect.size.width imageHeight = rect.size.height OSX.CGContextSaveGState(ctx) OSX.CGContextSetAlpha(ctx, opacity) OSX.CGContextTranslateCTM(ctx, xOffset, yOffset) OSX.CGContextScaleCTM(ctx, scale, scale) OSX.CGContextTranslateCTM(ctx, imageWidth / 2, imageHeight / 2) OSX.CGContextRotateCTM(ctx, angle * math.pi / 180) OSX.CGContextTranslateCTM(ctx, -imageWidth / 2, -imageHeight / 2) OSX.CGContextSetTextPosition(ctx, 0.0, 0.0); OSX.CTLineDraw(line, ctx); OSX.CGContextRestoreGState(ctx) # parameters ifile = 'input.pdf' ofile = 'output.pdf' text = 'Watermark Sample' xOffset, yOffset, angle, scale, opacity = 0.0, 400.0, 60.0, 2.0, 0.2 # create CoreText line (CTLine) font = OSX.CTFontCreateWithName('Helvetica', 36.0, None) astr = OSX.CFAttributedStringCreate(OSX.kCFAllocatorDefault, text, { OSX.kCTFontAttributeName : font }) line = OSX.CTLineCreateWithAttributedString(astr) # create output pdf context ourl = OSX.CFURLCreateFromFileSystemRepresentation(OSX.kCFAllocatorDefault, ofile, len(ofile), False) ctx = OSX.CGPDFContextCreateWithURL(ourl, None, None) # create input pdf document iurl = OSX.CFURLCreateFromFileSystemRepresentation(OSX.kCFAllocatorDefault, ifile, len(ifile), False) pdf = OSX.CGPDFDocumentCreateWithURL(iurl) if pdf: for i in range(0, OSX.CGPDFDocumentGetNumberOfPages(pdf)): page = OSX.CGPDFDocumentGetPage(pdf, i + 1) if page: mbox = OSX.CGPDFPageGetBoxRect(page, OSX.kCGPDFMediaBox) if OSX.CGRectIsEmpty(mbox): mbox = None OSX.CGContextBeginPage(ctx, mbox) OSX.CGContextDrawPDFPage(ctx, page) # elementary test OSX.CGContextSetTextPosition(ctx, 10.0, 10.0) OSX.CTLineDraw(line, ctx) # using general function drawWatermarkText(ctx, line, xOffset, yOffset, angle, scale, opacity) OSX.CGContextEndPage(ctx) del pdf OSX.CGPDFContextClose(ctx) del ctx
All the best,
Hiroto
Automator Watermark PDF Workflow