"""
Parse a PDF file and print some information about it.

Usage:
    python parse_page_contents.py inputfile ...
"""
from Quartz import *
import objc
import sys
import array

import Quartz

class MyDataScan (object):
    def __init__(self):
        self.numImagesWithColorThisPage = 0
        self.numImageMasksThisPage = 0
        self.numImagesMaskedWithMaskThisPage = 0
        self.numImagesMaskedWithColorsThisPage = 0

def printPageResults(outFile, myData, pageNum):
    if myData.numImagesWithColorThisPage:
        print >>outFile, "Found %d images with intrinsic color on page %d."%(
                myData.numImagesWithColorThisPage, pageNum)

    if myData.numImageMasksThisPage:
        print >>outFile, "Found %d image masks on page %d."%(
                    myData.numImageMasksThisPage,
                    pageNum)

    if myData.numImagesMaskedWithMaskThisPage:
        print >>outFile, "Found %d images masked with masks on page %d."%(
                    myData.numImagesMaskedWithMaskThisPage,
                    pageNum)

    if myData.numImagesMaskedWithColorsThisPage:
        print >>outFile, "Found %d images masked with colors on page %d."%(
                    myData.numImagesMaskedWithColorsThisPage,
                    pageNum)

def printDocResults(outFile, totPages, totImages):
    print >>outFile
    print >>outFile, "Summary: %d page document contains %d images."%(
                        totPages, totImages)
    print >>outFile


def checkImageType(imageDict, myScanData):
    hasMaskKey, isMask = CGPDFDictionaryGetBoolean(imageDict, "ImageMask", None);
    if not hasMaskKey:
        hasMaskKey, isMask = CGPDFDictionaryGetBoolean(imageDict, "IM", None);

    if hasMaskKey and isMask:
        myScanData.numImageMasksThisPage += 1
        return

    # If image is masked with an alpha image it has an SMask entry.
    hasSMaskKey, object = CGPDFDictionaryGetObject(imageDict, "SMask", None)
    if hasSMaskKey:
        # This object must be an XObject that is an image.
        # This code assumes the PDF is well formed in this regard.
        myScanData.numImagesMaskedWithMaskThisPage += 1
        return

    # If this image is masked with an image or with colors it has
    # a Mask entry.
    hasMask, object = CGPDFDictionaryGetObject(imageDict, "Mask", None)
    if hasMask:
        # If the object is an XObject then the mask is an image.
        # If it is an array, the mask is an array of colors.
        type = CGPDFObjectGetType(object)
        # Check if it is a stream type which it must be to be an XObject.
        if type == kCGPDFObjectTypeStream:
            myScanData.numImagesMaskedWithMaskThisPage += 1
        elif type == kCGPDFObjectTypeArray:
            myScanData.numImagesMaskedWithColorsThisPage += 1
        else:
            print >>sys.stderr, "Mask entry in Image object is not well formed!"

        return

    # This image is not a mask, is not masked with another image or
    # color so it must be an image with intrinsic color with no mask.
    myScanData.numImagesWithColorThisPage += 1

#       The "Do" operator consumes one value off the stack, the name of
#       the object to execute. The name is a resource in the resource
#       dictionary of the page and the object corresponding to that name
#       is an XObject. The most common types of XObjects are either
#       Form objects or Image objects. This code only counts images.
#
#       Note that forms, patterns, and potentially other resources contain
#       images. This code only counts the top level images in a PDF document,
#       not images embedded in other resources.
@objc.callbackFor(CGPDFOperatorTableSetCallback)
def myOperator_Do(s, info):
    # Check to see if this is an image or not.
    cs = CGPDFScannerGetContentStream(s)

    # The Do operator takes a name. Pop the name off the
    # stack. If this fails then the argument to the
    # Do operator is not a name and is therefore invalid!
    res, name = CGPDFScannerPopName(s, None)
    if not res:
        print >>sys.stderr, "Couldn't pop name off stack!"
        return

    # Get the resource with type "XObject" and the name
    # obtained from the stack.
    xobject = CGPDFContentStreamGetResource(cs, "XObject", name);
    if xobject is None:
        print >>sys.stderr, "Couldn't get XObject with name %s"%(name,)
        return

    # An XObject must be a stream so obtain the value from the xobject
    # as if it were a stream. If this fails, the PDF is malformed.
    res, stream = CGPDFObjectGetValue(xobject, kCGPDFObjectTypeStream, None)
    if not res:
        print >>sys.stderr, "XObject '%s' is not a stream"%(name,)
        return

    print stream

    # Streams consist of a dictionary and the data associated
    # with the stream. This code only cares about the dictionary.
    dict = CGPDFStreamGetDictionary(stream);
    if dict is None:
        print >>sys.stderr, "Couldn't obtain dictionary from stream %s!"%(name,)
        return

    # An XObject dict has a Subtype that indicates what kind it is.
    res, name = CGPDFDictionaryGetName(dict, "Subtype", None)
    if not res:
        print >>sys.stderr, "Couldn't get SubType of dictionary object!"
        return

    # This code is interested in the "Image" Subtype of an XObject.
    # Check whether this object has Subtype of "Image".
    if name != "Image":
        # The Subtype is not "Image" so this must be a form
        # or other type of XObject.
        return


    # This is an Image so figure out what variety of image it is.
    checkImageType(dict, info)

# This callback handles inline images. Inline images end with the
# "EI" operator.
@objc.callbackFor(CGPDFOperatorTableSetCallback)
def myOperator_EI(s, info):
    print "EI"
    # When the scanner encounters the EI operator, it has a
    # stream corresponding to the image on the operand stack.
    # This code pops the stream off the stack in order to
    # examine it.
    res, stream = CGPDFScannerPopStream(s, None)
    if not res:
        print >>sys.stderr, "Couldn't create stream from inline image"
        return

    # Get the image dictionary from the stream.
    dict = CGPDFStreamGetDictionary(stream);
    if dict is None:
        print >>sys.stderr, "Couldn't get dict from inline image stream!"
        return

    # By definition the stream passed to EI is an image so
    # pass it to the code to check the type of image.
    checkImageType(dict, info)

def createMyOperatorTable():
    myTable = CGPDFOperatorTableCreate()
    CGPDFOperatorTableSetCallback(myTable, "Do", myOperator_Do)
    CGPDFOperatorTableSetCallback(myTable, "EI", myOperator_EI)
    return myTable

def dumpPageStreams(url, outFile):
    # Create a CGPDFDocumentRef from the input PDF file.
    pdfDoc = CGPDFDocumentCreateWithURL(url);
    if pdfDoc is None:
        print >>sys.stderr, "Couldn't open PDF document!"
        return

    # Create the operator table with the needed callbacks.
    table = createMyOperatorTable();
    if table is None:
        print >>sys.stderr, "Couldn't create operator table!"
        return

    # Initialize the count of the images.
    totalImages = 0

    # Obtain the total number of pages for the document.
    totPages = CGPDFDocumentGetNumberOfPages(pdfDoc)

    # Loop over all the pages in the document, scanning the
    # content stream of each one.
    for i in range(1, totPages+1):
        # Get the PDF page for this page in the document.
        p = CGPDFDocumentGetPage(pdfDoc, i)

        # Create a reference to the content stream for this page.
        cs = CGPDFContentStreamCreateWithPage(p)

        if cs is None:
            print >>sys.stderr, "Couldn't create content stream for page #%d"%(i,)
            return

        # Initialize the counters of images for this page.
        myData = MyDataScan()

        # Create a scanner for this PDF document page.
        scanner = CGPDFScannerCreate(cs, table, 0);
        if scanner is None:
            print >>sys.stderr, "Couldn't create scanner for page #%d!"%(i,)
            return


        # CGPDFScannerScan causes Quartz to scan the content stream,
        # calling the callbacks in the table when the corresponding
        # operator is encountered. Once the content stream for the
        # page has been consumed or Quartz detects a malformed
        # content stream, CGPDFScannerScan returns.
        if not CGPDFScannerScan(scanner):
            print >>sys.stderr, "Scanner couldn't scan all of page #%d!"%(i,)

        # Print the results for this page.
        printPageResults(outFile, myData, i);

        # Update the total count of images with the count of the
        # images on this page.
        totalImages += (
                myData.numImagesWithColorThisPage +
                myData.numImageMasksThisPage +
                myData.numImagesMaskedWithMaskThisPage +
                myData.numImagesMaskedWithColorsThisPage)

        # Once the page has been scanned, release the
        # scanner for this page.
        CGPDFScannerRelease(scanner)
        # Release the content stream for this page.
        CGPDFContentStreamRelease(cs)
        # Done with this page; loop to next page.

    printDocResults(outFile, totPages, totalImages)

def main(args = None):
    if args is None:
        args = sys.argv

    if len(args) < 2:
        print >>sys.stderr, "Usage: %s inputfile ... "%(args[0],)
        return 1

    for inputFileName in args[1:]:
        print "Beginning Document %r"%(inputFileName,)

        print CFURLCreateFromFileSystemRepresentation.__metadata__()
        inURL = CFURLCreateFromFileSystemRepresentation(None, inputFileName,
                                len(inputFileName), False)
        if inURL is None:
            print >>sys.stderr, "Couldn't create URL for input file!"
            return 1

        dumpPageStreams(inURL, sys.stdout)
        #CFRelease(inURL)

    return 0

if __name__ == "__main__":
    sys.exit(main())
