macosquartz-graphicspyobjc

Apply a Quartz filter while saving PDF under Mac OS X 10.6.3


Using Mac OS X API, I'm trying to save a PDF file with a Quartz filter applied, just like it is possible from the "Save As" dialog in the Preview application. So far I've written the following code (using Python and pyObjC, but it isn't important for me):

-- filter-pdf.py: begin

from Foundation import *
from Quartz import *
import objc

page_rect = CGRectMake (0, 0, 612, 792)
fdict = NSDictionary.dictionaryWithContentsOfFile_("/System/Library/Filters/Blue
\ Tone.qfilter")
in_pdf = CGPDFDocumentCreateWithProvider(CGDataProviderCreateWithFilename ("test
.pdf"))
url = CFURLCreateWithFileSystemPath(None, "test_out.pdf", kCFURLPOSIXPathStyle, 
False)
c = CGPDFContextCreateWithURL(url, page_rect, fdict)

np = CGPDFDocumentGetNumberOfPages(in_pdf)
for ip in range (1, np+1):
        page = CGPDFDocumentGetPage(in_pdf, ip)
        r = CGPDFPageGetBoxRect(page, kCGPDFMediaBox)
        CGContextBeginPage(c, r)
        CGContextDrawPDFPage(c, page)
        CGContextEndPage(c)

-- filter-pdf.py: end

Unfortunalte, the filter "Blue Tone" isn't applied, the output PDF looks exactly as the input PDF.

Question: what I missed? How to apply a filter?

Well, the documentation doesn't promise that such way of creating and using "fdict" should cause that the filter is applied. But I just rewritten (as far as I can) sample code /Developer/Examples/Quartz/Python/filter-pdf.py, which was distributed with older versions of Mac (meanwhile, this code doesn't work too):

----- filter-pdf-old.py: begin

from CoreGraphics import *
import sys, os, math, getopt, string

def usage ():
  print '''
usage: python filter-pdf.py FILTER INPUT-PDF OUTPUT-PDF

Apply a ColorSync Filter to a PDF document.
'''

def main ():

  page_rect = CGRectMake (0, 0, 612, 792)

  try:
    opts,args = getopt.getopt (sys.argv[1:], '', [])
  except getopt.GetoptError:
    usage ()
    sys.exit (1)

  if len (args) != 3:
    usage ()
    sys.exit (1)

  filter = CGContextFilterCreateDictionary (args[0])
  if not filter:
    print 'Unable to create context filter'
    sys.exit (1)
  pdf = CGPDFDocumentCreateWithProvider (CGDataProviderCreateWithFilename (args[1]))
  if not pdf:
    print 'Unable to open input file'
    sys.exit (1)

  c = CGPDFContextCreateWithFilename (args[2], page_rect, filter)
  if not c:
    print 'Unable to create output context'
    sys.exit (1)

  for p in range (1, pdf.getNumberOfPages () + 1):
    #r = pdf.getMediaBox (p)
    r = pdf.getPage(p).getBoxRect(p)
    c.beginPage (r)
    c.drawPDFDocument (r, pdf, p)
    c.endPage ()

  c.finish ()

if __name__ == '__main__':
  main ()

----- filter-pdf-old.py: end

=======================================================================

The working code based on the answer:

from Foundation import *
from Quartz import *

pdf_url = NSURL.fileURLWithPath_("test.pdf")
pdf_doc = PDFDocument.alloc().initWithURL_(pdf_url)

furl = NSURL.fileURLWithPath_("/System/Library/Filters/Blue Tone.qfilter")
fobj = QuartzFilter.quartzFilterWithURL_(furl)
fdict = { 'QuartzFilter': fobj }
pdf_doc.writeToFile_withOptions_("test_out.pdf", fdict)

Solution

  • two approaches - if you need to open and modify an already existing file, use the PDFKit's PDFDocument (reference) and use PDFDocument's writeToFile_withOptions_ with option dict including the "QuartzFilter" option of needed filter.

    OTOH if you need your own drawing and have a CGContext at hand, you can use something along these lines:

    from Quartz import *
    data = NSMutableData.dataWithCapacity_(1024**2)
    dataConsumer = CGDataConsumerCreateWithCFData(data)
    context = CGPDFContextCreate(dataConsumer, None, None)
    f = QuartzFilter.quartzFilterWithURL_(NSURL.fileURLWithPath_("YourFltr.qfilter"))
    f.applyToContext_(context)
    # do your drawing
    CGPDFContextClose(context)
    # the PDF is in the data variable. Do whatever you need to do with the data (save to file...).