├── Python3 ├── DeBooklet.py ├── PDF Services │ ├── PDFfromStupidiWork.py │ ├── Save As PDF-X.py │ ├── booklet.py │ ├── readme.md │ └── watermark PDF.py ├── Shell Scripts │ ├── copyOutlines.py │ ├── createNotes.py │ ├── createPDFoutlines.py │ ├── creator.py │ ├── getInfo.py │ ├── getNotes.py │ ├── getPDFOutlines 1.py │ ├── getPDFOutlines 2.py │ ├── getPDFclip.py │ ├── listFilters.py │ ├── pagelayout.py │ ├── password.py │ ├── pdfsearch.py │ └── quartzfilter.py ├── addpage.py ├── bookletPDF.py ├── countpages.py ├── cropPDF.py ├── encrypt.py ├── graphpaper.py ├── image2pdf.py ├── indexnumbers.py ├── joinpdfs.py ├── makePDFX.py ├── metadata.py ├── pagenumber.py ├── pdf2png 3.py ├── pdf2tiff 3.py ├── pdf2txt.py ├── readme ├── removePage.py ├── rinsePDF.py ├── rotate.py ├── scalepdf.py ├── setTitle.py ├── splitPDF.py ├── tint.py ├── trimPDF.py ├── watermark.py └── watermarkPDF.py ├── QuartzFilters ├── Better PDFX-3.qfilter ├── Better Reduce File Size 150dpi.qfilter ├── Better Reduce File Size 300dpi.qfilter └── README.md ├── README.md ├── doc ├── PDFbutton.png ├── makequickaction.png ├── pdfworkflows.png ├── quickactionmenu.png └── readme.md └── legacy (python 2) ├── Automator_Scripts ├── DeBooklet.py ├── Helpful Illustration.png ├── ReadMe.md ├── addpage.py ├── bookletPDF.py ├── countpages.py ├── cropPDF.py ├── encrypt.py ├── graphpaper.py ├── image2pdf.py ├── indexnumbers.py ├── joinpdfs.py ├── metadata.py ├── pagenumber.py ├── pdf2png.py ├── pdf2tiff.py ├── pdf2txt.py ├── removePage.py ├── rinsePDF.py ├── rotate.py ├── splitPDF.py ├── tint.py ├── trimPDF.py └── watermark.py ├── Automator_Services ├── .DS_Store ├── Add Blank Page.workflow.zip ├── Add Index Numbers to PDFs.workflow.zip ├── Add Page Numbering.workflow.zip ├── Count PDF Pages.workflow.zip ├── Images to PDF.workflow.zip ├── Join PDFs.workflow.zip ├── Make PDF-X.workflow.zip ├── PDF 2 TIFF.workflow.zip ├── README.md ├── Rotate PDF pages.workflow.zip ├── Split PDF pages.workflow.zip └── Watermark PDF.workflow.zip ├── PDF_Services ├── ReadMe.md ├── Save As PDF-X.py ├── Save_PDF_from_iWork.py ├── booklet.py └── watermark PDF.py ├── Quick_Actions ├── Add Blank Page to End.workflow.zip ├── Add Index Numbers.workflow.zip ├── Add Metadata.workflow.zip ├── Add Page Numbering.workflow.zip ├── Count PDF Pages.workflow.zip ├── Crop to Trim Marks.workflow.zip ├── Encrypt PDF.workflow.zip ├── Export Text.workflow.zip ├── Graph Paper.workflow.zip ├── Images to PDF.workflow.zip ├── Join PDFs.workflow.zip ├── Make Booklet.workflow.zip ├── Make PDF-X.workflow.zip ├── PDF 2 PNG.workflow.zip ├── PDF 2 TIFF.workflow.zip ├── Readme.md ├── Rotate PDF pages.workflow.zip ├── Split PDF pages.workflow.zip ├── Tint PDF.workflow.zip └── Watermark with Text.workflow.zip └── Shell_Scripts ├── ReadMe.md ├── createPDFoutlines.py ├── creator.py ├── getInfo.py ├── getPDFOutlines.py ├── getPDFclip.py ├── listFilters.py ├── pagelayout.py ├── password.py ├── pdfsearch.py └── quartzfilter.py /Python3/DeBooklet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # DeBooklet v.1.1 (PYTHON 3) : Split page spreads into separate pages. 4 | # by Ben Byram-Wigfield 5 | 6 | # Doesn't sort the pages. 7 | # Files produced are 'quite large' as some residual data from the cropped area remains. 8 | 9 | import sys 10 | import os 11 | from Quartz import PDFDocument, kPDFDisplayBoxMediaBox, CGRectEqualToRect, CGRectMake 12 | from CoreFoundation import NSURL 13 | 14 | mediabox = kPDFDisplayBoxMediaBox 15 | # Set to False if you don't want page 1 split. 16 | doPageOne = True 17 | 18 | def debooklet(filename): 19 | shortName = os.path.splitext(filename)[0] 20 | outFilename = shortName + " paged.pdf" 21 | 22 | # If running python2, uncomment the following line: 23 | # filename = filename.decode('utf-8') 24 | pdfURL = NSURL.fileURLWithPath_(filename) 25 | leftPDF = PDFDocument.alloc().initWithURL_(pdfURL) 26 | rightPDF = PDFDocument.alloc().initWithURL_(pdfURL) 27 | newPDF = PDFDocument.alloc().init() 28 | if leftPDF: 29 | if not(doPageOne): 30 | leftPage = leftPDF.pageAtIndex_(0) 31 | newPDF.insertPage_atIndex_(leftPage, 0) 32 | pages = leftPDF.pageCount() 33 | startPage = int(not(doPageOne)) 34 | for p in range(startPage, pages): 35 | outPageCount = newPDF.pageCount() 36 | leftPage = leftPDF.pageAtIndex_(p) 37 | rightPage = rightPDF.pageAtIndex_(p) 38 | mediaBoxSize = leftPage.boundsForBox_(mediabox) 39 | rotation = leftPage.rotation() 40 | if (rotation == 0) or (rotation == 180): 41 | halfway = (mediaBoxSize.size.width/2) 42 | pageHeight = mediaBoxSize.size.height 43 | leftHandCrop = CGRectMake(0,0,halfway,pageHeight) 44 | rightHandCrop = CGRectMake(halfway, 0, halfway, pageHeight) 45 | leftPage.setBounds_forBox_(leftHandCrop, mediabox) 46 | rightPage.setBounds_forBox_(rightHandCrop, mediabox) 47 | else: 48 | halfway = (mediaBoxSize.size.height/2) 49 | pageWidth = mediaBoxSize.size.width 50 | topCrop = CGRectMake(0,0,pageWidth, halfway) 51 | bottomCrop = CGRectMake(0,halfway, pageWidth,halfway) 52 | leftPage.setBounds_forBox_(topCrop, mediabox) 53 | rightPage.setBounds_forBox_(bottomCrop, mediabox) 54 | 55 | newPDF.insertPage_atIndex_(leftPage, outPageCount) 56 | newPDF.insertPage_atIndex_(rightPage, outPageCount+1) 57 | 58 | newPDF.writeToFile_(outFilename) 59 | 60 | if __name__ == '__main__': 61 | for filename in sys.argv[1:]: 62 | debooklet(filename) 63 | -------------------------------------------------------------------------------- /Python3/PDF Services/PDFfromStupidiWork.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # SAVE PDF FROM STUPID iWORK 4 | # PDF Service to strip iWork file extension before saving PDF to designated folder 5 | # by Ben Byram-Wigfield v1.0 6 | 7 | # Save this file in ~/Library/PDF Services. It will then be available in the 8 | # PDF button of the print menu. 9 | 10 | import os 11 | import sys 12 | import Quartz as Quartz 13 | from Foundation import NSURL 14 | from AppKit import NSSavePanel, NSApp 15 | 16 | 17 | def save_dialog(directory, filename): 18 | panel = NSSavePanel.savePanel() 19 | panel.setTitle_("Save PDF document") 20 | myUrl = NSURL.fileURLWithPath_isDirectory_(directory, True) 21 | panel.setDirectoryURL_(myUrl) 22 | panel.setNameFieldStringValue_(filename) 23 | NSApp.activateIgnoringOtherApps_(True) 24 | ret_value = panel.runModal() 25 | if ret_value: 26 | return panel.filename() 27 | else: 28 | return '' 29 | 30 | 31 | # $1 is filename; $3 is complete temp filepath and name. 32 | # $2 is loads of CUPS parameters. 33 | 34 | def main(argv): 35 | (title, options, pathToFile) = argv[:] 36 | 37 | # Set the default location where the PDFs will go (you'll need to make sure this exists) 38 | 39 | destination = os.path.expanduser("~/Desktop/") 40 | 41 | 42 | stripTitle = (os.path.splitext(title)[0]) 43 | stripTitle += ".pdf" 44 | outputfile = save_dialog(destination, stripTitle) 45 | 46 | # Copy file to selected location. 47 | if outputfile != "": 48 | 49 | pdfURL = NSURL.fileURLWithPath_(pathToFile) 50 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 51 | if pdfDoc: 52 | pdfDoc.writeToFile_(outputfile) 53 | 54 | # Delete original PDF from spool folder 55 | os.remove(pathToFile) 56 | 57 | if __name__ == "__main__": 58 | main(sys.argv[1:]) -------------------------------------------------------------------------------- /Python3/PDF Services/Save As PDF-X.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # SAVE AS PDF-X: PDF Service to apply Quartz filter and move PDF to designated folder 4 | # by Ben Byram-Wigfield v1.4 5 | 6 | # $1 is filename; $3 is complete temp filepath and name. 7 | # $2 is loads of CUPS parameters. 8 | 9 | import os 10 | import sys 11 | import Quartz as Quartz 12 | from Foundation import NSURL 13 | from AppKit import NSSavePanel, NSApp 14 | 15 | 16 | def save_dialog(directory, filename): 17 | panel = NSSavePanel.savePanel() 18 | panel.setTitle_("Save PDF-X3 document") 19 | myUrl = NSURL.fileURLWithPath_isDirectory_(directory, True) 20 | panel.setDirectoryURL_(myUrl) 21 | panel.setNameFieldStringValue_(filename) 22 | NSApp.activateIgnoringOtherApps_(True) 23 | ret_value = panel.runModal() 24 | if ret_value: 25 | return panel.filename() 26 | else: 27 | return '' 28 | 29 | 30 | def main(argv): 31 | (title, options, pathToFile) = argv[:] 32 | 33 | # Set the default location where the PDFs will go (you'll need to make sure this exists) 34 | 35 | destination = os.path.expanduser("~/Desktop/") 36 | 37 | # Set the filepath of the filter. 38 | # Check for custom user filter; otherwise use the Not-Very-Good System filter. 39 | filterpath = os.path.expanduser("~/Library/Filters/Better PDF-X3.qfilter") 40 | if not os.path.exists(filterpath): 41 | filterpath = "/System/Library/Filters/Create Generic PDFX-3 Document.qfilter" 42 | 43 | title += ".pdf" 44 | outputfile = save_dialog(destination, title) 45 | 46 | if outputfile != "": 47 | 48 | pdfURL = NSURL.fileURLWithPath_(pathToFile) 49 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 50 | if pdfDoc : 51 | filterURL = NSURL.fileURLWithPath_(filterpath) 52 | value = Quartz.QuartzFilter.quartzFilterWithURL_(filterURL) 53 | options = { 'QuartzFilter': value } 54 | pdfDoc.writeToFile_withOptions_(outputfile, options) 55 | 56 | # Delete original PDF from spool folder 57 | os.remove(pathToFile) 58 | 59 | if __name__ == "__main__": 60 | main(sys.argv[1:]) -------------------------------------------------------------------------------- /Python3/PDF Services/booklet.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | 3 | 4 | # ---------------------------------------------------------------- 5 | # PDF Booklet Imposition Script for MacOS v2.3 (PDF Service) 6 | # by Ben Byram-Wigfield 7 | # Feel free to use, modify and pass on with acknowledgement. 8 | 9 | # 1. Set OPTIONS below for output folder, sheet size, and creep 10 | # 2. Install into ~/Library/PDF Services 11 | # 3. It will then appear as an option in the PDF button of the Print dialog. 12 | # (if it has executable flags set.) 13 | 14 | # Script can be configured for stacking 4pp signatures or gathering booklet spreads. 15 | # ---------------------------------------------------------------- 16 | 17 | # Beware of indexes starting from 0 and 1...!!! CGPDFDocument starts page count at 1. 18 | 19 | import os, sys 20 | import copy 21 | import Quartz as Quartz 22 | from Foundation import (NSURL, CFURLCreateFromFileSystemRepresentation, kCFAllocatorDefault) 23 | from AppKit import NSSavePanel, NSApp 24 | 25 | 26 | # Uncomment the sheet size you want. 27 | A3 = [[0,0], [1190.55, 841.88]] 28 | # A4 = [[0,0], [841.88, 595.28]] 29 | # USLetter = [[0,0], [792, 612]] 30 | # Tabloid = [[0,0], [1224, 792]] 31 | 32 | # OPTIONS 33 | # Change this to one of the sizes listed above, if you want. 34 | sheetSize = A3 35 | # Set the default location for saving the files. 36 | destination = os.path.expanduser("~/Desktop") 37 | # Set file suffix 38 | suffix = " booklet.pdf" 39 | # If hasSignatures, sheets will be arranged for stacking in 4pp sections. 40 | hasSignatures = False 41 | pagesPerSheet = 4 # Not sure what will happen if this is changed. 42 | creep = 0.5 # in points. NB: Eventually, the pages will collide. 43 | imposedOrder = [] 44 | 45 | # FUNCTIONS 46 | 47 | def save_dialog(directory, filename): 48 | panel = NSSavePanel.savePanel() 49 | panel.setTitle_("Save PDF booklet") 50 | myUrl = NSURL.fileURLWithPath_isDirectory_(directory, True) 51 | panel.setDirectoryURL_(myUrl) 52 | panel.setNameFieldStringValue_(filename) 53 | NSApp.activateIgnoringOtherApps_(True) 54 | ret_value = panel.runModal() 55 | if ret_value: 56 | return panel.filename() 57 | else: 58 | return '' 59 | 60 | def createPDFDocumentFromPath(path): 61 | url = NSURL.fileURLWithPath_(path) 62 | return Quartz.CGPDFDocumentCreateWithURL(url) 63 | 64 | # Creates a Context for drawing 65 | def createOutputContextWithPath(path, dictarray): 66 | url = NSURL.fileURLWithPath_(path) 67 | return Quartz.CGPDFContextCreateWithURL(url, None, dictarray) 68 | 69 | def imposition(pageRange): 70 | for i in range(1, (int(len(pageRange)/2)), 2): 71 | # First we do recto 72 | imposedOrder.append(pageRange[i*-1]) 73 | imposedOrder.append(pageRange[i-1]) 74 | # And now we do verso 75 | imposedOrder.append(pageRange[i]) 76 | imposedOrder.append(pageRange[(i+1)*-1]) 77 | 78 | return imposedOrder 79 | 80 | def getRotation(pdfpage): 81 | displayAngle = 0 82 | rotValue = Quartz.CGPDFPageGetRotationAngle(pdfpage) 83 | mediaBox = Quartz.CGPDFPageGetBoxRect(pdfpage, Quartz.kCGPDFMediaBox) 84 | if not Quartz.CGRectIsEmpty(mediaBox): 85 | x = Quartz.CGRectGetWidth(mediaBox) 86 | y = Quartz.CGRectGetHeight(mediaBox) 87 | if (x > y): displayAngle = -90 88 | displayAngle -= rotValue 89 | return displayAngle 90 | 91 | # Gets DocInfo from input file to pass to output. 92 | # PyObjC returns Keywords in an NSArray; they must be tupled. 93 | def getDocInfo(file): 94 | pdfURL = NSURL.fileURLWithPath_(file) 95 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 96 | if pdfDoc: 97 | metadata = pdfDoc.documentAttributes() 98 | if "Keywords" in metadata: 99 | keys = metadata["Keywords"] 100 | mutableMetadata = metadata.mutableCopy() 101 | mutableMetadata["Keywords"] = tuple(keys) 102 | return mutableMetadata 103 | else: 104 | return metadata 105 | 106 | def contextDone(context): 107 | if context: 108 | Quartz.CGPDFContextClose(context) 109 | del context 110 | 111 | # MAIN 112 | def main(argv): 113 | (title, options, pathToFile) = argv[:] 114 | shortName = os.path.splitext(title)[0] 115 | # If you want to save to a consistent location, use: 116 | # writeFilename = os.path.join(destination, shortName + suffix) 117 | writeFilename = save_dialog(destination, shortName + suffix) 118 | leftPage = copy.deepcopy(sheetSize) 119 | shift = sheetSize[1][0]/2 120 | leftPage[1][0] = shift 121 | rightPage = copy.deepcopy(leftPage) 122 | rightPage[0][0] = shift 123 | blanks = 0 124 | 125 | # Initiate new PDF, get source PDF data, number of pages. 126 | metaDict = getDocInfo(pathToFile) 127 | writeContext = createOutputContextWithPath(writeFilename, metaDict) 128 | source = createPDFDocumentFromPath(pathToFile) 129 | totalPages = Quartz.CGPDFDocumentGetNumberOfPages(source) 130 | 131 | # Add blank pages to round up to multiple of pages per sheet. 132 | UnsortedOrder = list(range(1, totalPages+1)) 133 | if totalPages%pagesPerSheet: 134 | blanks = pagesPerSheet - (totalPages%pagesPerSheet) 135 | for i in range(blanks): 136 | UnsortedOrder.append(0) 137 | totalPages = len(UnsortedOrder) 138 | 139 | if hasSignatures: 140 | signatureSize = pagesPerSheet 141 | else: 142 | signatureSize = totalPages 143 | 144 | for something in range(0, totalPages, signatureSize): 145 | imposition(UnsortedOrder[something:(something+signatureSize)]) 146 | 147 | # For each side of the sheet, we must... 148 | # ... create a PDF page, take two source pages and place them differently, then close the page. 149 | # If the source page number is 0, then move on without drawing. 150 | Sides = int(totalPages/2) 151 | count = 0 152 | for n in range(Sides): 153 | Quartz.CGContextBeginPage(writeContext, sheetSize) 154 | for position in [leftPage, rightPage]: 155 | if imposedOrder[count]: 156 | page = Quartz.CGPDFDocumentGetPage(source, imposedOrder[count]) 157 | Quartz.CGContextSaveGState(writeContext) 158 | # Check PDF page rotation AND mediabox orientation. 159 | angle = getRotation(page) 160 | Quartz.CGContextConcatCTM(writeContext, Quartz.CGPDFPageGetDrawingTransform(page, Quartz.kCGPDFMediaBox, position, angle, True)) 161 | # Uncomment next line to draw box round each page 162 | # Quartz.CGContextStrokeRectWithWidth(writeContext, leftPage, 2.0) 163 | Quartz.CGContextDrawPDFPage(writeContext, page) 164 | Quartz.CGContextRestoreGState(writeContext) 165 | count += 1 166 | Quartz.CGContextEndPage(writeContext) 167 | 168 | # Set creep for next sheet. 169 | if count%4 == 0: 170 | leftPage[0][0] += creep 171 | rightPage[0][0] -= creep 172 | 173 | # Do tidying up 174 | contextDone(writeContext) 175 | 176 | # Delete original PDF from spool folder 177 | os.remove(pathToFile) 178 | 179 | if __name__ == "__main__": 180 | main(sys.argv[1:]) -------------------------------------------------------------------------------- /Python3/PDF Services/readme.md: -------------------------------------------------------------------------------- 1 | PDF Services 2 | 3 | Apple has been annoying in its use of either bugs or features to restrict the use of PDF Services scripts. 4 | 5 | Currently (Sonoma OS 14), a python PDF Service will not run if the first line #! uses env. It will work if it calls python directly by a valid pathname, e.g. #!/usr/local/bin/python3. 6 | 7 | Ventura and earlier may have sandboxing issues with python running and trying to do things like open file dialogs. 8 | -------------------------------------------------------------------------------- /Python3/PDF Services/watermark PDF.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Merge v. 0.1 4 | # Merges two PDFs 5 | 6 | import sys 7 | import os 8 | import Quartz as Quartz 9 | from Foundation import NSURL, kCFAllocatorDefault 10 | from AppKit import NSSavePanel, NSApp 11 | 12 | # OPTIONS 13 | # Change this filepath to the PDF you want to use a letterhead / template: 14 | watermark = os.path.expanduser("/System/Library/Assistant/UIPlugins/FMF.siriUIBundle/Contents/Resources/person.pdf") 15 | destination = os.path.expanduser("~/Desktop") # Default destination 16 | suffix = " wm.pdf" # Use ".pdf" if no actual suffix required. 17 | 18 | # FUNCTIONS 19 | 20 | def save_dialog(directory, filename): 21 | panel = NSSavePanel.savePanel() 22 | panel.setTitle_("Save PDF booklet") 23 | myUrl = NSURL.fileURLWithPath_isDirectory_(directory, True) 24 | panel.setDirectoryURL_(myUrl) 25 | panel.setNameFieldStringValue_(filename) 26 | NSApp.activateIgnoringOtherApps_(True) 27 | ret_value = panel.runModal() 28 | if ret_value: 29 | return panel.filename() 30 | else: 31 | return '' 32 | 33 | # Loads in PDF document 34 | def createPDFDocumentWithPath(path): 35 | return Quartz.CGPDFDocumentCreateWithURL(Quartz.CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, path, len(path), False)) 36 | 37 | # Creates a Context for drawing 38 | def createOutputContextWithPath(path, dictarray): 39 | return Quartz.CGPDFContextCreateWithURL(Quartz.CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, path, len(path), False), None, dictarray) 40 | 41 | # Gets DocInfo from input file to pass to output. 42 | # PyObjC returns Keywords in an NSArray; they must be tupled. 43 | def getDocInfo(file): 44 | pdfURL = NSURL.fileURLWithPath_(file) 45 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 46 | if pdfDoc: 47 | metadata = pdfDoc.documentAttributes() 48 | if "Keywords" in metadata: 49 | keys = metadata["Keywords"] 50 | mutableMetadata = metadata.mutableCopy() 51 | mutableMetadata["Keywords"] = tuple(keys) 52 | return mutableMetadata 53 | else: 54 | return metadata 55 | 56 | def main(argv): 57 | (title, options, pathToFile) = argv[:] 58 | shortName = os.path.splitext(title)[0] 59 | # If you want to save to a consistent location, use: 60 | # writeFilename = os.path.join(destination, shortName + suffix) 61 | writeFilename = save_dialog(destination, shortName + suffix) 62 | shortName = os.path.splitext(pathToFile)[0] 63 | metaDict = getDocInfo(pathToFile) 64 | writeContext = createOutputContextWithPath(writeFilename, metaDict) 65 | readPDF = createPDFDocumentWithPath(pathToFile) 66 | mergePDF = createPDFDocumentWithPath(watermark) 67 | 68 | if writeContext != None and readPDF != None: 69 | numPages = Quartz.CGPDFDocumentGetNumberOfPages(readPDF) 70 | for pageNum in xrange(1, numPages + 1): 71 | page = Quartz.CGPDFDocumentGetPage(readPDF, pageNum) 72 | mergepage = Quartz.CGPDFDocumentGetPage(mergePDF, 1) 73 | if page: 74 | mediaBox = Quartz.CGPDFPageGetBoxRect(page, Quartz.kCGPDFMediaBox) 75 | if Quartz.CGRectIsEmpty(mediaBox): 76 | mediaBox = None 77 | Quartz.CGContextBeginPage(writeContext, mediaBox) 78 | Quartz.CGContextSetBlendMode(writeContext, Quartz.kCGBlendModeOverlay) 79 | Quartz.CGContextDrawPDFPage(writeContext, page) 80 | Quartz.CGContextDrawPDFPage(writeContext, mergepage) 81 | Quartz.CGContextEndPage(writeContext) 82 | Quartz.CGPDFContextClose(writeContext) 83 | del writeContext 84 | 85 | else: 86 | print "A valid input file and output file must be supplied." 87 | sys.exit(1) 88 | 89 | if __name__ == "__main__": 90 | main(sys.argv[1:]) 91 | -------------------------------------------------------------------------------- /Python3/Shell Scripts/copyOutlines.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # copyOutlines v.1.0 4 | # Copy PDF Table of Contents from one PDF to another. 5 | # by Ben Byram-Wigfield 6 | # copyOutlines.py 7 | 8 | 9 | from Foundation import NSURL 10 | import Quartz as Quartz 11 | import sys 12 | 13 | 14 | def copyOutlines(source, dest): 15 | pdfURL = NSURL.fileURLWithPath_(source) 16 | inPDF = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 17 | if inPDF: 18 | outline = Quartz.PDFOutline.alloc().init() 19 | outline = inPDF.outlineRoot() 20 | pdfURL = NSURL.fileURLWithPath_(dest) 21 | outPDF = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 22 | outPDF.setOutlineRoot_(outline) 23 | outPDF.writeToFile_(dest) 24 | 25 | if __name__ == '__main__': 26 | copyOutlines(sys.argv[1], sys.argv[2]) -------------------------------------------------------------------------------- /Python3/Shell Scripts/createNotes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from Foundation import NSURL, NSString 4 | from AppKit import NSColor 5 | import Quartz as Quartz 6 | import sys, os 7 | 8 | os.environ["PDFKIT_LOG_ANNOTATIONS"] = 'True' 9 | 10 | # You will need to change these filepaths to a local test pdf and an output file. 11 | 12 | outfile = '/path/to/file.pdf' 13 | infile = '/path/to/file.pdf' 14 | myYellow = NSColor.yellowColor() 15 | 16 | if __name__ == "__main__": 17 | 18 | myRect = Quartz.CGRectMake(100,100,10,14) 19 | pdfURL = NSURL.fileURLWithPath_(infile) 20 | myPDF = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 21 | if myPDF: 22 | pages = myPDF.pageCount() 23 | myNote = Quartz.PDFAnnotation.alloc().initWithBounds_forType_withProperties_(myRect, "Text", None) 24 | myNote.setContents_("Qwe qwe qwe ") 25 | myNote.setColor_(myYellow) 26 | page = myPDF.pageAtIndex_(0) 27 | page.addAnnotation_(myNote) 28 | 29 | myPDF.writeToFile_(outfile) 30 | else: 31 | print("No valid PDF was received.") 32 | -------------------------------------------------------------------------------- /Python3/Shell Scripts/createPDFoutlines.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # CREATE PDF OUTLINES v.1.2 : Add a simple list of Bookmarks to a PDF. 4 | 5 | from Foundation import NSURL, NSString 6 | import Quartz as Quartz 7 | import sys 8 | 9 | # You will need to change these filepaths to a local test pdf and an output file. 10 | infile = "/path/to/file.pdf" 11 | outfile = '/path/to/file.pdf' 12 | 13 | def makeOutline(page, label): 14 | # Create Destination 15 | myPage = myPDF.pageAtIndex_(page) 16 | pageSize = myPage.boundsForBox_(Quartz.kCGPDFMediaBox) 17 | x = 50 18 | y = Quartz.CGRectGetMaxY(pageSize) 19 | pagePoint = Quartz.CGPointMake(x,y) 20 | myDestination = Quartz.PDFDestination.alloc().initWithPage_atPoint_(myPage, pagePoint) 21 | myLabel = NSString.stringWithString_(label) 22 | myOutline = Quartz.PDFOutline.alloc().init() 23 | myOutline.setLabel_(myLabel) 24 | myOutline.setDestination_(myDestination) 25 | return myOutline 26 | 27 | 28 | if __name__ == "__main__": 29 | 30 | pdfURL = NSURL.fileURLWithPath_(infile) 31 | myPDF = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 32 | if myPDF: 33 | # Create Outlines. Add the Page Index (from 0) and label in pairs here: 34 | myTableOfContents = [ 35 | (0, 'Page 1'), 36 | (1, 'Page 2'), 37 | (2, 'Page 3') 38 | ] 39 | allMyOutlines = [] 40 | for index, outline in myTableOfContents: 41 | allMyOutlines.append(makeOutline(index, outline)) 42 | 43 | # Create a root Outline and add each outline 44 | rootOutline = Quartz.PDFOutline.alloc().init() 45 | for index, eachOutline in enumerate(allMyOutlines): 46 | childPosition = rootOutline.numberOfChildren() 47 | rootOutline.insertChild_atIndex_(eachOutline, childPosition) 48 | myPDF.setOutlineRoot_(rootOutline) 49 | myPDF.writeToFile_(outfile) 50 | print("done") 51 | 52 | -------------------------------------------------------------------------------- /Python3/Shell Scripts/creator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # CREATOR : Add [Creator] metadata to a PDF file. 4 | # by Ben Byram-Wigfield 5 | 6 | # creator -c - i [-o ] 7 | # 8 | import sys 9 | import os 10 | import getopt 11 | import Quartz as Quartz 12 | 13 | from CoreFoundation import NSURL 14 | 15 | def setMetadata(argv): 16 | inputfile = "" 17 | outputfile = "" 18 | value="" 19 | try: 20 | opts, args = getopt.getopt(argv,"hc:i:o:",["creator=", "input=", "output="]) 21 | except getopt.GetoptError: 22 | print ('creator.py -c -i -o ') 23 | sys.exit(2) 24 | for opt, arg in opts: 25 | if opt == '-h': 26 | print ('creator.py -c -i -o ') 27 | print ('longnames are: --creator, --input, --output') 28 | print ("If no output is specified, the input will be over-written.") 29 | sys.exit() 30 | elif opt in ("-c", "--creator"): 31 | value = arg 32 | elif opt in ("-i", "--input"): 33 | inputfile = arg 34 | elif opt in ("-o", "--output"): 35 | outputfile = arg 36 | 37 | if outputfile == "": outputfile = inputfile 38 | pdfURL = NSURL.fileURLWithPath_(inputfile) 39 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 40 | 41 | # Default value option: 42 | # if value == "": value = "Uncle Bob Silly" 43 | options = { Quartz.kCGPDFContextCreator: value } 44 | pdfDoc.writeToFile_withOptions_(outputfile, options) 45 | 46 | if __name__ == "__main__": 47 | setMetadata(sys.argv[1:]) 48 | 49 | """ 50 | Other Dict keys include: 51 | 52 | kCGPDFContextAuthor (string) 53 | kCGPDFContextTitle 54 | kCGPDFContextOwnerPassword 55 | kCGPDFContextUserPassword 56 | kCGPDFContextAllowsPrinting (boolean) 57 | kCGPDFContextAllowsCopying (boolean) 58 | 59 | kCGPDFContextMediaBox (CGRect) 60 | kCGPDFContextCropBox (CGRect) 61 | kCGPDFContextBleedBox (CGRect) 62 | kCGPDFContextTrimBox (CGRect) 63 | kCGPDFContextArtBox (CGRect) 64 | 65 | kCGPDFContextOutputIntent 66 | kCGPDFContextOutputIntents 67 | kCGPDFContextSubject 68 | kCGPDFContextKeywords 69 | kCGPDFContextEncryptionKeyLength 70 | 71 | kCGPDFXOutputIntentSubtype 72 | kCGPDFXOutputConditionIdentifier 73 | kCGPDFXOutputCondition 74 | kCGPDFXRegistryName 75 | kCGPDFXInfo 76 | kCGPDFXDestinationOutputProfile 77 | 78 | See the Apple Documentation page on Auxiliary Dictionary Keys for PDF Context for more. 79 | 80 | """ 81 | -------------------------------------------------------------------------------- /Python3/Shell Scripts/getInfo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # GETINFO: Gets PDF metadata for any PDF file(s) provided as an argument 4 | # by Ben Byram-Wigfield v1.4 5 | # 6 | 7 | import sys 8 | from Quartz import PDFDocument 9 | from Foundation import NSURL 10 | 11 | if __name__ == '__main__': 12 | 13 | for filename in sys.argv[1:]: 14 | pdfURL = NSURL.fileURLWithPath_(filename) 15 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 16 | if pdfDoc: 17 | print ("URL:", pdfDoc.documentURL()) # Might be nice to Unicode this. 18 | metadata = pdfDoc.documentAttributes() 19 | for key in metadata: 20 | print ("{}: {}".format(key, metadata[key])) 21 | print ("Number of Pages:", pdfDoc.pageCount()) 22 | print ("Is Encrypted:", pdfDoc.isEncrypted()) 23 | print ("Is Locked:", pdfDoc.isLocked()) 24 | print ("Allows Copying:", pdfDoc.allowsCopying()) 25 | print ("Allows Printing:", pdfDoc.allowsPrinting()) 26 | print ("Version: {}.{}".format(pdfDoc.majorVersion(),pdfDoc.minorVersion())) 27 | else: print ("Cannot get this file. (Not a PDF? / Bad filename?)") -------------------------------------------------------------------------------- /Python3/Shell Scripts/getNotes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # GETNOTES v1.1 4 | # by Ben Byram-Wigfield 5 | # Lists the Annotations in a PDF file. 6 | 7 | from Foundation import NSURL, NSString 8 | import Quartz as Quartz 9 | import sys 10 | 11 | def getNotes(infile): 12 | pdfURL = NSURL.fileURLWithPath_(infile) 13 | myPDF = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 14 | if myPDF: 15 | pages = myPDF.pageCount() 16 | for p in range(0, pages): 17 | page = myPDF.pageAtIndex_(p) 18 | if page.annotations(): 19 | for eachNote in page.annotations(): 20 | print (eachNote) 21 | print (eachNote.contents()) 22 | print("-------") 23 | 24 | if __name__ == "__main__": 25 | for filename in sys.argv[1:]: 26 | getNotes(filename) -------------------------------------------------------------------------------- /Python3/Shell Scripts/getPDFOutlines 1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # This script will list the Table of Contents data from any PDF file(s) given as arguments. 4 | 5 | from Foundation import NSURL 6 | import Quartz as Quartz 7 | import sys 8 | 9 | 10 | def getOutlines(infile): 11 | pdfURL = NSURL.fileURLWithPath_(infile) 12 | myPDF = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 13 | if myPDF: 14 | outline = Quartz.PDFOutline.alloc().init() 15 | outline = myPDF.outlineRoot() 16 | if outline: 17 | print (f'Root Outline: {outline.label()}') 18 | print (f'Number of Children: {outline.numberOfChildren()}') 19 | print (outline.index()) 20 | for n in range(outline.numberOfChildren()): 21 | print (f'Child: {n}') 22 | print (outline.childAtIndex_(n).label()) 23 | print (outline.childAtIndex_(n).destination()) 24 | print (outline.childAtIndex_(n).parent().label()) 25 | if __name__ == '__main__': 26 | for filename in sys.argv[1:]: 27 | getOutlines(filename) -------------------------------------------------------------------------------- /Python3/Shell Scripts/getPDFOutlines 2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # GET PDF OUTLINES v. 1.5 4 | # by Ben Byram-Wigfield 5 | # This script will produce rudimentary PDFmark data from any PDF file(s) given as arguments, 6 | # for PDF Outlines (bookmarks, ToCs) with page destinations. (Not Actions.) 7 | 8 | from Foundation import NSURL 9 | import Quartz as Quartz 10 | import sys 11 | 12 | 13 | def getDestination(thisOutline): 14 | thisDestination=thisOutline.destination() 15 | if thisDestination: 16 | stringDestination= str(thisDestination).split(",") 17 | OutlineType = stringDestination[0] 18 | pageNum = stringDestination[1].split("= ")[1] 19 | pageNum = str(int(pageNum)+1) 20 | return OutlineType, pageNum 21 | 22 | 23 | def recurseOutlines(thisOutline): 24 | # print (thisOutline.index()) 25 | print ("[ /Title (" + thisOutline.label() +")") 26 | Otype, pageNum = getDestination(thisOutline) 27 | print (" /Page " + pageNum) 28 | 29 | # View wil never be specified. 30 | Otype = "qwe" 31 | if Otype == "XYZ": 32 | print(" /View [/" + Otype + " 0 0 0]") 33 | if Otype == "Fit": 34 | print(" /View [/" + Otype + "]") 35 | 36 | if thisOutline.numberOfChildren() != 0: 37 | print (" /Count " + str(thisOutline.numberOfChildren())) 38 | print(" /OUT pdfmark\n") 39 | for n in range(thisOutline.numberOfChildren()): 40 | recurseOutlines(thisOutline.childAtIndex_(n)) 41 | else: 42 | print(" /OUT pdfmark\n") 43 | 44 | def getOutlines(infile): 45 | pdfURL = NSURL.fileURLWithPath_(infile) 46 | myPDF = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 47 | if myPDF: 48 | outline = Quartz.PDFOutline.alloc().init() 49 | outline = myPDF.outlineRoot() 50 | if outline: 51 | if outline.numberOfChildren() != 0: 52 | for n in range(outline.numberOfChildren()): 53 | recurseOutlines(outline.childAtIndex_(n)) 54 | else: 55 | print("No Outlines in this PDF.") 56 | 57 | if __name__ == '__main__': 58 | for filename in sys.argv[1:]: 59 | getOutlines(filename) -------------------------------------------------------------------------------- /Python3/Shell Scripts/getPDFclip.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # getPDFclip v.1.3 : Get PDF from Clipboard image data. 4 | # by Ben Byram-Wigfield. 5 | # This script saves a PDF with a copy of any image data found on the Mac Clipboard. 6 | 7 | # If Clipboard.pdf exists, the image is added as an extra page. 8 | 9 | from AppKit import NSPasteboard, NSPasteboardTypePDF, NSPasteboardTypeTIFF, NSPasteboardTypePNG, NSTIFFPboardType, NSPICTPboardType, NSImage 10 | from Foundation import NSURL 11 | import Quartz as Quartz 12 | import os, syslog 13 | 14 | # Change this to whatever filepath you want: 15 | outfile=os.path.expanduser("~/Desktop/Clipboard.pdf") 16 | 17 | 18 | myFavoriteTypes = [NSPasteboardTypePDF, NSPasteboardTypeTIFF, NSPasteboardTypePNG, NSTIFFPboardType, NSPICTPboardType, 'com.adobe.encapsulated-postscript'] 19 | pb = NSPasteboard.generalPasteboard() 20 | best_type = pb.availableTypeFromArray_(myFavoriteTypes) 21 | if best_type: 22 | clipData = pb.dataForType_(best_type) 23 | if clipData: 24 | image = NSImage.alloc().initWithPasteboard_(pb) 25 | if image: 26 | page = Quartz.PDFPage.alloc().initWithImage_(image) 27 | if os.path.exists(outfile): 28 | pdfURL = NSURL.fileURLWithPath_(outfile) 29 | myFile = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 30 | if myFile: 31 | pagenum = myFile.pageCount() 32 | myFile.insertPage_atIndex_(page, pagenum) 33 | print ("Image added to Clipboard file.") 34 | 35 | else: 36 | pageData = page.dataRepresentation() 37 | myFile = Quartz.PDFDocument.alloc().initWithData_(pageData) 38 | myFile.writeToFile_(outfile) 39 | print ("Clipboard file created.") 40 | 41 | else: 42 | print ("No clipboard image data was retrieved.") 43 | print ("These types were available:") 44 | print (pb.types()) 45 | -------------------------------------------------------------------------------- /Python3/Shell Scripts/listFilters.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # LIST FILTERS v.1.1 4 | # by Ben Byram-Wigfield 5 | # This will list all Quartz Filters known to the OS. 6 | # Note that the filter name is an internal value and not necessarily the filename. 7 | # Works with python3 8 | 9 | 10 | import Quartz as Quartz 11 | from Foundation import NSURL, NSString 12 | 13 | def listFilters(): 14 | Filters = (Quartz.QuartzFilterManager.filtersInDomains_(None)) 15 | for eachFilter in Filters: 16 | print (eachFilter.localizedName(), ':', eachFilter.url().fileSystemRepresentation().decode('UTF-8')) 17 | return 18 | 19 | 20 | listFilters() 21 | 22 | -------------------------------------------------------------------------------- /Python3/Shell Scripts/pagelayout.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # PAGE LAYOUT : Adds user-definable objects to a PDF page. 4 | # by Ben Byram-Wigfield v. 0.6 5 | # TO DO: 6 | # Add bitmap image or PDF file. 7 | # Include Rounded Rectangles and other BezierPaths. 8 | 9 | import os, sys 10 | import Quartz as Quartz 11 | from CoreText import (kCTFontAttributeName, CTFontCreateWithName, CTLineDraw, CTLineCreateWithAttributedString, kCTFontAttributeName, CTLineGetImageBounds) 12 | from CoreFoundation import (CFAttributedStringCreate, CFURLCreateFromFileSystemRepresentation, kCFAllocatorDefault) 13 | from math import pi as PI 14 | 15 | pageSize = [[0.,0.], [595.28, 841.88]] # A4 16 | whiteSwatch = [1.,1.,1.] 17 | redSwatch = [1.,0.,0.] 18 | blueSwatch = [0.,0.,1.] 19 | greenSwatch = [0.,1.,0.] 20 | blackSwatch = [0.,0.,0.] 21 | 22 | # Use inches instead of points e.g. "inch(1.5)" 23 | def inch(x): 24 | return 72.0*x 25 | 26 | # Use centimetres instead of points e.g. "cm(2.5)" 27 | def cm(x): 28 | return 28.25*x 29 | 30 | 31 | def makeRectangle(x, y, xSize, ySize, color, alpha): 32 | red, green, blue = color[:] 33 | Quartz.CGContextSetRGBFillColor (writeContext, red, green, blue, alpha) 34 | Quartz.CGContextFillRect (writeContext, Quartz.CGRectMake(x, y, xSize, ySize)) 35 | return 36 | 37 | 38 | def centerText(y, text, font, pointSize): 39 | typeStyle = CTFontCreateWithName(font, pointSize, None) 40 | astr = CFAttributedStringCreate(kCFAllocatorDefault, text, { kCTFontAttributeName : typeStyle }) 41 | line = CTLineCreateWithAttributedString(astr) 42 | textWidth = astr.size().width 43 | 44 | if line: 45 | x = (pageSize[1][0]-textWidth)/2 46 | # Quartz.CGContextSetAlpha(writeContext, opacity) 47 | Quartz.CGContextSetTextPosition(writeContext, x, y) 48 | CTLineDraw(line, writeContext) 49 | 50 | return 51 | 52 | def line(x, y, xSize, ySize, stroke, color, alpha): 53 | red, green, blue = color[:] 54 | Quartz.CGContextSetLineWidth(writeContext, stroke) 55 | Quartz.CGContextSetRGBStrokeColor(writeContext, red, green, blue, alpha) 56 | Quartz.CGContextMoveToPoint(writeContext, x, y) 57 | Quartz.CGContextAddLineToPoint(writeContext, x+xSize, y+ySize) 58 | Quartz.CGContextStrokePath(writeContext) 59 | return 60 | 61 | def circle(x, y, radius, color, alpha): 62 | red, green, blue = color[:] 63 | Quartz.CGContextSetRGBStrokeColor(writeContext, red, green, blue, alpha) 64 | Quartz.CGContextSetRGBFillColor(writeContext, red, green, blue, alpha) 65 | Quartz.CGContextAddArc(writeContext, x, y, radius, 0, 2*PI, 1) 66 | Quartz.CGContextClosePath(writeContext) 67 | Quartz.CGContextFillPath(writeContext) 68 | Quartz.CGContextSetLineWidth(writeContext, 2) 69 | Quartz.CGContextStrokePath(writeContext) 70 | return 71 | 72 | def addImage(x, y, path): 73 | # CGContextDrawImage(writeContext, rect, CGImageRef image) 74 | return 75 | 76 | def contextDone(context): 77 | if context: 78 | Quartz.CGPDFContextClose(context) 79 | del context 80 | 81 | def main(argv): 82 | global writeContext 83 | writeFilename = (os.path.expanduser("~/Desktop/Test.pdf")).encode('UTF-8') 84 | writeContext = Quartz.CGPDFContextCreateWithURL(CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, writeFilename, len(writeFilename), False), pageSize, None) 85 | Quartz.CGContextBeginPage(writeContext, pageSize) 86 | 87 | 88 | # HERE IS WHERE YOU WRITE YOUR PAGE! 89 | # ------------------------------------------------------------------ 90 | 91 | circle(100,100,100, blackSwatch, 0.5) 92 | circle(100,750,100, blackSwatch, 0.5) 93 | makeRectangle(100., 100., 400., 50., redSwatch, 0.75) 94 | makeRectangle(100., 700., 400., 50., greenSwatch, 0.75) 95 | line(100, 300, 400, 200, 12, blueSwatch, 1) 96 | circle(300.,400., 150., blueSwatch, 0.5) 97 | centerText(600, "Sample Text", "Helvetica-Bold", 12.0) 98 | 99 | # ------------------------------------------------------------------ 100 | 101 | Quartz.CGContextEndPage(writeContext) 102 | # Do tidying up 103 | contextDone(writeContext) 104 | 105 | if __name__ == "__main__": 106 | main(sys.argv[1:]) -------------------------------------------------------------------------------- /Python3/Shell Scripts/password.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # PASSWORD v1.0: Concept script to unlock encrypted PDFs, (if you know the password) 4 | # by Ben Byram-Wigfield 5 | # Currently, the script doesn't do anything with the unlocked data. But the code could be 6 | # added to other PDFSuite scripts in order to process locked PDFs. 7 | 8 | from Foundation import NSAppleScript, NSURL 9 | import Quartz as Quartz 10 | import sys 11 | 12 | # Sneaky call to AppleScript to get input from a dialog. 13 | def getTextFromDialog(): 14 | textOfMyScript = """ 15 | tell application "System Events" 16 | set myWords to "This PDF is protected" & return & "Please enter the password:" 17 | set theResponse to (display dialog myWords with title "Encrypted PDF" default answer "" buttons {"Cancel", "Continue"} default button 2 with icon 0 with hidden answer) 18 | end tell 19 | """ 20 | myScript = NSAppleScript.initWithSource_(NSAppleScript.alloc(), textOfMyScript) 21 | results, err = myScript.executeAndReturnError_(None) 22 | # results is an NSAppleEventDescriptor, which describes two things: 23 | # The button pressed (1), and the text returned (2). 24 | 25 | if not err: 26 | try: 27 | returnedInput = results.descriptorAtIndex_(2).stringValue() 28 | except: 29 | return None 30 | else: 31 | if returnedInput: 32 | return returnedInput 33 | else: 34 | return None 35 | else: 36 | print (err) 37 | return None 38 | 39 | 40 | def checkLock(infile): 41 | pdfURL = NSURL.fileURLWithPath_(infile) 42 | myPDF = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 43 | if myPDF: 44 | if myPDF.isLocked: 45 | print ("Locked") 46 | password = getTextFromDialog() 47 | if myPDF.unlockWithPassword_(password): 48 | print (infile, "Unlocked!") 49 | else: 50 | print ("Unable to unlock", infile) 51 | else: 52 | print ("No PDF data retrieved from", infile) 53 | 54 | if __name__ == '__main__': 55 | 56 | for filename in sys.argv[1:]: 57 | checkLock(filename) -------------------------------------------------------------------------------- /Python3/Shell Scripts/pdfsearch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # PDF TEXT SEARCH v1.0 4 | # by Ben Byram-Wigfield 5 | 6 | # Minimal function for searching text in a PDF for a string. 7 | # Useful safety tip: PDFKit's page index starts at zero 8 | 9 | import sys 10 | from Quartz import PDFDocument 11 | from Foundation import NSURL 12 | 13 | def pdfSearch(filepath, searchString): 14 | pdfURL = NSURL.fileURLWithPath_(filepath) 15 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 16 | if pdfDoc: 17 | searchResults = (pdfDoc.findString_withOptions_(searchString, 0)) 18 | if searchResults: 19 | for result in searchResults: 20 | eachPage = result.pages() 21 | print ("\'"+ searchString+"\' was found on page: "+str(pdfDoc.indexForPage_(eachPage[0])+1)) 22 | else: 23 | print("Nothing found.") 24 | else: 25 | print("Not a valid PDF.") 26 | return 27 | 28 | if __name__ == "__main__": 29 | # Set the filepath and searchString to your desired values 30 | filepath = '/path/to/file.pdf' 31 | searchString = 'word' 32 | pdfSearch(filepath, searchString) -------------------------------------------------------------------------------- /Python3/Shell Scripts/quartzfilter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # QUARTZFILTER v.1.6: Script to apply a MacOS Quartz Filter to a PDF file. 4 | # by Ben Byram-Wigfield 5 | # 6 | # quartzfilter.py 7 | # 8 | # The script will accept the bare name of a filter (WITH .qfilter) if file path not given. 9 | # E.g. quartzfilter.py /path/to/myPDF.pdf 'Sepia Tone.qfilter' /path/to/output.pdf 10 | 11 | import os, getopt, sys 12 | import Quartz as Quartz 13 | from Foundation import NSURL 14 | 15 | def checkFilter(name): 16 | if not os.path.split(name)[0]: 17 | Filters = (Quartz.QuartzFilterManager.filtersInDomains_(None)) 18 | found = False 19 | for eachFilter in Filters: 20 | filterPath = (eachFilter.url().fileSystemRepresentation()).decode('utf8') 21 | if name == os.path.split(filterPath)[1]: 22 | found = True 23 | break 24 | if found: 25 | return filterPath 26 | else: 27 | if os.path.exists(name): 28 | return name 29 | 30 | def main(argv): 31 | inputfile = "" 32 | outputfile = "" 33 | filter = "" 34 | 35 | try: 36 | opts, args = getopt.getopt(sys.argv[1:], "ifo", ["input", "filter", "output"]) 37 | except getopt.GetoptError as err: 38 | print(err) 39 | usage() 40 | sys.exit(2) 41 | 42 | if len(args) != 3: 43 | print("Wrong number of arguments") 44 | sys.exit(2) 45 | 46 | inputfile =args[0] 47 | if not inputfile: 48 | print ('Unable to open input file') 49 | sys.exit(2) 50 | 51 | filter = args[1] 52 | filter = checkFilter(filter) 53 | if not filter: 54 | print ('Unable to find Quartz Filter') 55 | sys.exit(2) 56 | 57 | outputfile = args[2] 58 | if not outputfile: 59 | print ('No valid output file specified') 60 | sys.exit(2) 61 | # You could just take the inputfile as the outputfile if not explicitly given. 62 | # outputfile = inputfile 63 | 64 | pdfURL = NSURL.fileURLWithPath_(inputfile) 65 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 66 | if not pdfDoc: 67 | print("Can't get PDF file. Make sure input is valid.") 68 | sys.exit(2) 69 | filterURL = NSURL.fileURLWithPath_(filter) 70 | value = Quartz.QuartzFilter.quartzFilterWithURL_(filterURL) 71 | dict = { 'QuartzFilter': value } 72 | pdfDoc.writeToFile_withOptions_(outputfile, dict) 73 | 74 | if __name__ == "__main__": 75 | main(sys.argv[1:]) -------------------------------------------------------------------------------- /Python3/addpage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # ADDPAGE v.3 : Adds a blank page to the END of any PDF file(s) sent as arguments. 4 | 5 | # by Ben Byram-Wigfield. 6 | 7 | # Rewritten for python3. You may need to pip install pyobjc. 8 | 9 | # Page size of blank page is taken from first page of PDF. 10 | # Can be used as Automator action or as shell script. 11 | 12 | 13 | from Quartz import PDFDocument, PDFPage, kPDFDisplayBoxMediaBox 14 | import sys 15 | from Foundation import NSURL 16 | 17 | # kPDFDisplayBoxMediaBox = 0 ; crop = 1; bleed = 2; trim = 3; artbox = 4 18 | mediabox = kPDFDisplayBoxMediaBox 19 | 20 | def addPage(filename): 21 | # filename = filename.decode('utf-8') 22 | pdfURL = NSURL.fileURLWithPath_(filename) 23 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 24 | if pdfDoc: 25 | pageNum = pdfDoc.pageCount() 26 | page = pdfDoc.pageAtIndex_(0) 27 | pageSize = page.boundsForBox_(mediabox) 28 | blankPage = PDFPage.alloc().init() 29 | blankPage.setBounds_forBox_(pageSize, mediabox) 30 | pdfDoc.insertPage_atIndex_(blankPage, pageNum) 31 | pdfDoc.writeToFile_(filename) 32 | return 33 | 34 | if __name__ == '__main__': 35 | for filename in sys.argv[1:]: 36 | addPage(filename) -------------------------------------------------------------------------------- /Python3/bookletPDF.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # ---------------------------------------------------------------- 4 | # PDF Booklet Imposition Script for MacOS v3.0 (Automator) 5 | # by Ben Byram-Wigfield 6 | # Feel free to use, modify and pass on with acknowledgement. 7 | # 8 | # Usage: As a script in Automator, or: bookletPDF.py ... 9 | # Original file is preserved, and output file has suffix added. 10 | # NB: The script will overwrite existing output file of the same name. 11 | # 12 | # Script can be configured for stacking 4pp signatures or gathering booklet spreads. 13 | # ---------------------------------------------------------------- 14 | 15 | # Beware of indexes starting from 0 and 1...!!! CGPDFDocument starts page count at 1. 16 | 17 | import os, sys 18 | import copy 19 | import Quartz as Quartz 20 | from Foundation import (NSURL, kCFAllocatorDefault) 21 | 22 | # Uncomment the sheet sizes you want. 23 | A3 = [[0,0], [1190.55, 841.88]] 24 | A4 = [[0,0], [841.88, 595.28]] 25 | # USLetter = [[0,0], [792, 612]] 26 | # Tabloid = [[0,0], [1224, 792]] 27 | 28 | # OPTIONS 29 | # Change this to one of the sizes listed above, if you want. 30 | sheetSize = A4 31 | 32 | # Set file suffix 33 | suffix = " booklet.pdf" 34 | # If hasSignatures, sheets will be arranged for stacking in 4pp sections. 35 | hasSignatures = False 36 | pagesPerSheet = 4 # Not sure what will happen if this is changed. 37 | creep = 0.5 # in points. NB: Eventually, the pages will collide. 38 | imposedOrder = [] 39 | 40 | # FUNCTIONS 41 | # Loads in PDF document 42 | def createPDFDocumentFromPath(path): 43 | url = NSURL.fileURLWithPath_(path) 44 | return Quartz.CGPDFDocumentCreateWithURL(url) 45 | 46 | # Creates a Context for drawing 47 | def createOutputContextWithPath(path, dictarray): 48 | url = NSURL.fileURLWithPath_(path) 49 | return Quartz.CGPDFContextCreateWithURL(url, None, dictarray) 50 | 51 | # Get page sequence for imposition order (e.g. 4,1,2,3) 52 | def imposition(pageRange): 53 | for i in range(1, (int(len(pageRange)/2)), 2): 54 | # First we do recto 55 | imposedOrder.append(pageRange[i*-1]) 56 | imposedOrder.append(pageRange[i-1]) 57 | # And now we do verso 58 | imposedOrder.append(pageRange[i]) 59 | imposedOrder.append(pageRange[(i+1)*-1]) 60 | return imposedOrder 61 | 62 | # Make sure pages are portrait in orientation. 63 | def getRotation(pdfpage): 64 | displayAngle = 0 65 | rotValue = Quartz.CGPDFPageGetRotationAngle(pdfpage) 66 | mediaBox = Quartz.CGPDFPageGetBoxRect(pdfpage, Quartz.kCGPDFMediaBox) 67 | if not Quartz.CGRectIsEmpty(mediaBox): 68 | x = Quartz.CGRectGetWidth(mediaBox) 69 | y = Quartz.CGRectGetHeight(mediaBox) 70 | if (x > y): displayAngle = -90 71 | displayAngle -= rotValue 72 | return displayAngle 73 | 74 | # Gets DocInfo from input file to pass to output. 75 | # PyObjC returns Keywords in an NSArray; they must be tupled. 76 | def getDocInfo(file): 77 | pdfURL = NSURL.fileURLWithPath_(file) 78 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 79 | if pdfDoc: 80 | metadata = pdfDoc.documentAttributes() 81 | if "Keywords" in metadata: 82 | keys = metadata["Keywords"] 83 | mutableMetadata = metadata.mutableCopy() 84 | mutableMetadata["Keywords"] = tuple(keys) 85 | return mutableMetadata 86 | else: 87 | return metadata 88 | 89 | # Close Context when finished 90 | def contextDone(context): 91 | if context: 92 | Quartz.CGPDFContextClose(context) 93 | del context 94 | 95 | # MAIN 96 | def makeBooklet(argv): 97 | 98 | leftPage = copy.deepcopy(sheetSize) 99 | shift = sheetSize[1][0]/2 100 | leftPage[1][0] = shift 101 | rightPage = copy.deepcopy(leftPage) 102 | rightPage[0][0] = shift 103 | blanks = 0 104 | 105 | # Initiate new PDF, get source PDF data, number of pages. 106 | shortName = os.path.splitext(argv)[0] 107 | writeFilename = shortName + suffix 108 | metaDict = getDocInfo(argv) 109 | writeContext = createOutputContextWithPath(writeFilename, metaDict) 110 | source = createPDFDocumentFromPath(argv) 111 | totalPages = Quartz.CGPDFDocumentGetNumberOfPages(source) 112 | 113 | # Add 0 to Unsorted Order for each blank page required to be a multiple of pages per sheet. 114 | UnsortedOrder = list(range(1, totalPages+1)) 115 | if totalPages%pagesPerSheet: 116 | blanks = pagesPerSheet - (totalPages%pagesPerSheet) 117 | for i in range(blanks): 118 | UnsortedOrder.append(0) 119 | totalPages = len(UnsortedOrder) 120 | 121 | if hasSignatures: 122 | signatureSize = pagesPerSheet * 4 123 | else: 124 | signatureSize = totalPages 125 | 126 | for something in range(0, totalPages, signatureSize): 127 | imposition(UnsortedOrder[something:(something+signatureSize)]) 128 | 129 | # For each side of the sheet, we must... 130 | # ... create a PDF page, take two source pages and place them differently, then close the page. 131 | # If the source page number is 0 (i.e. blank), then move on without drawing. 132 | Sides = int(totalPages/2) 133 | count = 0 134 | for n in range(Sides): 135 | Quartz.CGContextBeginPage(writeContext, sheetSize) 136 | for position in [leftPage, rightPage]: 137 | if imposedOrder[count]: 138 | page = Quartz.CGPDFDocumentGetPage(source, imposedOrder[count]) 139 | Quartz.CGContextSaveGState(writeContext) 140 | # Check PDF page rotation AND mediabox orientation. 141 | angle = getRotation(page) 142 | Quartz.CGContextConcatCTM(writeContext, Quartz.CGPDFPageGetDrawingTransform(page, Quartz.kCGPDFMediaBox, position, angle, True)) 143 | # Uncomment next line to draw box round each page 144 | # Quartz.CGContextStrokeRectWithWidth(writeContext, leftPage, 2.0) 145 | Quartz.CGContextDrawPDFPage(writeContext, page) 146 | Quartz.CGContextRestoreGState(writeContext) 147 | count += 1 148 | Quartz.CGContextEndPage(writeContext) 149 | 150 | # Set creep for next sheet. 151 | if count%4 == 0: 152 | leftPage[0][0] += creep 153 | rightPage[0][0] -= creep 154 | 155 | # Do tidying up 156 | contextDone(writeContext) 157 | 158 | if __name__ == "__main__": 159 | for filename in sys.argv[1:]: 160 | makeBooklet(filename) -------------------------------------------------------------------------------- /Python3/countpages.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # COUNTPAGES: Adds together the sum of pages from all PDFs supplied as arguments. 4 | # by Ben Byram-Wigfield v.2.0 5 | 6 | # Uses an Alert dialog to report the number! 7 | 8 | import sys 9 | from Quartz import PDFDocument 10 | from AppKit import (NSApp, NSAlert, NSInformationalAlertStyle, NSURL) 11 | 12 | pdfnum=0 13 | 14 | def displayAlert(message, info, buttons): 15 | alert = NSAlert.alloc().init() 16 | alert.setMessageText_(message) 17 | alert.setInformativeText_(info) 18 | alert.setAlertStyle_(NSInformationalAlertStyle) 19 | for button in buttons: 20 | alert.addButtonWithTitle_(button) 21 | NSApp.activateIgnoringOtherApps_(True) 22 | buttonPressed = alert.runModal() 23 | return buttonPressed 24 | # First button will return 1000, second 1001, etc.. 25 | 26 | def pageCount(pdfPath): 27 | # pdfPath = pdfPath.decode('utf-8') 28 | pdfURL = NSURL.fileURLWithPath_(pdfPath) 29 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 30 | if pdfDoc: 31 | return pdfDoc.pageCount() 32 | 33 | if __name__ == '__main__': 34 | 35 | for filename in sys.argv[1:]: 36 | pdfnum=pdfnum+pageCount(filename) 37 | 38 | displayAlert("Combined Page Count:", str(pdfnum), ["OK"]) 39 | 40 | # Or just print the number to stdout. 41 | # print(pdfnum) -------------------------------------------------------------------------------- /Python3/cropPDF.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # CROP PDF v.1.2 : Crop the mediabox by a given set of margins. 4 | # by Ben Byram-Wigfield v1.2 5 | 6 | # Use on the Command line with filenames as arguments (e.g. cropPDF.py /path/to/file.pdf) 7 | # Or in Automator as a Quick Action/Service. Select "Receives PDF Files in Finder" 8 | # Then Add "Run Shell Script" action. Select /usr/bin/env python3 from the drop-down list; 9 | # Select "Pass Input" as "as arguments". 10 | # Paste script into area for scripts, replacing existing text. 11 | 12 | import sys 13 | import os 14 | from Quartz import PDFDocument, kPDFDisplayBoxMediaBox, kPDFDisplayBoxTrimBox, CGRectEqualToRect, CGRectMake 15 | from CoreFoundation import NSURL 16 | 17 | mediabox = kPDFDisplayBoxMediaBox 18 | # Margins: left margin, bottom margin, right margin, top margin. 19 | margins = [45, 45, 45, 45] 20 | 21 | def trimPDF(filename): 22 | # filename = filename.decode('utf-8') 23 | shortName = os.path.splitext(filename)[0] 24 | outFilename = shortName + " TPS.pdf" 25 | pdfURL = NSURL.fileURLWithPath_(filename) 26 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 27 | if pdfDoc: 28 | pages = pdfDoc.pageCount() 29 | for p in range(0, pages): 30 | page = pdfDoc.pageAtIndex_(p) 31 | mediaBoxSize = page.boundsForBox_(mediabox) 32 | trimBoxSize = CGRectMake(margins[0], margins[1], (mediaBoxSize.size.width - margins[2] - margins[0]), (mediaBoxSize.size.height - margins[3] - margins[1])) 33 | page.setBounds_forBox_(trimBoxSize, mediabox) 34 | 35 | pdfDoc.writeToFile_(outFilename) 36 | 37 | if __name__ == '__main__': 38 | for filename in sys.argv[1:]: 39 | trimPDF(filename) 40 | -------------------------------------------------------------------------------- /Python3/encrypt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | # ENCRYPT : Encrypt PDF and lock with password. 5 | # by Ben Byram-Wigfield v.1.1 6 | # but copying or extracting requires the Owner Password. 7 | # WARNING: Some versions of OS X (High Sierra) corrupt PDF metadata after encryption. 8 | 9 | import os, sys 10 | from Quartz import PDFDocument, kCGPDFContextAllowsCopying, kCGPDFContextAllowsPrinting, kCGPDFContextUserPassword, kCGPDFContextOwnerPassword 11 | from CoreFoundation import (NSURL) 12 | 13 | copyPassword = "12345678" # Password for copying and printing 14 | # openPassword = copyPassword # Or enter a different password to open the file. 15 | openPassword = '' # to allow opening. 16 | 17 | def encrypt(filename): 18 | if not filename: 19 | print ('Unable to open input file') 20 | sys.exit(2) 21 | shortName = os.path.splitext(filename)[0] 22 | outputfile = shortName+" locked.pdf" 23 | pdfURL = NSURL.fileURLWithPath_(filename) 24 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 25 | if pdfDoc : 26 | options = { 27 | kCGPDFContextAllowsCopying: False, 28 | kCGPDFContextAllowsPrinting: False, 29 | kCGPDFContextOwnerPassword: copyPassword, 30 | kCGPDFContextUserPassword: openPassword} 31 | pdfDoc.writeToFile_withOptions_(outputfile, options) 32 | return 33 | 34 | if __name__ == "__main__": 35 | for filename in sys.argv[1:]: 36 | encrypt(filename) -------------------------------------------------------------------------------- /Python3/graphpaper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # GRAPHPAPER: v3.0 4 | # Modified from /System/Library/Automator/Add Grid to PDF Documents.action/Contents/Resources/graphpaper.py 5 | # to include small and large gradations; better handling of existing metadata; and made faster! 6 | # Now works for python3 7 | 8 | import sys 9 | import os 10 | import Quartz as Quartz 11 | from Foundation import NSURL, kCFAllocatorDefault 12 | 13 | # Loads in PDF document 14 | def createPDFDocumentFromPath(path): 15 | url = NSURL.fileURLWithPath_(path) 16 | return Quartz.CGPDFDocumentCreateWithURL(url) 17 | 18 | # Creates a Context for drawing 19 | def createOutputContextWithPath(path, dictarray): 20 | url = NSURL.fileURLWithPath_(path) 21 | return Quartz.CGPDFContextCreateWithURL(url, None, dictarray) 22 | 23 | # Gets DocInfo from input file to pass to output. 24 | # PyObjC returns Keywords in an NSArray; they must be tupled. 25 | def getDocInfo(file): 26 | pdfURL = NSURL.fileURLWithPath_(file) 27 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 28 | if pdfDoc: 29 | metadata = pdfDoc.documentAttributes() 30 | if "Keywords" in metadata: 31 | keys = metadata["Keywords"] 32 | mutableMetadata = metadata.mutableCopy() 33 | mutableMetadata["Keywords"] = tuple(keys) 34 | return mutableMetadata 35 | else: 36 | return metadata 37 | 38 | def drawLines(pdf, mediaBox, grid, red, green, blue, alpha) : 39 | Quartz.CGContextSetRGBStrokeColor(pdf, red, green, blue, alpha) 40 | x = mediaBox.origin.x 41 | count = -1 42 | while x <= mediaBox.size.width: 43 | count += 1 44 | if not count%grid[1]: 45 | Quartz.CGContextSetLineWidth(pdf, 0.5) 46 | else: 47 | Quartz.CGContextSetLineWidth(pdf, 0.1) 48 | Quartz.CGContextMoveToPoint(pdf, x, mediaBox.origin.y) 49 | Quartz.CGContextAddLineToPoint(pdf, x, mediaBox.origin.y + mediaBox.size.height) 50 | Quartz.CGContextStrokePath(pdf) 51 | x += grid[0] 52 | 53 | y = mediaBox.origin.y 54 | count = -1 55 | while y < mediaBox.size.height: 56 | count += 1 57 | if not count%grid[1]: 58 | Quartz.CGContextSetLineWidth(pdf, 0.5) 59 | else: 60 | Quartz.CGContextSetLineWidth(pdf, 0.1) 61 | Quartz.CGContextMoveToPoint(pdf, mediaBox.origin.x, y) 62 | Quartz.CGContextAddLineToPoint(pdf, mediaBox.origin.x + mediaBox.size.width, y) 63 | Quartz.CGContextStrokePath(pdf) 64 | y += grid[0] 65 | 66 | def makeGrid(filename): 67 | 68 | # The distance, in points, between each grid line, and frequency of thicker lines: 69 | # Default is 10pt, 10 (Useful for determining values in points) 70 | # Other useful values are: 71 | # [12, 6] (Points and Inches) 72 | # [9, 8] (1/8ths and Inches) 73 | # [2.835, 10] (mm, cm) 74 | gridSize = [10,10] 75 | 76 | # The color of the grid lines 77 | # Default is dark Yellow 78 | red = 1 79 | green = 0.6 80 | blue = 0.1 81 | alpha = 1 82 | 83 | over = 1 84 | 85 | writeContext = None 86 | 87 | shortName = os.path.splitext(filename)[0] 88 | outFilename = shortName + "+grid.pdf" 89 | metaDict = getDocInfo(filename) 90 | 91 | writeContext = createOutputContextWithPath(outFilename, metaDict) 92 | readPDF = createPDFDocumentFromPath(filename) 93 | 94 | if writeContext != None and readPDF != None: 95 | numPages = Quartz.CGPDFDocumentGetNumberOfPages(readPDF) 96 | for pageNum in range(1, numPages + 1): 97 | page = Quartz.CGPDFDocumentGetPage(readPDF, pageNum) 98 | if page: 99 | mediaBox = Quartz.CGPDFPageGetBoxRect(page, Quartz.kCGPDFMediaBox) 100 | if Quartz.CGRectIsEmpty(mediaBox): 101 | mediaBox = None 102 | Quartz.CGContextBeginPage(writeContext, mediaBox) 103 | if (not over) : 104 | drawLines(writeContext, mediaBox, gridSize, red, green, blue, alpha) 105 | Quartz.CGContextDrawPDFPage(writeContext, page) 106 | if (over) : 107 | drawLines(writeContext, mediaBox, gridSize, red, green, blue, alpha) 108 | Quartz.CGContextEndPage(writeContext) 109 | Quartz.CGPDFContextClose(writeContext) 110 | del writeContext 111 | 112 | else: 113 | print ("A valid input file and output file must be supplied.") 114 | sys.exit(1) 115 | 116 | if __name__ == "__main__": 117 | for filename in sys.argv[1:]: 118 | makeGrid(filename) 119 | -------------------------------------------------------------------------------- /Python3/image2pdf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # IMAGE2PDF v.3.0 : Convert image files to one PDF. 4 | # by Ben Byram-Wigfield 5 | # Rewritten using PDFKit. Now for python 3 6 | 7 | import sys, os 8 | import Quartz as Quartz 9 | from LaunchServices import kUTTypePDF 10 | from CoreFoundation import NSImage 11 | 12 | def getFilename(filepath, basename): 13 | fullname = basename + ".pdf" 14 | i=0 15 | while os.path.exists(os.path.join(filepath, fullname)): 16 | i += 1 17 | fullname = basename + " %02d.pdf"%i 18 | return os.path.join(filepath, fullname) 19 | 20 | 21 | def imageToPdf(argv): 22 | prefix = os.path.dirname(argv[0]) 23 | filename = "Combined" 24 | pdfout = getFilename(prefix, filename) 25 | 26 | for index, eachFile in enumerate(argv): 27 | 28 | image = NSImage.alloc().initWithContentsOfFile_(eachFile) 29 | if image: 30 | page = Quartz.PDFPage.alloc().initWithImage_(image) 31 | if index == 0: 32 | pageData = page.dataRepresentation() 33 | pdf = Quartz.PDFDocument.alloc().initWithData_(pageData) 34 | else: 35 | pdf.insertPage_atIndex_(page, index) 36 | 37 | pdf.writeToFile_(pdfout) 38 | 39 | 40 | if __name__ == "__main__": 41 | imageToPdf(sys.argv[1:]) -------------------------------------------------------------------------------- /Python3/indexnumbers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # INDEX NUMBERS v.3.0 - for python3 4 | # This script stamps "N of X" on the first page of all PDF documents passed to it, 5 | # where N is the sequential number of each document and X is the total. 6 | # by Ben Byram-Wigfield 7 | # Options for position, size, font are below. 8 | 9 | 10 | import sys, os, math 11 | import Quartz as Quartz 12 | from CoreText import (kCTFontAttributeName, CTFontCreateWithName, CTLineDraw, CTLineCreateWithAttributedString, kCTFontAttributeName, CTLineGetImageBounds) 13 | from CoreFoundation import (CFAttributedStringCreate, CFURLCreateFromFileSystemRepresentation, kCFAllocatorDefault, NSURL) 14 | from AppKit import NSFontManager 15 | 16 | 17 | # Creates a PDF Object from incoming file. 18 | def createPDFDocumentFromPath(path): 19 | url = NSURL.fileURLWithPath_(path) 20 | return Quartz.CGPDFDocumentCreateWithURL(url) 21 | 22 | # Creates a Context for drawing 23 | def createOutputContextWithPath(path, dictarray): 24 | url = NSURL.fileURLWithPath_(path) 25 | return Quartz.CGPDFContextCreateWithURL(url, None, dictarray) 26 | 27 | # Gets DocInfo from input file to pass to output. 28 | # PyObjC returns Keywords in an NSArray; they must be tupled. 29 | def getDocInfo(file): 30 | pdfURL = NSURL.fileURLWithPath_(file) 31 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 32 | if pdfDoc: 33 | metadata = pdfDoc.documentAttributes() 34 | if "Keywords" in metadata: 35 | keys = metadata["Keywords"] 36 | mutableMetadata = metadata.mutableCopy() 37 | mutableMetadata["Keywords"] = tuple(keys) 38 | return mutableMetadata 39 | else: 40 | return metadata 41 | 42 | # Closes the Context 43 | def contextDone(context): 44 | if context: 45 | Quartz.CGPDFContextClose(context) 46 | del context 47 | 48 | def drawWatermarkText(writeContext, line, xOffset, yOffset, angle, scale, opacity): 49 | if line: 50 | rect = CTLineGetImageBounds(line, writeContext) 51 | imageWidth = rect.size.width 52 | imageHeight = rect.size.height 53 | 54 | Quartz.CGContextSaveGState(writeContext) 55 | Quartz.CGContextSetAlpha(writeContext, opacity) 56 | Quartz.CGContextTranslateCTM(writeContext, xOffset, yOffset) 57 | Quartz.CGContextTranslateCTM(writeContext, imageWidth / 2, imageHeight / 2) 58 | Quartz.CGContextRotateCTM(writeContext, angle * math.pi / 180) 59 | Quartz.CGContextTranslateCTM(writeContext, -imageWidth / 2, -imageHeight / 2) 60 | Quartz.CGContextScaleCTM(writeContext, scale, scale) 61 | Quartz.CGContextSetTextPosition(writeContext, 0.0, 0.0) 62 | CTLineDraw(line, writeContext) 63 | Quartz.CGContextRestoreGState(writeContext) 64 | 65 | # Check that the selected font is active, else use Helvetica Bold. 66 | def selectFont(typeface, pointSize): 67 | manager = NSFontManager.sharedFontManager() 68 | fontList = list(manager.availableFonts()) 69 | if typeface not in fontList: 70 | typeface = 'Helvetica-Bold' 71 | return CTFontCreateWithName(typeface, pointSize, None) 72 | 73 | 74 | if __name__ == '__main__': 75 | 76 | 77 | # OPTIONS: Set the distance in points from bottom left corner of page; 78 | # For other uses, set the angle, scale, and opacity of text 79 | # Font must be the PostScript name (i.e. no spaces) (See Get Info in FontBook) 80 | xOffset, yOffset, angle, scale, opacity = 45.0, 800.0, 0.0, 1.0, 1.0 81 | font = selectFont('Helvetica-Bold', 12.0) 82 | 83 | 84 | for index, filename in enumerate(sys.argv[1:], start = 1): 85 | # Get path, create new folder. 86 | # Currently script will stop if folder exists. 87 | totalCount = len(sys.argv[1:]) 88 | text = str(index) + " of " + str(totalCount) 89 | if index == 1: 90 | dirPath = os.path.dirname(filename) 91 | location = os.path.join(dirPath, "Indexed") 92 | try: 93 | os.mkdir(location) 94 | except: 95 | print ("Can't create directory '%s'"%(location)) 96 | sys.exit() 97 | nameOnly = os.path.basename(filename) 98 | outFilename = os.path.join(location, nameOnly) 99 | pdf = createPDFDocumentFromPath(filename) 100 | pages = Quartz.CGPDFDocumentGetNumberOfPages(pdf) 101 | metaDict = getDocInfo(filename) 102 | writeContext = createOutputContextWithPath(outFilename, metaDict) 103 | 104 | # Write page 1 with the added text 105 | if pdf: 106 | page = Quartz.CGPDFDocumentGetPage(pdf, 1) 107 | if page: 108 | mbox = Quartz.CGPDFPageGetBoxRect(page, Quartz.kCGPDFMediaBox) 109 | # if Quartz.CGRectIsEmpty(mbox): mbox = None 110 | Quartz.CGContextBeginPage(writeContext, mbox) 111 | Quartz.CGContextDrawPDFPage(writeContext, page) 112 | astr = CFAttributedStringCreate(kCFAllocatorDefault, text, { kCTFontAttributeName : font }) 113 | line = CTLineCreateWithAttributedString(astr) 114 | drawWatermarkText(writeContext, line, xOffset , yOffset, angle, scale, opacity) 115 | Quartz.CGContextEndPage(writeContext) 116 | 117 | # Write out the rest of the pages 118 | for i in range(2, (pages+1)): 119 | page = Quartz.CGPDFDocumentGetPage(pdf, i) 120 | if page: 121 | Quartz.CGContextBeginPage(writeContext, mbox) 122 | Quartz.CGContextDrawPDFPage(writeContext, page) 123 | Quartz.CGContextEndPage(writeContext) 124 | del pdf 125 | contextDone(writeContext) -------------------------------------------------------------------------------- /Python3/joinpdfs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # JOINPDFS v3.0 : Tool to concatenate PDFs for python3 4 | # New tool built from the ground up using PDFKit, instead of Core Graphics. 5 | # Now writes Table of Contents for each file added; importing existing ToCs in each file! 6 | # by Ben Byram-Wigfield 7 | 8 | import sys 9 | import os.path 10 | from CoreFoundation import (CFURLCreateFromFileSystemRepresentation, kCFAllocatorDefault, NSURL, NSString) 11 | import Quartz as Quartz 12 | 13 | def createPDFDocumentWithPath(path): 14 | pdfURL = NSURL.fileURLWithPath_(path) 15 | if pdfURL: 16 | return Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 17 | 18 | def getFilename(filepath, basename): 19 | fullname = basename + ".pdf" 20 | i=0 21 | while os.path.exists(os.path.join(filepath, fullname)): 22 | i += 1 23 | fullname = basename + " %02d.pdf"%i 24 | return os.path.join(filepath, fullname) 25 | 26 | 27 | def getOutline(page, label, pageObject): 28 | pageSize = pageObject.boundsForBox_(Quartz.kCGPDFMediaBox) 29 | x = 0 30 | y = Quartz.CGRectGetHeight(pageSize) 31 | pagePoint = Quartz.CGPointMake(x,y-1) 32 | myDestination = Quartz.PDFDestination.alloc().initWithPage_atPoint_(pageObject, pagePoint) 33 | myLabel = NSString.stringWithString_(label) 34 | myOutline = Quartz.PDFOutline.alloc().init() 35 | myOutline.setLabel_(myLabel) 36 | myOutline.setDestination_(myDestination) 37 | return myOutline 38 | 39 | def join(incomingFiles): 40 | 41 | # Set the output file location and name. 42 | prefix = os.path.dirname(incomingFiles[0]) 43 | filename = "Combined" 44 | outfile = getFilename(prefix, filename) 45 | # Load in the first PDF file, to which the rest will be added. 46 | firstPDF = createPDFDocumentWithPath(incomingFiles[0]) 47 | outlineIndex = 0 48 | rootOutline = Quartz.PDFOutline.alloc().init() 49 | firstLabel = os.path.basename(incomingFiles[0]) 50 | firstPage = firstPDF.pageAtIndex_(0) 51 | firstOutline = getOutline(0, firstLabel, firstPage) 52 | # Check and copy existing Outlines into new structure. 53 | existingOutline = firstPDF.outlineRoot() 54 | if existingOutline: 55 | i=0 56 | while i < (existingOutline.numberOfChildren()): 57 | childOutline = existingOutline.childAtIndex_(i) 58 | firstOutline.insertChild_atIndex_(childOutline, i) 59 | i +=1 60 | rootOutline.insertChild_atIndex_(firstOutline, outlineIndex) 61 | 62 | 63 | # create PDFDocument object for the remaining files. 64 | pdfObjects = list(map(createPDFDocumentWithPath, incomingFiles[1:])) 65 | for index, doc in enumerate(pdfObjects): 66 | if doc: 67 | pages = doc.pageCount() 68 | pageIndex = firstPDF.pageCount() 69 | tocLabel = os.path.basename(incomingFiles[index+1]) 70 | for p in range(0, pages): 71 | page = doc.pageAtIndex_(p) 72 | firstPDF.insertPage_atIndex_(page, pageIndex+p) 73 | if p == 0: 74 | outline = getOutline(pageIndex, tocLabel, page) 75 | existingOutline = doc.outlineRoot() 76 | if existingOutline: 77 | i=0 78 | while i < (existingOutline.numberOfChildren()): 79 | childOutline = existingOutline.childAtIndex_(i) 80 | outline.insertChild_atIndex_(childOutline, i) 81 | i +=1 82 | 83 | rootOutline.insertChild_atIndex_(outline, index+1) 84 | # Add the root Outline to the first PDF. 85 | firstPDF.setOutlineRoot_(rootOutline) 86 | firstPDF.writeToFile_(outfile) 87 | 88 | if __name__ == "__main__": 89 | if len(sys.argv) > 1: 90 | join(sys.argv[1:]) -------------------------------------------------------------------------------- /Python3/makePDFX.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Make PDFX: Create a PDF/X-3 compliant document 4 | # v.3.0 Now written in python 3 5 | # 6 | 7 | import sys 8 | import os 9 | import Quartz as Quartz 10 | from CoreFoundation import (NSURL, QuartzFilter) 11 | 12 | filterpath = os.path.expanduser("~/Library/Filters/Better PDFX-3.qfilter") 13 | if not os.path.exists(filterpath): 14 | filterpath = "/System/Library/Filters/Create Generic PDFX-3 Document.qfilter" 15 | print ("Using System filter, which is not very good") 16 | 17 | for inputfile in sys.argv[1:]: 18 | prefix = os.path.splitext(inputfile) 19 | outfile = prefix[0] + 'X.pdf' 20 | pdfURL = NSURL.fileURLWithPath_(inputfile) 21 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 22 | if pdfDoc: 23 | filterURL = NSURL.fileURLWithPath_(filterpath) 24 | value = QuartzFilter.quartzFilterWithURL_(filterURL) 25 | options = { 'QuartzFilter': value } 26 | pdfDoc.writeToFile_withOptions_(outfile, options) 27 | -------------------------------------------------------------------------------- /Python3/metadata.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # METADATA : Add or Change metadata to a PDF file. 4 | # by Ben Byram-Wigfield 5 | # Re-written for python3 6 | import sys 7 | import os 8 | import getopt 9 | import Quartz.CoreGraphics as Quartz 10 | 11 | from CoreFoundation import NSURL 12 | 13 | def setMetadata(filename): 14 | options = {} 15 | author='Ben Byram-Wigfield' 16 | creator = 'PDFSuite Python Scripts' 17 | subject = '' 18 | keywords = 'PDF Magic' 19 | 20 | # Get Title from filename. Or delete these two lines and set a string value. 21 | title = os.path.basename(filename) 22 | title = os.path.splitext(title)[0] 23 | 24 | authorKey = Quartz.kCGPDFContextAuthor 25 | creatorKey = Quartz.kCGPDFContextCreator 26 | subjectKey = Quartz.kCGPDFContextSubject 27 | keywordsKey = Quartz.kCGPDFContextKeywords 28 | titleKey = Quartz.kCGPDFContextTitle 29 | 30 | shortName = os.path.splitext(filename)[0] 31 | pdfURL = NSURL.fileURLWithPath_(filename) 32 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 33 | 34 | # Default value option: 35 | if author: 36 | options[authorKey] = author 37 | if creator: 38 | options[creatorKey] = creator 39 | if subject: 40 | options[subjectKey] = subject 41 | if keywords: 42 | options[keywordsKey] = keywords 43 | if title: 44 | options[titleKey] = title 45 | 46 | print (options) 47 | 48 | # To save to a separate file, uncomment the next line. 49 | # filename = shortName + " data.pdf" 50 | pdfDoc.writeToFile_withOptions_(filename, options) 51 | 52 | if __name__ == "__main__": 53 | for filepath in sys.argv[1:]: 54 | setMetadata(filepath) 55 | 56 | """ 57 | Dict keys include: 58 | 59 | kCGPDFContextAuthor (string) 60 | kCGPDFContextCreator 61 | kCGPDFContextTitle 62 | 63 | 64 | kCGPDFContextOwnerPassword 65 | kCGPDFContextUserPassword 66 | kCGPDFContextAllowsPrinting (boolean) 67 | kCGPDFContextAllowsCopying (boolean) 68 | 69 | kCGPDFContextOutputIntent 70 | kCGPDFContextOutputIntents 71 | kCGPDFContextSubject 72 | kCGPDFContextKeywords 73 | kCGPDFContextEncryptionKeyLength 74 | 75 | kCGPDFXOutputIntentSubtype 76 | kCGPDFXOutputConditionIdentifier 77 | kCGPDFXOutputCondition 78 | kCGPDFXRegistryName 79 | kCGPDFXInfo 80 | kCGPDFXDestinationOutputProfile 81 | 82 | See the Apple Documentation page on Auxiliary Dictionary Keys for PDF Context for more. 83 | 84 | """ -------------------------------------------------------------------------------- /Python3/pagenumber.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | # PAGE NUMBER v.3.0 - re-written for python 3 5 | # This script places page numbers on facing pages (excluding page 1). Options for position, size, font are below. 6 | # By Ben Byram-Wigfield. 7 | 8 | import sys, os, math 9 | import Quartz as Quartz 10 | from CoreText import (kCTFontAttributeName, CTFontCreateWithName, CTLineDraw, CTLineCreateWithAttributedString, kCTFontAttributeName, CTLineGetImageBounds) 11 | from CoreFoundation import (CFAttributedStringCreate, CFURLCreateFromFileSystemRepresentation, kCFAllocatorDefault, NSURL) 12 | from AppKit import NSFontManager 13 | 14 | # Creates a PDF Object from incoming file. 15 | def createPDFDocumentFromPath(path): 16 | url = NSURL.fileURLWithPath_(path) 17 | return Quartz.CGPDFDocumentCreateWithURL(url) 18 | 19 | # Creates a Context for drawing 20 | def createOutputContextWithPath(path, dictarray): 21 | url = NSURL.fileURLWithPath_(path) 22 | return Quartz.CGPDFContextCreateWithURL(url, None, dictarray) 23 | 24 | # Gets DocInfo from input file to pass to output. 25 | # PyObjC returns Keywords in an NSArray; they must be tupled. 26 | def getDocInfo(file): 27 | pdfURL = NSURL.fileURLWithPath_(file) 28 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 29 | if pdfDoc: 30 | metadata = pdfDoc.documentAttributes() 31 | if "Keywords" in metadata: 32 | keys = metadata["Keywords"] 33 | mutableMetadata = metadata.mutableCopy() 34 | mutableMetadata["Keywords"] = tuple(keys) 35 | return mutableMetadata 36 | else: 37 | return metadata 38 | 39 | # Check that the selected font is active, else use Helvetica Bold. 40 | def selectFont(typeface, pointSize): 41 | manager = NSFontManager.sharedFontManager() 42 | fontList = list(manager.availableFonts()) 43 | if typeface not in fontList: 44 | typeface = 'Helvetica-Bold' 45 | 46 | return CTFontCreateWithName(typeface, pointSize, None) 47 | 48 | # Closes the Context 49 | def contextDone(context): 50 | if context: 51 | Quartz.CGPDFContextClose(context) 52 | del context 53 | 54 | def drawWatermarkText(writeContext, line, xOffset, yOffset, angle, scale, opacity): 55 | if line: 56 | rect = CTLineGetImageBounds(line, writeContext) 57 | imageWidth = rect.size.width 58 | imageHeight = rect.size.height 59 | 60 | Quartz.CGContextSaveGState(writeContext) 61 | Quartz.CGContextSetAlpha(writeContext, opacity) 62 | Quartz.CGContextTranslateCTM(writeContext, xOffset, yOffset) 63 | Quartz.CGContextScaleCTM(writeContext, scale, scale) 64 | Quartz.CGContextTranslateCTM(writeContext, imageWidth / 2, imageHeight / 2) 65 | Quartz.CGContextRotateCTM(writeContext, angle * math.pi / 180) 66 | Quartz.CGContextTranslateCTM(writeContext, -imageWidth / 2, -imageHeight / 2) 67 | Quartz.CGContextSetTextPosition(writeContext, 0.0, 0.0); 68 | CTLineDraw(line, writeContext); 69 | Quartz.CGContextRestoreGState(writeContext) 70 | 71 | 72 | if __name__ == '__main__': 73 | 74 | for filename in sys.argv[1:]: 75 | shortName = os.path.splitext(filename)[0] 76 | outFilename = shortName + " NUM.pdf" 77 | pdf = createPDFDocumentFromPath(filename) 78 | metaDict = getDocInfo(filename) 79 | writeContext = createOutputContextWithPath(outFilename, metaDict) 80 | pages = Quartz.CGPDFDocumentGetNumberOfPages(pdf) 81 | 82 | # OPTIONS: Set the RELATIVE distance from outside top corner of page; 83 | # For other uses, set the angle, scale, and opacity of text 84 | # Font must be the PostScript name (i.e. no spaces) (See Get Info in FontBook) 85 | xOffset, yOffset, angle, scale, opacity = 45.0, 45.0, 0.0, 1.0, 1.0 86 | font = selectFont('TimesNewRomanPSMT', 12.0) 87 | 88 | if pdf: 89 | for i in range(1, (pages+1)): 90 | page = Quartz.CGPDFDocumentGetPage(pdf, i) 91 | if page: 92 | mbox = Quartz.CGPDFPageGetBoxRect(page, Quartz.kCGPDFMediaBox) 93 | if Quartz.CGRectIsEmpty(mbox): mbox = None 94 | Quartz.CGContextBeginPage(writeContext, mbox) 95 | Quartz.CGContextDrawPDFPage(writeContext, page) 96 | text = str(i) 97 | astr = CFAttributedStringCreate(kCFAllocatorDefault, text, { kCTFontAttributeName : font }) 98 | line = CTLineCreateWithAttributedString(astr) 99 | x = Quartz.CGRectGetWidth(mbox) 100 | y = Quartz.CGRectGetHeight(mbox) 101 | y -= yOffset 102 | if i == 1: # Don't put number on page 1 103 | pass 104 | elif i%2 == 1: # Move right hand number in by its own width. 105 | textWidth = astr.size().width 106 | x = x - xOffset 107 | x = x - textWidth 108 | drawWatermarkText(writeContext, line, x , y, angle, scale, opacity) 109 | else: 110 | x = xOffset 111 | drawWatermarkText(writeContext, line, x, y, angle, scale, opacity) 112 | 113 | Quartz.CGContextEndPage(writeContext) 114 | del pdf 115 | contextDone(writeContext) -------------------------------------------------------------------------------- /Python3/pdf2png 3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | PDF2PNG v.3.0: Creates a bitmap image from each page of each PDF supplied to it. 5 | by Ben Byram-Wigfield 6 | Now written for python3. You may need to install pyobjc with pip3. 7 | 8 | """ 9 | import os, sys 10 | import Quartz as Quartz 11 | # from LaunchServices import (kUTTypeJPEG, kUTTypeTIFF, kUTTypePNG, kCFAllocatorDefault) 12 | 13 | kUTTypeJPEG = 'public.jpeg' 14 | kUTTypeTIFF = 'public.tiff' 15 | kUTTypePNG = 'public.png' 16 | kCFAllocatorDefault = None 17 | 18 | resolution = 300.0 #dpi 19 | scale = resolution/72.0 20 | 21 | cs = Quartz.CGColorSpaceCreateWithName(Quartz.kCGColorSpaceSRGB) 22 | whiteColor = Quartz.CGColorCreate(cs, (1, 1, 1, 1)) 23 | # Options: Quartz.kCGImageAlphaNoneSkipLast (no trans), Quartz.kCGImageAlphaPremultipliedLast 24 | transparency = Quartz.kCGImageAlphaNoneSkipLast 25 | 26 | #Save image to file 27 | def writeImage (image, url, type, options): 28 | destination = Quartz.CGImageDestinationCreateWithURL(url, type, 1, None) 29 | Quartz.CGImageDestinationAddImage(destination, image, options) 30 | Quartz.CGImageDestinationFinalize(destination) 31 | return 32 | 33 | def getFilename(filepath): 34 | i=0 35 | newName = filepath 36 | while os.path.exists(newName): 37 | i += 1 38 | newName = filepath + " %02d"%i 39 | return newName 40 | 41 | if __name__ == '__main__': 42 | 43 | for filename in sys.argv[1:]: 44 | filenameNonU = filename.encode('utf8') 45 | pdf = Quartz.CGPDFDocumentCreateWithProvider(Quartz.CGDataProviderCreateWithFilename(filenameNonU)) 46 | print(pdf, filenameNonU) 47 | numPages = Quartz.CGPDFDocumentGetNumberOfPages(pdf) 48 | shortName = os.path.splitext(filename)[0] 49 | prefix = os.path.splitext(os.path.basename(filename))[0] 50 | folderName = getFilename(shortName) 51 | try: 52 | os.mkdir(folderName) 53 | except: 54 | print("Can't create directory '%s'"%(folderName)) 55 | sys.exit() 56 | # For each page, create a file 57 | for i in range (1, numPages+1): 58 | page = Quartz.CGPDFDocumentGetPage(pdf, i) 59 | if page: 60 | #Get mediabox 61 | mediaBox = Quartz.CGPDFPageGetBoxRect(page, Quartz.kCGPDFMediaBox) 62 | x = Quartz.CGRectGetWidth(mediaBox) 63 | y = Quartz.CGRectGetHeight(mediaBox) 64 | x *= scale 65 | y *= scale 66 | r = Quartz.CGRectMake(0,0,x, y) 67 | # Create a Bitmap Context, draw a white background and add the PDF 68 | writeContext = Quartz.CGBitmapContextCreate(None, int(x), int(y), 8, 0, cs, transparency) 69 | Quartz.CGContextSaveGState (writeContext) 70 | Quartz.CGContextScaleCTM(writeContext, scale,scale) 71 | Quartz.CGContextSetFillColorWithColor(writeContext, whiteColor) 72 | Quartz.CGContextFillRect(writeContext, r) 73 | Quartz.CGContextDrawPDFPage(writeContext, page) 74 | Quartz.CGContextRestoreGState(writeContext) 75 | # Convert to an "Image" 76 | image = Quartz.CGBitmapContextCreateImage(writeContext) 77 | # Create unique filename per page 78 | outFile = folderName +"/" + prefix + " %03d.png"%i 79 | outFile_nonU = outFile.encode('utf8') 80 | url = Quartz.CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, outFile_nonU, len(outFile_nonU), False) 81 | # kUTTypeJPEG, kUTTypeTIFF, kUTTypePNG 82 | type = kUTTypePNG 83 | # See the full range of image properties on Apple's developer pages. 84 | options = { 85 | Quartz.kCGImagePropertyDPIHeight: resolution, 86 | Quartz.kCGImagePropertyDPIWidth: resolution 87 | } 88 | writeImage (image, url, type, options) 89 | del page 90 | -------------------------------------------------------------------------------- /Python3/pdf2tiff 3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | PDF2TIFF v.3.0: Creates a bitmap image from each page of each PDF supplied to it. 5 | by Ben Byram-Wigfield 6 | Now written for python3. You may need to install pyobjc with pip3. 7 | 8 | """ 9 | import os, sys 10 | import Quartz as Quartz 11 | # from LaunchServices import (kUTTypeJPEG, kUTTypeTIFF, kUTTypePNG, kCFAllocatorDefault) 12 | 13 | kUTTypeJPEG = 'public.jpeg' 14 | kUTTypeTIFF = 'public.tiff' 15 | kUTTypePNG = 'public.png' 16 | kCFAllocatorDefault = None 17 | 18 | resolution = 300.0 #dpi 19 | scale = resolution/72.0 20 | 21 | cs = Quartz.CGColorSpaceCreateWithName(Quartz.kCGColorSpaceSRGB) 22 | whiteColor = Quartz.CGColorCreate(cs, (1, 1, 1, 1)) 23 | # Options: Quartz.kCGImageAlphaNoneSkipLast (no trans), Quartz.kCGImageAlphaPremultipliedLast 24 | transparency = Quartz.kCGImageAlphaNoneSkipLast 25 | 26 | #Save image to file 27 | def writeImage (image, url, type, options): 28 | destination = Quartz.CGImageDestinationCreateWithURL(url, type, 1, None) 29 | Quartz.CGImageDestinationAddImage(destination, image, options) 30 | Quartz.CGImageDestinationFinalize(destination) 31 | return 32 | 33 | def getFilename(filepath): 34 | i=0 35 | newName = filepath 36 | while os.path.exists(newName): 37 | i += 1 38 | newName = filepath + " %02d"%i 39 | return newName 40 | 41 | if __name__ == '__main__': 42 | 43 | for filename in sys.argv[1:]: 44 | filenameNonU = filename.encode('utf8') 45 | pdf = Quartz.CGPDFDocumentCreateWithProvider(Quartz.CGDataProviderCreateWithFilename(filenameNonU)) 46 | print(pdf, filenameNonU) 47 | numPages = Quartz.CGPDFDocumentGetNumberOfPages(pdf) 48 | shortName = os.path.splitext(filename)[0] 49 | prefix = os.path.splitext(os.path.basename(filename))[0] 50 | folderName = getFilename(shortName) 51 | try: 52 | os.mkdir(folderName) 53 | except: 54 | print("Can't create directory '%s'"%(folderName)) 55 | sys.exit() 56 | # For each page, create a file 57 | for i in range (1, numPages+1): 58 | page = Quartz.CGPDFDocumentGetPage(pdf, i) 59 | if page: 60 | #Get mediabox 61 | mediaBox = Quartz.CGPDFPageGetBoxRect(page, Quartz.kCGPDFMediaBox) 62 | x = Quartz.CGRectGetWidth(mediaBox) 63 | y = Quartz.CGRectGetHeight(mediaBox) 64 | x *= scale 65 | y *= scale 66 | r = Quartz.CGRectMake(0,0,x, y) 67 | # Create a Bitmap Context, draw a white background and add the PDF 68 | writeContext = Quartz.CGBitmapContextCreate(None, int(x), int(y), 8, 0, cs, transparency) 69 | Quartz.CGContextSaveGState (writeContext) 70 | Quartz.CGContextScaleCTM(writeContext, scale,scale) 71 | Quartz.CGContextSetFillColorWithColor(writeContext, whiteColor) 72 | Quartz.CGContextFillRect(writeContext, r) 73 | Quartz.CGContextDrawPDFPage(writeContext, page) 74 | Quartz.CGContextRestoreGState(writeContext) 75 | # Convert to an "Image" 76 | image = Quartz.CGBitmapContextCreateImage(writeContext) 77 | # Create unique filename per page 78 | outFile = folderName +"/" + prefix + " %03d.tif"%i 79 | outFile_nonU = outFile.encode('utf8') 80 | url = Quartz.CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, outFile_nonU, len(outFile_nonU), False) 81 | # kUTTypeJPEG, kUTTypeTIFF, kUTTypePNG 82 | type = kUTTypeTIFF 83 | # See the full range of image properties on Apple's developer pages. 84 | options = { 85 | Quartz.kCGImagePropertyDPIHeight: resolution, 86 | Quartz.kCGImagePropertyDPIWidth: resolution 87 | } 88 | writeImage (image, url, type, options) 89 | del page 90 | -------------------------------------------------------------------------------- /Python3/pdf2txt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # PDF2TXT: Output text content of a PDF file to a new text file 4 | # by Ben Byram-Wigfield v.3.0 for python3 5 | 6 | import os, sys 7 | from Quartz import PDFDocument 8 | from CoreFoundation import (NSURL, NSString) 9 | 10 | # Can't seem to import this constant, so manually creating it. 11 | NSUTF8StringEncoding = 4 12 | 13 | def main(): 14 | for filename in sys.argv[1:]: 15 | shortName = os.path.splitext(filename)[0] 16 | outputfile = shortName+" text.txt" 17 | pdfURL = NSURL.fileURLWithPath_(filename) 18 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 19 | if pdfDoc : 20 | pdfString = NSString.stringWithString_(pdfDoc.string()) 21 | pdfString.writeToFile_atomically_encoding_error_(outputfile, True, NSUTF8StringEncoding, None) 22 | 23 | if __name__ == "__main__": 24 | main() 25 | -------------------------------------------------------------------------------- /Python3/readme: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Python3/removePage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # REMOVEPAGE v.3.0 : Removes the first page of any PDF file(s) sent as arguments. 4 | # Unless there's only one page. 5 | 6 | # by Ben Byram-Wigfield. 7 | 8 | from Quartz import PDFDocument 9 | import sys 10 | from Foundation import NSURL 11 | 12 | def removePage(filename): 13 | pdfURL = NSURL.fileURLWithPath_(filename) 14 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 15 | if pdfDoc: 16 | pageNum = pdfDoc.pageCount() 17 | if pageNum > 1: 18 | pdfDoc.removePageAtIndex_(0) 19 | pdfDoc.writeToFile_(filename) 20 | return 21 | 22 | if __name__ == '__main__': 23 | for filename in sys.argv[1:]: 24 | removePage(filename) -------------------------------------------------------------------------------- /Python3/rinsePDF.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # RINSE PDF v3.0 : This script will re-save a PDF, which may fix some errors in the PDF data. 4 | # by Ben Byram-Wigfield. Now updated for python 3 5 | 6 | import sys 7 | import os 8 | import Quartz as Quartz 9 | from CoreFoundation import NSURL 10 | 11 | 12 | for inputfile in sys.argv[1:]: 13 | outfile = inputfile 14 | # To save with a new name, uncomment the lines below. 15 | prefix = os.path.splitext(inputfile) 16 | # outfile = prefix[0] + 'rinsed.pdf' 17 | pdfURL = NSURL.fileURLWithPath_(inputfile) 18 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 19 | pdfDoc.writeToFile_(outfile) 20 | -------------------------------------------------------------------------------- /Python3/rotate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Produces new PDF file with all pages rotated by 90 degrees. 4 | # by Ben Byram-Wigfield v3.0 for python3 5 | 6 | # There are two ways to rotate a PDF page/file. 7 | # 1: Create a new PDF context, graphically transform each page of the original and save the file. 8 | # 2: Adjust the 'rotation' parameter in each page. 9 | # This is the 2nd way, which is easier. 10 | # also preserves DocInfo and other metadata. 11 | 12 | import sys 13 | import os 14 | from Quartz import PDFDocument 15 | from CoreFoundation import NSURL 16 | 17 | def doRotate(filename): 18 | shortName = os.path.splitext(filename)[0] 19 | outFilename = shortName + "+90.pdf" 20 | pdfURL = NSURL.fileURLWithPath_(filename) 21 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 22 | if pdfDoc: 23 | pages = pdfDoc.pageCount() 24 | for p in range(0, pages): 25 | page = pdfDoc.pageAtIndex_(p) 26 | existingRotation = page.rotation() 27 | newRotation = existingRotation + 90 28 | page.setRotation_(newRotation) 29 | pdfDoc.writeToFile_(outFilename) 30 | 31 | 32 | if __name__ == '__main__': 33 | for filename in sys.argv[1:]: 34 | doRotate(filename) -------------------------------------------------------------------------------- /Python3/scalepdf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Scale PDF v. 0.1 4 | # Scales PDFs to a given size 5 | # DOESN'T WORK YET 6 | 7 | import sys 8 | import os 9 | import Quartz as Quartz 10 | from Foundation import NSURL, kCFAllocatorDefault 11 | 12 | # OPTIONS 13 | scaleSize = 1.41 14 | A3 = [[0,0], [1190.55, 841.88]] 15 | 16 | # Loads in PDF document 17 | def createPDFDocumentWithPath(path): 18 | url = NSURL.fileURLWithPath_(path) 19 | return Quartz.CGPDFDocumentCreateWithURL(url) 20 | 21 | # Creates a Context for drawing 22 | def createOutputContextWithPath(path, dictarray): 23 | url = NSURL.fileURLWithPath_(path) 24 | return Quartz.CGPDFContextCreateWithURL(url, None, dictarray) 25 | 26 | # Gets DocInfo from input file to pass to output. 27 | # PyObjC returns Keywords in an NSArray; they must be tupled. 28 | def getDocInfo(file): 29 | pdfURL = NSURL.fileURLWithPath_(file) 30 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 31 | if pdfDoc: 32 | metadata = pdfDoc.documentAttributes() 33 | if "Keywords" in metadata: 34 | keys = metadata["Keywords"] 35 | mutableMetadata = metadata.mutableCopy() 36 | mutableMetadata["Keywords"] = tuple(keys) 37 | return mutableMetadata 38 | else: 39 | return metadata 40 | 41 | def scale(filename): 42 | writeContext = None 43 | mediabox = 0 # Quartz.kPDFDisplayBoxMediaBox 44 | shortName = os.path.splitext(filename)[0] 45 | outFilename = shortName + " size.pdf" 46 | metaDict = getDocInfo(filename) 47 | writeContext = createOutputContextWithPath(outFilename, metaDict) 48 | pdfDoc = createPDFDocumentWithPath(filename) 49 | if pdfDoc: 50 | pages = Quartz.CGPDFDocumentGetNumberOfPages(pdfDoc) 51 | for p in range(0, pages): 52 | page = Quartz.CGPDFDocumentGetPage(pdfDoc, p) 53 | if page: 54 | mediaBoxSize = Quartz.CGPDFPageGetBoxRect(page, Quartz.kCGPDFMediaBox) 55 | Quartz.CGContextBeginPage(writeContext, mediaBoxSize) 56 | Quartz.CGContextSaveGState(writeContext) 57 | 58 | Quartz.CGContextDrawPDFPage(writeContext, page) 59 | print("qwe") 60 | # Quartz.CGContextDrawPDFPage(writeContext, mergepage) 61 | Quartz.CGContextEndPage(writeContext) 62 | Quartz.CGPDFContextClose(writeContext) 63 | del writeContext 64 | 65 | else: 66 | print ("A valid input file and output file must be supplied.") 67 | sys.exit(1) 68 | 69 | 70 | 71 | if __name__ == "__main__": 72 | for filename in sys.argv[1:]: 73 | 74 | scale(filename) 75 | -------------------------------------------------------------------------------- /Python3/setTitle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # TITLE : Change Title metadata to filename of a PDF file. 4 | # by Ben Byram-Wigfield 5 | 6 | 7 | import sys 8 | import os 9 | import getopt 10 | import Quartz as Quartz 11 | 12 | from CoreFoundation import NSURL 13 | 14 | def setMetadata(filename): 15 | pdfURL = NSURL.fileURLWithPath_(filename) 16 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 17 | value = os.path.splitext(filename)[0] 18 | options = { Quartz.kCGPDFContextTitle: value } 19 | pdfDoc.writeToFile_withOptions_(outputfile, options) 20 | 21 | if __name__ == "__main__": 22 | for filename in sys.argv[1:]: 23 | setMetadata(filename) 24 | 25 | 26 | -------------------------------------------------------------------------------- /Python3/splitPDF.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | SPLITPDF v3.0 : Takes an existing PDF and creates individual page documents in a new folder. 4 | by Ben Byram-Wigfield 5 | 6 | Now written for python3 7 | 8 | """ 9 | import os, sys 10 | import Quartz as Quartz 11 | from CoreFoundation import (CFAttributedStringCreate, CFURLCreateFromFileSystemRepresentation, NSURL) 12 | 13 | def createPDFDocumentFromPath(path): 14 | pdfURL = NSURL.fileURLWithPath_(path) 15 | if pdfURL: 16 | return Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 17 | 18 | def getFilename(filepath): 19 | i=0 20 | newName = filepath 21 | while os.path.exists(newName): 22 | i += 1 23 | newName = filepath + " %02d"%i 24 | return newName 25 | 26 | def strip(filename): 27 | # 28 | pdf = createPDFDocumentFromPath(filename) 29 | numPages = pdf.pageCount() 30 | shortName = os.path.splitext(filename)[0] 31 | prefix = os.path.splitext(os.path.basename(filename))[0] 32 | metaDict = pdf.documentAttributes() 33 | folderName = getFilename(shortName) 34 | try: 35 | os.mkdir(folderName) 36 | except: 37 | print ("Can't create directory '%s'"%(folderName)) 38 | sys.exit() 39 | 40 | # For each page, create a file. Index starts at ZERO!!! 41 | # You won't get leading zeros in filenames beyond 99. 42 | for i in range (1, numPages+1): 43 | page = pdf.pageAtIndex_(i-1) 44 | if page: 45 | newDoc = Quartz.PDFDocument.alloc().initWithData_(page.dataRepresentation()) 46 | outFile = folderName +"/" + prefix + " %03d.pdf"%i 47 | newDoc.writeToFile_withOptions_(outFile, metaDict) 48 | 49 | if __name__ == "__main__": 50 | for filename in sys.argv[1:]: 51 | strip(filename) -------------------------------------------------------------------------------- /Python3/tint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # TINT: v1.1 4 | # Overlays a tint over the entire PDF. 5 | # by Ben Byram-Wigfield 6 | 7 | import sys 8 | import os 9 | import Quartz as Quartz 10 | from Foundation import NSURL, kCFAllocatorDefault 11 | 12 | # Loads in PDF document 13 | def createPDFDocumentFromPath(path): 14 | url = NSURL.fileURLWithPath_(path) 15 | return Quartz.CGPDFDocumentCreateWithURL(url) 16 | 17 | # Creates a Context for drawing 18 | def createOutputContextWithPath(path, dictarray): 19 | url = NSURL.fileURLWithPath_(path) 20 | return Quartz.CGPDFContextCreateWithURL(url, None, dictarray) 21 | 22 | 23 | def makeRectangle(x, y, xSize, ySize, color, alpha): 24 | red, green, blue = color[:] 25 | Quartz.CGContextSetRGBFillColor (writeContext, red, green, blue, alpha) 26 | Quartz.CGContextFillRect (writeContext, Quartz.CGRectMake(x, y, xSize, ySize)) 27 | return 28 | 29 | # Gets DocInfo from input file to pass to output. 30 | # PyObjC returns Keywords in an NSArray; they must be tupled. 31 | def getDocInfo(file): 32 | pdfURL = NSURL.fileURLWithPath_(file) 33 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 34 | if pdfDoc: 35 | metadata = pdfDoc.documentAttributes() 36 | if "Keywords" in metadata: 37 | keys = metadata["Keywords"] 38 | mutableMetadata = metadata.mutableCopy() 39 | mutableMetadata["Keywords"] = tuple(keys) 40 | return mutableMetadata 41 | else: 42 | return metadata 43 | 44 | def tint(filename): 45 | global writeContext 46 | # The color of the tint. Sepia is 0.44, 0.26, 0.08. Some transparency may help. 47 | red = 1.0 48 | green = 0.93 49 | blue = 0.81 50 | alpha = 1.0 51 | myColor=[red, green, blue] 52 | 53 | writeContext = None 54 | 55 | shortName = os.path.splitext(filename)[0] 56 | outFilename = shortName + "+tint.pdf" 57 | metaDict = getDocInfo(filename) 58 | 59 | writeContext = createOutputContextWithPath(outFilename, metaDict) 60 | readPDF = createPDFDocumentFromPath(filename) 61 | 62 | if writeContext != None and readPDF != None: 63 | numPages = Quartz.CGPDFDocumentGetNumberOfPages(readPDF) 64 | for pageNum in range(1, numPages + 1): 65 | page = Quartz.CGPDFDocumentGetPage(readPDF, pageNum) 66 | if page: 67 | mediaBox = Quartz.CGPDFPageGetBoxRect(page, Quartz.kCGPDFMediaBox) 68 | if Quartz.CGRectIsEmpty(mediaBox): 69 | mediaBox = None 70 | Quartz.CGContextBeginPage(writeContext, mediaBox) 71 | Quartz.CGContextSetBlendMode(writeContext, Quartz.kCGBlendModeOverlay) 72 | Quartz.CGContextDrawPDFPage(writeContext, page) 73 | makeRectangle(0, 0, mediaBox.size.width, mediaBox.size.height, myColor, alpha) 74 | Quartz.CGContextEndPage(writeContext) 75 | Quartz.CGPDFContextClose(writeContext) 76 | del writeContext 77 | 78 | else: 79 | print ("A valid input file and output file must be supplied.") 80 | sys.exit(1) 81 | if __name__ == "__main__": 82 | for filename in sys.argv[1:]: 83 | 84 | tint(filename) 85 | -------------------------------------------------------------------------------- /Python3/trimPDF.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # TRIM PDF v.1.0 : Crop the mediabox to the size of the trimbox, if different. 4 | # This lets you crop a page containing printers crop marks to the trimmed page size. 5 | # by Ben Byram-Wigfield v1.0 6 | 7 | import sys 8 | import os 9 | from Quartz import PDFDocument, kPDFDisplayBoxMediaBox, kPDFDisplayBoxTrimBox, CGRectEqualToRect 10 | from CoreFoundation import NSURL 11 | 12 | mediabox = kPDFDisplayBoxMediaBox 13 | trimbox = kPDFDisplayBoxTrimBox 14 | 15 | def trimPDF(filename): 16 | hasBeenChanged = False 17 | # filename = filename.decode('utf-8') 18 | shortName = os.path.splitext(filename)[0] 19 | outFilename = shortName + " TPS.pdf" 20 | pdfURL = NSURL.fileURLWithPath_(filename) 21 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 22 | if pdfDoc: 23 | pages = pdfDoc.pageCount() 24 | for p in range(0, pages): 25 | page = pdfDoc.pageAtIndex_(p) 26 | mediaBoxSize = page.boundsForBox_(mediabox) 27 | trimBoxSize = page.boundsForBox_(trimbox) 28 | if not CGRectEqualToRect(mediaBoxSize, trimBoxSize): 29 | page.setBounds_forBox_(trimBoxSize, mediabox) 30 | hasBeenChanged = True 31 | if hasBeenChanged: 32 | pdfDoc.writeToFile_(outFilename) 33 | 34 | if __name__ == '__main__': 35 | for filename in sys.argv[1:]: 36 | trimPDF(filename) 37 | -------------------------------------------------------------------------------- /Python3/watermark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # WATERMARK: Superimposed text on pages of PDF documents. 4 | # By Ben Byram-Wigfield v3.0 5 | # Options for position, size, font, text and opacity are below. 6 | 7 | import sys, os, math 8 | import Quartz as Quartz 9 | from CoreText import (kCTFontAttributeName, CTFontCreateWithName, CTLineDraw, CTLineCreateWithAttributedString, kCTFontAttributeName, CTLineGetImageBounds) 10 | from CoreFoundation import (CFAttributedStringCreate, CFURLCreateFromFileSystemRepresentation, kCFAllocatorDefault, NSURL) 11 | from AppKit import NSFontManager 12 | 13 | 14 | # Creates a PDF Object from incoming file. 15 | def createPDFDocumentFromPath(path): 16 | url = NSURL.fileURLWithPath_(path) 17 | return Quartz.CGPDFDocumentCreateWithURL(url) 18 | 19 | # Creates a Context for drawing 20 | def createOutputContextWithPath(path, dictarray): 21 | url = NSURL.fileURLWithPath_(path) 22 | return Quartz.CGPDFContextCreateWithURL(url, None, dictarray) 23 | 24 | # Gets DocInfo from input file to pass to output. 25 | # PyObjC returns Keywords in an NSArray; they must be tupled. 26 | def getDocInfo(file): 27 | pdfURL = NSURL.fileURLWithPath_(file) 28 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 29 | if pdfDoc: 30 | metadata = pdfDoc.documentAttributes() 31 | if "Keywords" in metadata: 32 | keys = metadata["Keywords"] 33 | mutableMetadata = metadata.mutableCopy() 34 | mutableMetadata["Keywords"] = tuple(keys) 35 | return mutableMetadata 36 | else: 37 | return metadata 38 | 39 | # Closes the Context 40 | def contextDone(context): 41 | if context: 42 | Quartz.CGPDFContextClose(context) 43 | del context 44 | 45 | def drawWatermarkText(writeContext, line, xOffset, yOffset, angle, opacity): 46 | if line: 47 | rect = CTLineGetImageBounds(line, writeContext) 48 | imageWidth = rect.size.width 49 | imageHeight = rect.size.height 50 | Quartz.CGContextSaveGState(writeContext) 51 | Quartz.CGContextSetAlpha(writeContext, opacity) 52 | Quartz.CGContextTranslateCTM(writeContext, xOffset, yOffset) 53 | Quartz.CGContextRotateCTM(writeContext, angle * math.pi / 180) 54 | Quartz.CGContextSetTextPosition(writeContext, 0.0, 0.0) 55 | CTLineDraw(line, writeContext) 56 | Quartz.CGContextRestoreGState(writeContext) 57 | # return 58 | 59 | # Check that the selected font is active, else use Helvetica Bold. 60 | def selectFont(typeface, pointSize): 61 | manager = NSFontManager.sharedFontManager() 62 | fontList = list(manager.availableFonts()) 63 | if typeface not in fontList: 64 | typeface = 'Helvetica-Bold' 65 | 66 | return CTFontCreateWithName(typeface, pointSize, None) 67 | 68 | def getFilename(filepath, suffix): 69 | fullname = filepath + suffix + ".pdf" 70 | i=0 71 | while os.path.exists(fullname): 72 | i += 1 73 | fullname = filepath + suffix + " %02d.pdf"%i 74 | return fullname 75 | 76 | if __name__ == '__main__': 77 | 78 | # OPTIONS: Set the distance from bottom left of page; 79 | # Set the angle and opacity of text 80 | # Font must be the PostScript name (i.e. no spaces) (See Get Info in FontBook) 81 | xOffset, yOffset, angle, opacity = 110.0, 200.0, 45.0, 0.5 82 | font = selectFont('Helvetica-Bold', 150.0) 83 | text = "SAMPLE" 84 | 85 | for filename in sys.argv[1:]: 86 | shortName = os.path.splitext(filename)[0] 87 | outFilename = getFilename(shortName, " WM") 88 | pdf = createPDFDocumentFromPath(filename) 89 | metaDict = getDocInfo(filename) 90 | writeContext = createOutputContextWithPath(outFilename, metaDict) 91 | pages = Quartz.CGPDFDocumentGetNumberOfPages(pdf) 92 | 93 | if pdf: 94 | for i in range(1, (pages+1)): 95 | page = Quartz.CGPDFDocumentGetPage(pdf, i) 96 | if page: 97 | mbox = Quartz.CGPDFPageGetBoxRect(page, Quartz.kCGPDFMediaBox) 98 | # if Quartz.CGRectIsEmpty(mbox): mbox = None 99 | Quartz.CGContextBeginPage(writeContext, mbox) 100 | Quartz.CGContextDrawPDFPage(writeContext, page) 101 | astr = CFAttributedStringCreate(kCFAllocatorDefault, text, { kCTFontAttributeName : font }) 102 | line = CTLineCreateWithAttributedString(astr) 103 | drawWatermarkText(writeContext, line, xOffset , yOffset, angle, opacity) 104 | Quartz.CGContextEndPage(writeContext) 105 | del pdf 106 | contextDone(writeContext) -------------------------------------------------------------------------------- /Python3/watermarkPDF.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # Merge v. 0.1 5 | # Merges two PDFs 6 | 7 | import sys 8 | import os 9 | import Quartz as Quartz 10 | from Foundation import NSURL, kCFAllocatorDefault 11 | 12 | # OPTIONS 13 | # Filename of file you want added to all the others. 14 | watermark = os.path.expanduser("~/Desktop/Test.pdf") 15 | 16 | 17 | # Loads in PDF document 18 | def createPDFDocumentWithPath(path): 19 | url = NSURL.fileURLWithPath_(path) 20 | return Quartz.CGPDFDocumentCreateWithURL(url) 21 | 22 | # Creates a Context for drawing 23 | def createOutputContextWithPath(path, dictarray): 24 | url = NSURL.fileURLWithPath_(path) 25 | return Quartz.CGPDFContextCreateWithURL(url, None, dictarray) 26 | 27 | # Gets DocInfo from input file to pass to output. 28 | # PyObjC returns Keywords in an NSArray; they must be tupled. 29 | def getDocInfo(file): 30 | pdfURL = NSURL.fileURLWithPath_(file) 31 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 32 | if pdfDoc: 33 | metadata = pdfDoc.documentAttributes() 34 | if "Keywords" in metadata: 35 | keys = metadata["Keywords"] 36 | mutableMetadata = metadata.mutableCopy() 37 | mutableMetadata["Keywords"] = tuple(keys) 38 | return mutableMetadata 39 | else: 40 | return metadata 41 | 42 | 43 | 44 | def merge(filename): 45 | 46 | writeContext = None 47 | 48 | shortName = os.path.splitext(filename)[0] 49 | outFilename = shortName + "+wm.pdf" 50 | metaDict = getDocInfo(filename) 51 | 52 | writeContext = createOutputContextWithPath(outFilename, metaDict) 53 | readPDF = createPDFDocumentWithPath(filename) 54 | mergePDF = createPDFDocumentWithPath(watermark) 55 | 56 | if writeContext != None and readPDF != None: 57 | numPages = Quartz.CGPDFDocumentGetNumberOfPages(readPDF) 58 | for pageNum in range(1, numPages + 1): 59 | page = Quartz.CGPDFDocumentGetPage(readPDF, pageNum) 60 | mergepage = Quartz.CGPDFDocumentGetPage(mergePDF, 1) 61 | if page: 62 | mediaBox = Quartz.CGPDFPageGetBoxRect(page, Quartz.kCGPDFMediaBox) 63 | if Quartz.CGRectIsEmpty(mediaBox): 64 | mediaBox = None 65 | Quartz.CGContextBeginPage(writeContext, mediaBox) 66 | Quartz.CGContextSetBlendMode(writeContext, Quartz.kCGBlendModeOverlay) 67 | 68 | Quartz.CGContextDrawPDFPage(writeContext, page) 69 | Quartz.CGContextDrawPDFPage(writeContext, mergepage) 70 | Quartz.CGContextEndPage(writeContext) 71 | Quartz.CGPDFContextClose(writeContext) 72 | del writeContext 73 | 74 | else: 75 | print ("A valid input file and output file must be supplied.") 76 | sys.exit(1) 77 | 78 | 79 | 80 | if __name__ == "__main__": 81 | for filename in sys.argv[1:]: 82 | 83 | merge(filename) 84 | -------------------------------------------------------------------------------- /QuartzFilters/Better Reduce File Size 150dpi.qfilter: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Domains 6 | 7 | Applications 8 | 9 | Printing 10 | 11 | 12 | FilterData 13 | 14 | ColorSettings 15 | 16 | ImageSettings 17 | 18 | Compression Quality 19 | 0.0085358796641230583 20 | ImageCompression 21 | ImageJPEGCompress 22 | ImageScaleSettings 23 | 24 | ImageResolution 25 | 150 26 | ImageScaleFactor 27 | 0.0 28 | ImageScaleInterpolate 29 | 3 30 | ImageSizeMax 31 | 0 32 | ImageSizeMin 33 | 0 34 | 35 | 36 | 37 | 38 | FilterType 39 | 1 40 | Name 41 | Better Reduce File Size 150dpi 42 | 43 | 44 | -------------------------------------------------------------------------------- /QuartzFilters/Better Reduce File Size 300dpi.qfilter: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Domains 6 | 7 | Applications 8 | 9 | Printing 10 | 11 | 12 | FilterData 13 | 14 | ColorSettings 15 | 16 | ImageSettings 17 | 18 | Compression Quality 19 | 1 20 | ImageCompression 21 | ImageJPEGCompress 22 | ImageScaleSettings 23 | 24 | ImageResolution 25 | 300 26 | ImageScaleFactor 27 | 0.0 28 | ImageScaleInterpolate 29 | 3 30 | ImageSizeMax 31 | 0 32 | ImageSizeMin 33 | 0 34 | 35 | 36 | 37 | 38 | FilterType 39 | 1 40 | Name 41 | Better Reduce File Size 300dpi 42 | 43 | 44 | -------------------------------------------------------------------------------- /QuartzFilters/README.md: -------------------------------------------------------------------------------- 1 | # QuartzFilters 2 | 3 | This is a collection of Quartz ColorSync Filters for MacOS. While filters work well at manipulating, converting or altering images and PDF documents, the OS-bundled app to create them, ColorSync Utility, is profoundly broken and can corrupt the settings of installed filters. 4 | 5 | Put these files in {user}/Library/Filters. Another location is /Library/Filters. They will then be available to apps like Preview, and you can use them in conjunction with the scripts and workflows in PDFSuite to produce other ways of applying them to PDFs. 6 | 7 | Because of the problems with ColorSync Utility, it is recommended to Lock user Quartz Filters after installing, before opening ColorSync Utility. That way, the app will not alter the data of your filters. You can either lock filters using Get Info in the Finder, or there is a _Read-only : true_ key/value pair in the XML. 8 | 9 | ### Better PDFX-3.qfilter ### 10 | This filter is designed to modify PDFs so that they are compatible with the PDF/X-3 standard. It's significantly better than Apple's "Create Generic PDFX-3 Document" filter _(if that isn't damning it with faint praise)_. Apple's filter flattens transparency to 72 dpi, which is hopeless for print. It also uses a Generic CMYK profile, which is very flat, and it makes minimal effort to manage colour. This filter aims to redress all these problems in the Apple default filter. 11 | The Better PDFX-3 filter offers improved transparency and colour management, using parameters similar to those in Adobe Distiller's PDF/X-3:2002 joboptions. 12 | 13 | ### Better Reduce File Size ### 14 | These filters will compress PDF files, if possible, without too much lossage. 15 | 16 | ## Quartz Filter Documentation 17 | Apple's documentation on working with Quartz Filters is minimal. Here are a few notes that I've discovered: 18 | 19 | It is possible to create or modify Quartz Filters without using ColorSync Utility. The qfilter format is a standard Apple XML property list. You can get an understanding of the format by opening the System filters in a text editor like BBEdit, and comparing to the options in CS Utility. The only tricky bit is the inclusion of ICC colour profiles. This done by taking the .icc profile's complete data and base64 encoding it (use a line break every 52 chars), and then inserting this as a data object in the array FilterProfileArray. Profiles are then referenced in the XML by their index number (starts at 0). Because profiles are contained within the filter, they do not need to be installed in the system. 20 | 21 | If the Quartz Filter Manager cannot parse the Filter, it will not be loaded. It may also cause ColorSync Utility to hang without opening its window. Large numbers of ICC profiles and Quartz Filters can cause the Utility to spinwheel before its window opens. 22 | 23 | You can interogate the OS for the name, url and properties of each Quartz Filter installed. See the listFilters.py script in the Shell_Scripts folder. (This script just gives names and filepaths: it's trivial to add .properties to get a representation of the data.) Note that the name of a filter is not its filename, but a data value within the qfilter file. 24 | 25 | You can also create filters programmatically. Make an NSDictionary of the data, then call the quartzFilterWithProperties method. However, this is only for use within your code, there doesn't seem a way of saving it. 26 | -------------------------------------------------------------------------------- /doc/PDFbutton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/doc/PDFbutton.png -------------------------------------------------------------------------------- /doc/makequickaction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/doc/makequickaction.png -------------------------------------------------------------------------------- /doc/pdfworkflows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/doc/pdfworkflows.png -------------------------------------------------------------------------------- /doc/quickactionmenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/doc/quickactionmenu.png -------------------------------------------------------------------------------- /doc/readme.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/DeBooklet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # DeBooklet v.1.1 : Split page spreads into separate pages. 5 | # by Ben Byram-Wigfield v1.0 6 | 7 | # Doesn't sort the pages. 8 | # Files produced are 'quite large' as some residual data from the cropped area remains. 9 | 10 | import sys 11 | import os 12 | from Quartz import PDFDocument, kPDFDisplayBoxMediaBox, CGRectEqualToRect, CGRectMake 13 | from CoreFoundation import NSURL 14 | 15 | mediabox = kPDFDisplayBoxMediaBox 16 | # Set to False if you don't want page 1 split. 17 | doPageOne = True 18 | 19 | def debooklet(filename): 20 | shortName = os.path.splitext(filename)[0] 21 | outFilename = shortName + " paged.pdf" 22 | 23 | # If running python2, uncomment the following line: 24 | # filename = filename.decode('utf-8') 25 | pdfURL = NSURL.fileURLWithPath_(filename) 26 | leftPDF = PDFDocument.alloc().initWithURL_(pdfURL) 27 | rightPDF = PDFDocument.alloc().initWithURL_(pdfURL) 28 | newPDF = PDFDocument.alloc().init() 29 | if leftPDF: 30 | if not(doPageOne): 31 | leftPage = leftPDF.pageAtIndex_(0) 32 | newPDF.insertPage_atIndex_(leftPage, 0) 33 | pages = leftPDF.pageCount() 34 | startPage = int(not(doPageOne)) 35 | for p in range(startPage, pages): 36 | outPageCount = newPDF.pageCount() 37 | leftPage = leftPDF.pageAtIndex_(p) 38 | rightPage = rightPDF.pageAtIndex_(p) 39 | mediaBoxSize = leftPage.boundsForBox_(mediabox) 40 | rotation = leftPage.rotation() 41 | if (rotation == 0) or (rotation == 180): 42 | halfway = (mediaBoxSize.size.width/2) 43 | pageHeight = mediaBoxSize.size.height 44 | leftHandCrop = CGRectMake(0,0,halfway,pageHeight) 45 | rightHandCrop = CGRectMake(halfway, 0, halfway, pageHeight) 46 | leftPage.setBounds_forBox_(leftHandCrop, mediabox) 47 | rightPage.setBounds_forBox_(rightHandCrop, mediabox) 48 | else: 49 | halfway = (mediaBoxSize.size.height/2) 50 | pageWidth = mediaBoxSize.size.width 51 | topCrop = CGRectMake(0,0,pageWidth, halfway) 52 | bottomCrop = CGRectMake(0,halfway, pageWidth,halfway) 53 | leftPage.setBounds_forBox_(topCrop, mediabox) 54 | rightPage.setBounds_forBox_(bottomCrop, mediabox) 55 | 56 | newPDF.insertPage_atIndex_(leftPage, outPageCount) 57 | newPDF.insertPage_atIndex_(rightPage, outPageCount+1) 58 | 59 | newPDF.writeToFile_(outFilename) 60 | 61 | if __name__ == '__main__': 62 | for filename in sys.argv[1:]: 63 | debooklet(filename) 64 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/Helpful Illustration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Automator_Scripts/Helpful Illustration.png -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/ReadMe.md: -------------------------------------------------------------------------------- 1 | ## Automator Scripts 2 | 3 | Automator workflows containing these scripts, for use as MacOS 'Services', can be found in the 'Automator Services' folder. You can also make your own Automator workflows with them following these instructions: 4 | 1. Download the scripts. 5 | 2. Launch Automator and create a New Service. 6 | 3. Set the drop-down menus to read "Service receives **PDF Files** in **Finder**". 7 | 4. Add "Run Shell Script" action. (under "Utilities".) 8 | 5. Set the shell drop-down list to **/usr/bin/python** and "Pass input" to **as arguments**. 9 | 6. Paste in the script you want to use (replacing the existing text). 10 | 11 | Alternatively, you can use them in the Terminal with PDF files as arguments. Drop the script into a Terminal window, then drop all the files you want to process. 12 | 13 | ### Add Blank Page (addpage.py) 14 | This script adds a blank page to the end of a PDF file. The page size for the blank page is the same as the first page of the PDF. 15 | 16 | ### Count pages in PDF (countpages.py) 17 | This counts the number of pages in one or more PDF files passed to it. It provides a cumulative count for multiple file arguments. 18 | 19 | ### Make Booklet (booklet.py) 20 | This will impose PDF pages into booklet spreads. 21 | 22 | ### deBooklet (DeBooklet.py) 23 | This splits each PDF page into two pieces: useful to 'de-booklet' a PDF of spreads-as-pages. 24 | 25 | ### Encrypt PDF or add security (encrypt.py) 26 | This adds encryption and passwords to prevent unauthorized copying, printing, or opening. Note that the passwords are stored in plain text within the body of the script. 27 | 28 | ### Combine images to one PDF (imagestopdf.py) 29 | Entirely rewritten v2.0 script, that merges all image files into one PDF. This script is several seconds faster than Apple's own Automator action! 30 | 31 | ### Add Index Numbers (indexnumbers.py) 32 | This script adds the text "n of x" on the first page of all PDFs passed to it, where x is the number of files in the batch and n is the ordinal number of each file, e.g. _1 of 3, 2 of 3, 3 of 3_ 33 | 34 | ### Join PDF files into one file (myjoin.py) 35 | This script will combine all PDF files supplied as arguments into one file, called "Combined.pdf". Files are passed in the order they appear in the Finder window, so re-ordering the List or Column will change the order of selected items for combining. 36 | 37 | ### Add Page Number (pagenumber.py) 38 | This script adds a folio number to facing pages of PDF files supplied as arguments. Users can set the offset position from the outer top corner, font, size. There are also settings for the scale, opacity and angle of text. A new file is produced, suffixed "NUM". A slightly modified version of this script is used to produce text watermarks (watermark.py). 39 | 40 | ### Export pages as images (pdf2tiff.py) 41 | This script exports each page as 300dpi RGB TIFF images from PDFs supplied as arguments. Options in the script alow for JPEG and PNG filetypes, resolution, transparency and other parameters. Images are saved to a folder with the name of each original file (minus file extension). If the folder exists, a new one with an incremental number will be made. 42 | 43 | ### Export Text to txt file (pdf2txt.py) 44 | This script outputs the entire readable text from a PDF file to a .txt file. The text file has the same name as the PDF, with the different file extension. If it already exists, an index number will be added to the filename. 45 | 46 | ### Rotate all pages in PDF (rotate.py) 47 | This will rotate all the pages of any PDF files passed as arguments by 90˚ into a new file suffixed "+90". (Suffixes are cumulative.) 48 | 49 | ### Split PDF into separate files (splitPDF.py) 50 | This script creates separate PDFs for each page in an existing PDF. The page files are saved inside a folder with the name of the source file (minus .pdf extension). 51 | 52 | ### Trim pages to the Crop Marks (trimPDF.py) 53 | This script compares the trimbox to the mediabox. If the two are different, it crops the page to the trimbox. This may be useful for cropping a PDF that contains crop marks and bleed area to the trimmed page size. 54 | 55 | ### Watermark all pages of PDF (watermark.py) 56 | By default, this script adds the word "SAMPLE" in 150pt Helvetica-Bold, at 45˚ angle, with 50% opacity, to every page of PDFs passed to it. These settings can easily be altered. 57 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/addpage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | # ADDPAGE v.1.4 : Adds a blank page to the END of any PDF file(s) sent as arguments. 5 | 6 | # by Ben Byram-Wigfield. 7 | 8 | # Page size of blank page is taken from first page of PDF. 9 | # Can be used as Automator action or as shell script. 10 | 11 | 12 | from Quartz import PDFDocument, PDFPage, kPDFDisplayBoxMediaBox 13 | import sys 14 | from Foundation import NSURL 15 | 16 | 17 | mediabox = kPDFDisplayBoxMediaBox 18 | 19 | 20 | def addPage(filename): 21 | filename = filename.decode('utf-8') 22 | pdfURL = NSURL.fileURLWithPath_(filename) 23 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 24 | if pdfDoc: 25 | pageNum = pdfDoc.pageCount() 26 | page = pdfDoc.pageAtIndex_(0) 27 | pageSize = page.boundsForBox_(mediabox) 28 | blankPage = PDFPage.alloc().init() 29 | blankPage.setBounds_forBox_(pageSize, mediabox) 30 | pdfDoc.insertPage_atIndex_(blankPage, pageNum) 31 | pdfDoc.writeToFile_(filename) 32 | return 33 | 34 | if __name__ == '__main__': 35 | for filename in sys.argv[1:]: 36 | addPage(filename) 37 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/bookletPDF.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # ---------------------------------------------------------------- 5 | # PDF Booklet Imposition Script for MacOS v2.4 (Automator) 6 | # by Ben Byram-Wigfield 7 | # Feel free to use, modify and pass on with acknowledgement. 8 | # 9 | # Usage: As a script in Automator, or: bookletPDF.py ... 10 | # Original file is preserved, and output file has suffix added. 11 | # NB: The script will overwrite existing output file of the same name. 12 | # 13 | # Script can be configured for stacking 4pp signatures or gathering booklet spreads. 14 | # ---------------------------------------------------------------- 15 | 16 | # Beware of indexes starting from 0 and 1...!!! CGPDFDocument starts page count at 1. 17 | 18 | import os, sys 19 | import copy 20 | import Quartz as Quartz 21 | from Foundation import (NSURL, kCFAllocatorDefault) 22 | 23 | # Uncomment the sheet size you want. 24 | A3 = [[0,0], [1190.55, 841.88]] 25 | A4 = [[0,0], [841.88, 595.28]] 26 | # USLetter = [[0,0], [792, 612]] 27 | # Tabloid = [[0,0], [1224, 792]] 28 | 29 | # OPTIONS 30 | # Change this to one of the sizes listed above, if you want. 31 | sheetSize = A3 32 | 33 | # Set file suffix 34 | suffix = " booklet.pdf" 35 | # If hasSignatures, sheets will be arranged for stacking in 4pp sections. 36 | hasSignatures = False 37 | pagesPerSheet = 4 # Not sure what will happen if this is changed. 38 | creep = 0.5 # in points. NB: Eventually, the pages will collide. 39 | imposedOrder = [] 40 | 41 | # FUNCTIONS 42 | # Loads in PDF document 43 | def createPDFDocumentFromPath(path): 44 | url = NSURL.fileURLWithPath_(path) 45 | return Quartz.CGPDFDocumentCreateWithURL(url) 46 | 47 | # Creates a Context for drawing 48 | def createOutputContextFromPath(path, dictarray): 49 | url = NSURL.fileURLWithPath_(path) 50 | return Quartz.CGPDFContextCreateWithURL(url, None, dictarray) 51 | 52 | # Get page sequence for imposition order (e.g. 4,1,2,3) 53 | def imposition(pageRange): 54 | for i in range(1, (len(pageRange)/2), 2): 55 | # First we do recto 56 | imposedOrder.append(pageRange[i*-1]) 57 | imposedOrder.append(pageRange[i-1]) 58 | # And now we do verso 59 | imposedOrder.append(pageRange[i]) 60 | imposedOrder.append(pageRange[(i+1)*-1]) 61 | return imposedOrder 62 | 63 | # Make sure pages are portrait in orientation. 64 | def getRotation(pdfpage): 65 | displayAngle = 0 66 | rotValue = Quartz.CGPDFPageGetRotationAngle(pdfpage) 67 | mediaBox = Quartz.CGPDFPageGetBoxRect(pdfpage, Quartz.kCGPDFMediaBox) 68 | if not Quartz.CGRectIsEmpty(mediaBox): 69 | x = Quartz.CGRectGetWidth(mediaBox) 70 | y = Quartz.CGRectGetHeight(mediaBox) 71 | if (x > y): displayAngle = -90 72 | displayAngle -= rotValue 73 | return displayAngle 74 | 75 | # Gets DocInfo from input file to pass to output. 76 | # PyObjC returns Keywords in an NSArray; they must be tupled. 77 | def getDocInfo(file): 78 | # file = file.decode('utf-8') 79 | pdfURL = NSURL.fileURLWithPath_(file) 80 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 81 | if pdfDoc: 82 | metadata = pdfDoc.documentAttributes() 83 | if "Keywords" in metadata: 84 | keys = metadata["Keywords"] 85 | mutableMetadata = metadata.mutableCopy() 86 | mutableMetadata["Keywords"] = tuple(keys) 87 | return mutableMetadata 88 | else: 89 | return metadata 90 | 91 | # Close Context when finished 92 | def contextDone(context): 93 | if context: 94 | Quartz.CGPDFContextClose(context) 95 | del context 96 | 97 | # MAIN 98 | def makeBooklet(argv): 99 | 100 | leftPage = copy.deepcopy(sheetSize) 101 | shift = sheetSize[1][0]/2 102 | leftPage[1][0] = shift 103 | rightPage = copy.deepcopy(leftPage) 104 | rightPage[0][0] = shift 105 | blanks = 0 106 | 107 | # Initiate new PDF, get source PDF data, number of pages. 108 | shortName = os.path.splitext(argv)[0] 109 | writeFilename = shortName + suffix 110 | # writeFilename = writeFilename.encode('utf-8') 111 | metaDict = getDocInfo(argv) 112 | writeContext = createOutputContextFromPath(writeFilename, metaDict) 113 | source = createPDFDocumentFromPath(argv) 114 | totalPages = Quartz.CGPDFDocumentGetNumberOfPages(source) 115 | 116 | # Add 0 to Unsorted Order for each blank page required to be a multiple of pages per sheet. 117 | UnsortedOrder = range(1, totalPages+1) 118 | if totalPages%pagesPerSheet: 119 | blanks = pagesPerSheet - (totalPages%pagesPerSheet) 120 | for i in range(blanks): 121 | UnsortedOrder.append(0) 122 | totalPages = len(UnsortedOrder) 123 | 124 | if hasSignatures: 125 | signatureSize = pagesPerSheet 126 | else: 127 | signatureSize = totalPages 128 | 129 | for something in range(0, totalPages, signatureSize): 130 | imposition(UnsortedOrder[something:(something+signatureSize)]) 131 | 132 | # For each side of the sheet, we must... 133 | # ... create a PDF page, take two source pages and place them differently, then close the page. 134 | # If the source page number is 0 (i.e. blank), then move on without drawing. 135 | Sides = totalPages/2 136 | count = 0 137 | for n in range(Sides): 138 | Quartz.CGContextBeginPage(writeContext, sheetSize) 139 | for position in [leftPage, rightPage]: 140 | if imposedOrder[count]: 141 | page = Quartz.CGPDFDocumentGetPage(source, imposedOrder[count]) 142 | Quartz.CGContextSaveGState(writeContext) 143 | # Check PDF page rotation AND mediabox orientation. 144 | angle = getRotation(page) 145 | Quartz.CGContextConcatCTM(writeContext, Quartz.CGPDFPageGetDrawingTransform(page, Quartz.kCGPDFMediaBox, position, angle, True)) 146 | # Uncomment next line to draw box round each page 147 | # Quartz.CGContextStrokeRectWithWidth(writeContext, leftPage, 2.0) 148 | Quartz.CGContextDrawPDFPage(writeContext, page) 149 | Quartz.CGContextRestoreGState(writeContext) 150 | count += 1 151 | Quartz.CGContextEndPage(writeContext) 152 | 153 | # Set creep for next sheet. 154 | if count%4 == 0: 155 | leftPage[0][0] += creep 156 | rightPage[0][0] -= creep 157 | 158 | # Do tidying up 159 | contextDone(writeContext) 160 | 161 | if __name__ == "__main__": 162 | for filename in sys.argv[1:]: 163 | makeBooklet(filename) 164 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/countpages.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | # COUNTPAGES: Adds together the sum of pages from all PDFs supplied as arguments. 5 | # by Ben Byram-Wigfield v.1.7 6 | 7 | # Uses an Alert dialog to report the number! 8 | 9 | import sys 10 | from Quartz import (PDFDocument, CGPDFDocumentCreateWithProvider, CGDataProviderCreateWithFilename, CGPDFDocumentGetNumberOfPages) 11 | from AppKit import (NSApp, NSAlert, NSInformationalAlertStyle, NSURL) 12 | 13 | pdfnum=0 14 | 15 | def displayAlert(message, info, buttons): 16 | alert = NSAlert.alloc().init() 17 | alert.setMessageText_(message) 18 | alert.setInformativeText_(info) 19 | alert.setAlertStyle_(NSInformationalAlertStyle) 20 | for button in buttons: 21 | alert.addButtonWithTitle_(button) 22 | NSApp.activateIgnoringOtherApps_(True) 23 | buttonPressed = alert.runModal() 24 | return buttonPressed 25 | # First button will return 1000, second 1001, etc.. 26 | 27 | def pageCount(pdfPath): 28 | # The first way of counting pages, using PDFDocument. 29 | pdfPath = pdfPath.decode('utf-8') 30 | pdfURL = NSURL.fileURLWithPath_(pdfPath) 31 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 32 | if pdfDoc: 33 | return pdfDoc.pageCount() 34 | 35 | if __name__ == '__main__': 36 | 37 | for filename in sys.argv[1:]: 38 | pdfnum=pdfnum+pageCount(filename) 39 | 40 | displayAlert("Combined Page Count:", str(pdfnum), ["OK"]) 41 | 42 | # Or just print the number to stdout. 43 | # print(pdfnum) 44 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/cropPDF.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | # CROP PDF v.1.0 : Crop the mediabox by a given set of margins. 5 | # by Ben Byram-Wigfield v1.0 6 | 7 | # Use on the Command line with filenames as arguments (e.g. cropPDF.py /path/to/file.pdf) 8 | # Or in Automator as a Quick Action/Service. Select "Receives PDF Files in Finder" 9 | # Then Add "Run Shell Script" action. Select /usr/bin/python from the drop-down list; 10 | # Select "Pass Input" as "as arguments". 11 | # Paste script into area for scripts, replacing existing text. 12 | 13 | import sys 14 | import os 15 | from Quartz import PDFDocument, kPDFDisplayBoxMediaBox, kPDFDisplayBoxTrimBox, CGRectEqualToRect, CGRectMake 16 | from CoreFoundation import NSURL 17 | 18 | mediabox = kPDFDisplayBoxMediaBox 19 | # Margins: left margin, bottom margin, right margin, top margin. 20 | margins = [45, 45, 45, 45] 21 | 22 | 23 | def trimPDF(filename): 24 | filename = filename.decode('utf-8') 25 | shortName = os.path.splitext(filename)[0] 26 | outFilename = shortName + " TPS.pdf" 27 | pdfURL = NSURL.fileURLWithPath_(filename) 28 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 29 | if pdfDoc: 30 | pages = pdfDoc.pageCount() 31 | for p in range(0, pages): 32 | page = pdfDoc.pageAtIndex_(p) 33 | mediaBoxSize = page.boundsForBox_(mediabox) 34 | trimBoxSize = CGRectMake(margins[0], margins[1], (mediaBoxSize.size.width - margins[2] - margins[0]), (mediaBoxSize.size.height - margins[3] - margins[1])) 35 | page.setBounds_forBox_(trimBoxSize, mediabox) 36 | 37 | pdfDoc.writeToFile_(outFilename) 38 | 39 | if __name__ == '__main__': 40 | for filename in sys.argv[1:]: 41 | trimPDF(filename) 42 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/encrypt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # ENCRYPT : Encrypt PDF and lock with password. 5 | # by Ben Byram-Wigfield v.1.1 6 | # but copying or extracting requires the Owner Password. 7 | # WARNING: Some versions of OS X (High Sierra) corrupt PDF metadata after encryption. 8 | 9 | import os, sys 10 | from Quartz import PDFDocument, kCGPDFContextAllowsCopying, kCGPDFContextAllowsPrinting, kCGPDFContextUserPassword, kCGPDFContextOwnerPassword 11 | from CoreFoundation import (NSURL) 12 | 13 | copyPassword = "12345678" # Password for copying and printing 14 | openPassword = copyPassword # Or enter a different password to open the file. 15 | # Set openPassword as '' to allow opening. 16 | 17 | def encrypt(filename): 18 | filename =filename.decode('utf-8') 19 | if not filename: 20 | print 'Unable to open input file' 21 | sys.exit(2) 22 | shortName = os.path.splitext(filename)[0] 23 | outputfile = shortName+" locked.pdf" 24 | pdfURL = NSURL.fileURLWithPath_(filename) 25 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 26 | if pdfDoc : 27 | options = { 28 | kCGPDFContextAllowsCopying: False, 29 | kCGPDFContextAllowsPrinting: False, 30 | kCGPDFContextOwnerPassword: copyPassword, 31 | kCGPDFContextUserPassword: openPassword} 32 | pdfDoc.writeToFile_withOptions_(outputfile, options) 33 | return 34 | 35 | if __name__ == "__main__": 36 | for filename in sys.argv[1:]: 37 | encrypt(filename) 38 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/graphpaper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | # 4 | # GRAPHPAPER: v1.1 5 | # Modified from /System/Library/Automator/Add Grid to PDF Documents.action/Contents/Resources/graphpaper.py 6 | # to include small and large gradations; better handling of existing metadata; and made faster! 7 | 8 | import sys 9 | import os 10 | import Quartz as Quartz 11 | from Foundation import NSURL, kCFAllocatorDefault 12 | 13 | # Loads in PDF document 14 | def createPDFDocumentWithPath(path): 15 | url = NSURL.fileURLWithPath_(path) 16 | return Quartz.CGPDFDocumentCreateWithURL(url) 17 | 18 | # Creates a Context for drawing 19 | def createOutputContextWithPath(path, dictarray): 20 | url = NSURL.fileURLWithPath_(path) 21 | return Quartz.CGPDFContextCreateWithURL(url, None, dictarray) 22 | 23 | # Gets DocInfo from input file to pass to output. 24 | # PyObjC returns Keywords in an NSArray; they must be tupled. 25 | def getDocInfo(file): 26 | file = file.decode('utf-8') 27 | pdfURL = NSURL.fileURLWithPath_(file) 28 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 29 | if pdfDoc: 30 | metadata = pdfDoc.documentAttributes() 31 | if "Keywords" in metadata: 32 | keys = metadata["Keywords"] 33 | mutableMetadata = metadata.mutableCopy() 34 | mutableMetadata["Keywords"] = tuple(keys) 35 | return mutableMetadata 36 | else: 37 | return metadata 38 | 39 | def drawLines(pdf, mediaBox, grid, red, green, blue, alpha) : 40 | Quartz.CGContextSetRGBStrokeColor(pdf, red, green, blue, alpha) 41 | x = mediaBox.origin.x 42 | count = -1 43 | while x <= mediaBox.size.width: 44 | count += 1 45 | if not count%grid[1]: 46 | Quartz.CGContextSetLineWidth(pdf, 0.5) 47 | else: 48 | Quartz.CGContextSetLineWidth(pdf, 0.1) 49 | Quartz.CGContextMoveToPoint(pdf, x, mediaBox.origin.y) 50 | Quartz.CGContextAddLineToPoint(pdf, x, mediaBox.origin.y + mediaBox.size.height) 51 | Quartz.CGContextStrokePath(pdf) 52 | x += grid[0] 53 | 54 | y = mediaBox.origin.y 55 | count = -1 56 | while y < mediaBox.size.height: 57 | count += 1 58 | if not count%grid[1]: 59 | Quartz.CGContextSetLineWidth(pdf, 0.5) 60 | else: 61 | Quartz.CGContextSetLineWidth(pdf, 0.1) 62 | Quartz.CGContextMoveToPoint(pdf, mediaBox.origin.x, y) 63 | Quartz.CGContextAddLineToPoint(pdf, mediaBox.origin.x + mediaBox.size.width, y) 64 | Quartz.CGContextStrokePath(pdf) 65 | y += grid[0] 66 | 67 | def makeGrid(filename): 68 | 69 | # The distance, in points, between each grid line, and frequency of thicker lines: 70 | # Default is 10pt, 10 (Useful for determining values in points) 71 | # Other useful values are: 72 | # [12, 6] (Points and Inches) 73 | # [9, 8] (1/8ths and Inches) 74 | # [2.835, 10] (mm, cm) 75 | gridSize = [10,10] 76 | 77 | # The color of the grid lines 78 | # Default is dark Yellow 79 | red = 1 80 | green = 0.6 81 | blue = 0.1 82 | alpha = 1 83 | 84 | over = 1 85 | 86 | writeContext = None 87 | 88 | shortName = os.path.splitext(filename)[0] 89 | outFilename = shortName + "+grid.pdf" 90 | metaDict = getDocInfo(filename) 91 | 92 | writeContext = createOutputContextWithPath(outFilename, metaDict) 93 | readPDF = createPDFDocumentWithPath(filename) 94 | 95 | if writeContext != None and readPDF != None: 96 | numPages = Quartz.CGPDFDocumentGetNumberOfPages(readPDF) 97 | for pageNum in xrange(1, numPages + 1): 98 | page = Quartz.CGPDFDocumentGetPage(readPDF, pageNum) 99 | if page: 100 | mediaBox = Quartz.CGPDFPageGetBoxRect(page, Quartz.kCGPDFMediaBox) 101 | if Quartz.CGRectIsEmpty(mediaBox): 102 | mediaBox = None 103 | Quartz.CGContextBeginPage(writeContext, mediaBox) 104 | if (not over) : 105 | drawLines(writeContext, mediaBox, gridSize, red, green, blue, alpha) 106 | Quartz.CGContextDrawPDFPage(writeContext, page) 107 | if (over) : 108 | drawLines(writeContext, mediaBox, gridSize, red, green, blue, alpha) 109 | Quartz.CGContextEndPage(writeContext) 110 | Quartz.CGPDFContextClose(writeContext) 111 | del writeContext 112 | 113 | else: 114 | print "A valid input file and output file must be supplied." 115 | sys.exit(1) 116 | 117 | if __name__ == "__main__": 118 | for filename in sys.argv[1:]: 119 | makeGrid(filename) 120 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/image2pdf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | # 4 | # IMAGE2PDF v.2.0 : Convert image files to one PDF. 5 | # by Ben Byram-Wigfield 6 | # Rewritten using PDFKit. 7 | 8 | import sys, os 9 | import Quartz as Quartz 10 | from LaunchServices import kUTTypePDF 11 | from CoreFoundation import NSImage 12 | 13 | def getFilename(filepath, basename): 14 | fullname = basename + ".pdf" 15 | i=0 16 | while os.path.exists(os.path.join(filepath, fullname)): 17 | i += 1 18 | fullname = basename + " %02d.pdf"%i 19 | return os.path.join(filepath, fullname) 20 | 21 | 22 | def imageToPdf(argv): 23 | prefix = os.path.dirname(argv[0]) 24 | filename = "Combined" 25 | pdfout = getFilename(prefix, filename) 26 | 27 | for index, eachFile in enumerate(argv): 28 | image = NSImage.alloc().initWithContentsOfFile_(eachFile) 29 | if image: 30 | page = Quartz.PDFPage.alloc().initWithImage_(image) 31 | if index == 0: 32 | pageData = page.dataRepresentation() 33 | pdf = Quartz.PDFDocument.alloc().initWithData_(pageData) 34 | else: 35 | pdf.insertPage_atIndex_(page, index) 36 | 37 | pdf.writeToFile_(pdfout) 38 | 39 | 40 | if __name__ == "__main__": 41 | imageToPdf(sys.argv[1:]) 42 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/indexnumbers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # INDEX NUMBERS v.1.4 5 | # This script stamps "N of X" on the first page of all PDF documents passed to it, 6 | # where N is the sequential number of each document and X is the total. 7 | # by Ben Byram-Wigfield 8 | # Options for position, size, font are below. 9 | # With thanks to user Hiroto on Apple Support Communities. 10 | 11 | import sys, os, math 12 | import Quartz as Quartz 13 | from CoreText import (kCTFontAttributeName, CTFontCreateWithName, CTLineDraw, CTLineCreateWithAttributedString, kCTFontAttributeName, CTLineGetImageBounds) 14 | from CoreFoundation import (CFAttributedStringCreate, CFURLCreateFromFileSystemRepresentation, kCFAllocatorDefault, NSURL) 15 | from AppKit import NSFontManager 16 | 17 | 18 | # Creates a PDF Object from incoming file. 19 | def createPDFDocumentFromPath(path): 20 | url = NSURL.fileURLWithPath_(path) 21 | return Quartz.CGPDFDocumentCreateWithURL(url) 22 | 23 | # Creates a Context for drawing 24 | def createOutputContextWithPath(path, dictarray): 25 | url = NSURL.fileURLWithPath_(path) 26 | return Quartz.CGPDFContextCreateWithURL(url, None, dictarray) 27 | 28 | # Gets DocInfo from input file to pass to output. 29 | # PyObjC returns Keywords in an NSArray; they must be tupled. 30 | def getDocInfo(file): 31 | file = file.decode('utf-8') 32 | pdfURL = NSURL.fileURLWithPath_(file) 33 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 34 | if pdfDoc: 35 | metadata = pdfDoc.documentAttributes() 36 | if "Keywords" in metadata: 37 | keys = metadata["Keywords"] 38 | mutableMetadata = metadata.mutableCopy() 39 | mutableMetadata["Keywords"] = tuple(keys) 40 | return mutableMetadata 41 | else: 42 | return metadata 43 | 44 | # Closes the Context 45 | def contextDone(context): 46 | if context: 47 | Quartz.CGPDFContextClose(context) 48 | del context 49 | 50 | def drawWatermarkText(writeContext, line, xOffset, yOffset, angle, scale, opacity): 51 | if line: 52 | rect = CTLineGetImageBounds(line, writeContext) 53 | imageWidth = rect.size.width 54 | imageHeight = rect.size.height 55 | 56 | Quartz.CGContextSaveGState(writeContext) 57 | Quartz.CGContextSetAlpha(writeContext, opacity) 58 | Quartz.CGContextTranslateCTM(writeContext, xOffset, yOffset) 59 | Quartz.CGContextTranslateCTM(writeContext, imageWidth / 2, imageHeight / 2) 60 | Quartz.CGContextRotateCTM(writeContext, angle * math.pi / 180) 61 | Quartz.CGContextTranslateCTM(writeContext, -imageWidth / 2, -imageHeight / 2) 62 | Quartz.CGContextScaleCTM(writeContext, scale, scale) 63 | Quartz.CGContextSetTextPosition(writeContext, 0.0, 0.0) 64 | CTLineDraw(line, writeContext) 65 | Quartz.CGContextRestoreGState(writeContext) 66 | 67 | # Check that the selected font is active, else use Helvetica Bold. 68 | def selectFont(typeface, pointSize): 69 | manager = NSFontManager.sharedFontManager() 70 | fontList = list(manager.availableFonts()) 71 | if typeface not in fontList: 72 | typeface = 'Helvetica-Bold' 73 | return CTFontCreateWithName(typeface, pointSize, None) 74 | 75 | 76 | if __name__ == '__main__': 77 | 78 | 79 | # OPTIONS: Set the distance in points from bottom left corner of page; 80 | # For other uses, set the angle, scale, and opacity of text 81 | # Font must be the PostScript name (i.e. no spaces) (See Get Info in FontBook) 82 | xOffset, yOffset, angle, scale, opacity = 45.0, 800.0, 0.0, 1.0, 1.0 83 | font = selectFont('Helvetica-Bold', 12.0) 84 | 85 | 86 | for index, filename in enumerate(sys.argv[1:], start = 1): 87 | # Get path, create new folder 88 | totalCount = len(sys.argv[1:]) 89 | text = str(index) + " of " + str(totalCount) 90 | if index == 1: 91 | dirPath = os.path.dirname(filename) 92 | location = os.path.join(dirPath, "Indexed") 93 | try: 94 | os.mkdir(location) 95 | except: 96 | print "Can't create directory '%s'"%(location) 97 | sys.exit() 98 | nameOnly = os.path.basename(filename) 99 | outFilename = os.path.join(location, nameOnly) 100 | pdf = createPDFDocumentFromPath(filename) 101 | pages = Quartz.CGPDFDocumentGetNumberOfPages(pdf) 102 | metaDict = getDocInfo(filename) 103 | writeContext = createOutputContextWithPath(outFilename, metaDict) 104 | 105 | # Write page 1 with the added text 106 | if pdf: 107 | page = Quartz.CGPDFDocumentGetPage(pdf, 1) 108 | if page: 109 | mbox = Quartz.CGPDFPageGetBoxRect(page, Quartz.kCGPDFMediaBox) 110 | # if Quartz.CGRectIsEmpty(mbox): mbox = None 111 | Quartz.CGContextBeginPage(writeContext, mbox) 112 | Quartz.CGContextDrawPDFPage(writeContext, page) 113 | astr = CFAttributedStringCreate(kCFAllocatorDefault, text, { kCTFontAttributeName : font }) 114 | line = CTLineCreateWithAttributedString(astr) 115 | drawWatermarkText(writeContext, line, xOffset , yOffset, angle, scale, opacity) 116 | Quartz.CGContextEndPage(writeContext) 117 | 118 | # Write out the rest of the pages 119 | for i in range(2, (pages+1)): 120 | page = Quartz.CGPDFDocumentGetPage(pdf, i) 121 | if page: 122 | Quartz.CGContextBeginPage(writeContext, mbox) 123 | Quartz.CGContextDrawPDFPage(writeContext, page) 124 | Quartz.CGContextEndPage(writeContext) 125 | del pdf 126 | contextDone(writeContext) -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/joinpdfs.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # coding=utf-8 3 | # 4 | # JOINPDFS v2.2 : Tool to concatenate PDFs. 5 | # New tool built from the ground up using PDFKit, instead of Core Graphics. 6 | # Now writes Table of Contents for each file added; importing existing ToCs in each file! 7 | # by Ben Byram-Wigfield 8 | 9 | import sys 10 | import os.path 11 | from CoreFoundation import (CFURLCreateFromFileSystemRepresentation, kCFAllocatorDefault, NSURL, NSString) 12 | import Quartz as Quartz 13 | 14 | def createPDFDocumentWithPath(path): 15 | pdfURL = NSURL.fileURLWithPath_(path) 16 | if pdfURL: 17 | return Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 18 | 19 | def getFilename(filepath, basename): 20 | fullname = basename + ".pdf" 21 | i=0 22 | while os.path.exists(os.path.join(filepath, fullname)): 23 | i += 1 24 | fullname = basename + " %02d.pdf"%i 25 | return os.path.join(filepath, fullname) 26 | 27 | 28 | def getOutline(page, label, pageObject): 29 | pageSize = pageObject.boundsForBox_(Quartz.kCGPDFMediaBox) 30 | x = 0 31 | y = Quartz.CGRectGetHeight(pageSize) 32 | pagePoint = Quartz.CGPointMake(x,y-1) 33 | myDestination = Quartz.PDFDestination.alloc().initWithPage_atPoint_(pageObject, pagePoint) 34 | myLabel = NSString.stringWithString_(label) 35 | myOutline = Quartz.PDFOutline.alloc().init() 36 | myOutline.setLabel_(myLabel) 37 | myOutline.setDestination_(myDestination) 38 | return myOutline 39 | 40 | def join(incomingFiles): 41 | 42 | # Set the output file location and name. 43 | prefix = os.path.dirname(incomingFiles[0]) 44 | filename = "Combined" 45 | outfile = getFilename(prefix, filename) 46 | # Load in the first PDF file, to which the rest will be added. 47 | firstPDF = createPDFDocumentWithPath(incomingFiles[0]) 48 | outlineIndex = 0 49 | rootOutline = Quartz.PDFOutline.alloc().init() 50 | firstLabel = os.path.basename(incomingFiles[0]) 51 | firstPage = firstPDF.pageAtIndex_(0) 52 | firstOutline = getOutline(0, firstLabel, firstPage) 53 | # Check and copy existing Outlines into new structure. 54 | existingOutline = firstPDF.outlineRoot() 55 | if existingOutline: 56 | i=0 57 | while i < (existingOutline.numberOfChildren()): 58 | childOutline = existingOutline.childAtIndex_(i) 59 | firstOutline.insertChild_atIndex_(childOutline, i) 60 | i +=1 61 | rootOutline.insertChild_atIndex_(firstOutline, outlineIndex) 62 | 63 | 64 | # create PDFDocument object for the remaining files. 65 | pdfObjects = map(createPDFDocumentWithPath, incomingFiles[1:]) 66 | for index, doc in enumerate(pdfObjects): 67 | if doc: 68 | pages = doc.pageCount() 69 | pageIndex = firstPDF.pageCount() 70 | tocLabel = os.path.basename(incomingFiles[index+1]) 71 | for p in range(0, pages): 72 | page = doc.pageAtIndex_(p) 73 | firstPDF.insertPage_atIndex_(page, pageIndex+p) 74 | if p == 0: 75 | outline = getOutline(pageIndex, tocLabel, page) 76 | existingOutline = doc.outlineRoot() 77 | if existingOutline: 78 | i=0 79 | while i < (existingOutline.numberOfChildren()): 80 | childOutline = existingOutline.childAtIndex_(i) 81 | outline.insertChild_atIndex_(childOutline, i) 82 | i +=1 83 | 84 | rootOutline.insertChild_atIndex_(outline, index+1) 85 | # Add the root Outline to the first PDF. 86 | firstPDF.setOutlineRoot_(rootOutline) 87 | firstPDF.writeToFile_(outfile) 88 | 89 | if __name__ == "__main__": 90 | if len(sys.argv) > 1: 91 | join(sys.argv[1:]) 92 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/metadata.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | # METADATA : Add or Change metadata to a PDF file. 5 | # by Ben Byram-Wigfield 6 | # 7 | import sys 8 | import os 9 | import getopt 10 | import Quartz.CoreGraphics as Quartz 11 | 12 | from CoreFoundation import NSURL 13 | 14 | def setMetadata(filename): 15 | options = {} 16 | author='Ben Byram-Wigfield' 17 | creator = 'PDFSuite Python Scripts' 18 | subject = '' 19 | keywords = 'PDF Magic' 20 | 21 | # Get Title from filename. Or delete these two lines and set a string value. 22 | title = os.path.basename(filename) 23 | title = os.path.splitext(title)[0] 24 | 25 | authorKey = Quartz.kCGPDFContextAuthor 26 | creatorKey = Quartz.kCGPDFContextCreator 27 | subjectKey = Quartz.kCGPDFContextSubject 28 | keywordsKey = Quartz.kCGPDFContextKeywords 29 | titleKey = Quartz.kCGPDFContextTitle 30 | 31 | filename = filename.decode('utf-8') 32 | shortName = os.path.splitext(filename)[0] 33 | pdfURL = NSURL.fileURLWithPath_(filename) 34 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 35 | 36 | # Default value option: 37 | if author: 38 | options[authorKey] = author 39 | if creator: 40 | options[creatorKey] = creator 41 | if subject: 42 | options[subjectKey] = subject 43 | if keywords: 44 | options[keywordsKey] = keywords 45 | if title: 46 | options[titleKey] = title 47 | 48 | print options 49 | 50 | # To save to a separate file, uncomment the next line. 51 | # filename = shortName + " data.pdf" 52 | pdfDoc.writeToFile_withOptions_(filename, options) 53 | 54 | if __name__ == "__main__": 55 | for filepath in sys.argv[1:]: 56 | setMetadata(filepath) 57 | 58 | """ 59 | Dict keys include: 60 | 61 | kCGPDFContextAuthor (string) 62 | kCGPDFContextCreator 63 | kCGPDFContextTitle 64 | 65 | 66 | kCGPDFContextOwnerPassword 67 | kCGPDFContextUserPassword 68 | kCGPDFContextAllowsPrinting (boolean) 69 | kCGPDFContextAllowsCopying (boolean) 70 | 71 | kCGPDFContextOutputIntent 72 | kCGPDFContextOutputIntents 73 | kCGPDFContextSubject 74 | kCGPDFContextKeywords 75 | kCGPDFContextEncryptionKeyLength 76 | 77 | kCGPDFXOutputIntentSubtype 78 | kCGPDFXOutputConditionIdentifier 79 | kCGPDFXOutputCondition 80 | kCGPDFXRegistryName 81 | kCGPDFXInfo 82 | kCGPDFXDestinationOutputProfile 83 | 84 | See the Apple Documentation page on Auxiliary Dictionary Keys for PDF Context for more. 85 | 86 | """ 87 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/pagenumber.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # PAGE NUMBER v.1.8 5 | # This script places page numbers on facing pages (excluding page 1). Options for position, size, font are below. 6 | # By Ben Byram-Wigfield. 7 | # With thanks to user Hiroto on Apple Support Communities. 8 | 9 | import sys, os, math 10 | import Quartz as Quartz 11 | from CoreText import (kCTFontAttributeName, CTFontCreateWithName, CTLineDraw, CTLineCreateWithAttributedString, kCTFontAttributeName, CTLineGetImageBounds) 12 | from CoreFoundation import (CFAttributedStringCreate, CFURLCreateFromFileSystemRepresentation, kCFAllocatorDefault, NSURL) 13 | from AppKit import NSFontManager 14 | 15 | # Creates a PDF Object from incoming file. 16 | def createPDFDocumentWithPath(path): 17 | # return Quartz.CGPDFDocumentCreateWithURL(Quartz.CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, path, len(path), False)) 18 | url = NSURL.fileURLWithPath_(path) 19 | return Quartz.CGPDFDocumentCreateWithURL(url) 20 | 21 | # Creates a Context for drawing 22 | def createOutputContextWithPath(path, dictarray): 23 | url = NSURL.fileURLWithPath_(path) 24 | return Quartz.CGPDFContextCreateWithURL(url, None, dictarray) 25 | 26 | # Gets DocInfo from input file to pass to output. 27 | # PyObjC returns Keywords in an NSArray; they must be tupled. 28 | def getDocInfo(file): 29 | file = file.decode('utf-8') 30 | pdfURL = NSURL.fileURLWithPath_(file) 31 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 32 | if pdfDoc: 33 | metadata = pdfDoc.documentAttributes() 34 | if "Keywords" in metadata: 35 | keys = metadata["Keywords"] 36 | mutableMetadata = metadata.mutableCopy() 37 | mutableMetadata["Keywords"] = tuple(keys) 38 | return mutableMetadata 39 | else: 40 | return metadata 41 | 42 | # Check that the selected font is active, else use Helvetica Bold. 43 | def selectFont(typeface, pointSize): 44 | manager = NSFontManager.sharedFontManager() 45 | fontList = list(manager.availableFonts()) 46 | if typeface not in fontList: 47 | typeface = 'Helvetica-Bold' 48 | 49 | return CTFontCreateWithName(typeface, pointSize, None) 50 | 51 | # Closes the Context 52 | def contextDone(context): 53 | if context: 54 | Quartz.CGPDFContextClose(context) 55 | del context 56 | 57 | def drawWatermarkText(writeContext, line, xOffset, yOffset, angle, scale, opacity): 58 | if line: 59 | rect = CTLineGetImageBounds(line, writeContext) 60 | imageWidth = rect.size.width 61 | imageHeight = rect.size.height 62 | 63 | Quartz.CGContextSaveGState(writeContext) 64 | Quartz.CGContextSetAlpha(writeContext, opacity) 65 | Quartz.CGContextTranslateCTM(writeContext, xOffset, yOffset) 66 | Quartz.CGContextScaleCTM(writeContext, scale, scale) 67 | Quartz.CGContextTranslateCTM(writeContext, imageWidth / 2, imageHeight / 2) 68 | Quartz.CGContextRotateCTM(writeContext, angle * math.pi / 180) 69 | Quartz.CGContextTranslateCTM(writeContext, -imageWidth / 2, -imageHeight / 2) 70 | Quartz.CGContextSetTextPosition(writeContext, 0.0, 0.0); 71 | CTLineDraw(line, writeContext); 72 | Quartz.CGContextRestoreGState(writeContext) 73 | 74 | 75 | if __name__ == '__main__': 76 | 77 | for filename in sys.argv[1:]: 78 | shortName = os.path.splitext(filename)[0] 79 | outFilename = shortName + " NUM.pdf" 80 | pdf = createPDFDocumentWithPath(filename) 81 | metaDict = getDocInfo(filename) 82 | writeContext = createOutputContextWithPath(outFilename, metaDict) 83 | pages = Quartz.CGPDFDocumentGetNumberOfPages(pdf) 84 | 85 | # OPTIONS: Set the RELATIVE distance from outside top corner of page; 86 | # For other uses, set the angle, scale, and opacity of text 87 | # Font must be the PostScript name (i.e. no spaces) (See Get Info in FontBook) 88 | xOffset, yOffset, angle, scale, opacity = 45.0, 45.0, 0.0, 1.0, 1.0 89 | font = selectFont('TimesNewRomanPSMT', 12.0) 90 | 91 | if pdf: 92 | for i in range(1, (pages+1)): 93 | page = Quartz.CGPDFDocumentGetPage(pdf, i) 94 | if page: 95 | mbox = Quartz.CGPDFPageGetBoxRect(page, Quartz.kCGPDFMediaBox) 96 | if Quartz.CGRectIsEmpty(mbox): mbox = None 97 | Quartz.CGContextBeginPage(writeContext, mbox) 98 | Quartz.CGContextDrawPDFPage(writeContext, page) 99 | text = str(i) 100 | astr = CFAttributedStringCreate(kCFAllocatorDefault, text, { kCTFontAttributeName : font }) 101 | line = CTLineCreateWithAttributedString(astr) 102 | x = Quartz.CGRectGetWidth(mbox) 103 | y = Quartz.CGRectGetHeight(mbox) 104 | y -= yOffset 105 | if i == 1: # Don't put number on page 1 106 | pass 107 | elif i%2 == 1: # Move right hand number in by its own width. 108 | textWidth = astr.size().width 109 | x = x - xOffset 110 | x = x - textWidth 111 | drawWatermarkText(writeContext, line, x , y, angle, scale, opacity) 112 | else: 113 | x = xOffset 114 | drawWatermarkText(writeContext, line, x, y, angle, scale, opacity) 115 | 116 | Quartz.CGContextEndPage(writeContext) 117 | del pdf 118 | contextDone(writeContext) 119 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/pdf2png.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | """ 5 | PDF2PNG v.2.0: Creates a bitmap image from each page of each PDF supplied to it. 6 | by Ben Byram-Wigfield 7 | 8 | Acknowledgement is made to Jeff Laing for the CGContext bit. 9 | """ 10 | import os, sys 11 | import Quartz as Quartz 12 | from LaunchServices import (kUTTypeJPEG, kUTTypeTIFF, kUTTypePNG, kCFAllocatorDefault) 13 | 14 | resolution = 300.0 #dpi 15 | scale = resolution/72.0 16 | 17 | cs = Quartz.CGColorSpaceCreateWithName(Quartz.kCGColorSpaceSRGB) 18 | whiteColor = Quartz.CGColorCreate(cs, (1, 1, 1, 1)) 19 | # Options: kCGImageAlphaNoneSkipLast (no trans), kCGImageAlphaPremultipliedLast 20 | transparency = Quartz.kCGImageAlphaNoneSkipLast 21 | 22 | #Save image to file 23 | def writeImage (image, url, type, options): 24 | destination = Quartz.CGImageDestinationCreateWithURL(url, type, 1, None) 25 | Quartz.CGImageDestinationAddImage(destination, image, options) 26 | Quartz.CGImageDestinationFinalize(destination) 27 | return 28 | 29 | def getFilename(filepath): 30 | i=0 31 | newName = filepath 32 | while os.path.exists(newName): 33 | i += 1 34 | newName = filepath + " %02d"%i 35 | return newName 36 | 37 | if __name__ == '__main__': 38 | 39 | for filename in sys.argv[1:]: 40 | pdf = Quartz.CGPDFDocumentCreateWithProvider(Quartz.CGDataProviderCreateWithFilename(filename)) 41 | numPages = Quartz.CGPDFDocumentGetNumberOfPages(pdf) 42 | shortName = os.path.splitext(filename)[0] 43 | prefix = os.path.splitext(os.path.basename(filename))[0] 44 | folderName = getFilename(shortName) 45 | try: 46 | os.mkdir(folderName) 47 | except: 48 | print "Can't create directory '%s'"%(folderName) 49 | sys.exit() 50 | 51 | # For each page, create a file 52 | for i in range (1, numPages+1): 53 | page = Quartz.CGPDFDocumentGetPage(pdf, i) 54 | if page: 55 | #Get mediabox 56 | mediaBox = Quartz.CGPDFPageGetBoxRect(page, Quartz.kCGPDFMediaBox) 57 | x = Quartz.CGRectGetWidth(mediaBox) 58 | y = Quartz.CGRectGetHeight(mediaBox) 59 | x *= scale 60 | y *= scale 61 | r = Quartz.CGRectMake(0,0,x, y) 62 | # Create a Bitmap Context, draw a white background and add the PDF 63 | writeContext = Quartz.CGBitmapContextCreate(None, int(x), int(y), 8, 0, cs, transparency) 64 | Quartz.CGContextSaveGState (writeContext) 65 | Quartz.CGContextScaleCTM(writeContext, scale,scale) 66 | Quartz.CGContextSetFillColorWithColor(writeContext, whiteColor) 67 | Quartz.CGContextFillRect(writeContext, r) 68 | Quartz.CGContextDrawPDFPage(writeContext, page) 69 | Quartz.CGContextRestoreGState(writeContext) 70 | # Convert to an "Image" 71 | image = Quartz.CGBitmapContextCreateImage(writeContext) 72 | # Create unique filename per page 73 | outFile = folderName +"/" + prefix + " %03d.png"%i 74 | url = Quartz.CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, outFile, len(outFile), False) 75 | # kUTTypeJPEG, kUTTypeTIFF, kUTTypePNG 76 | type = kUTTypePNG 77 | # See the full range of image properties on Apple's developer pages. 78 | options = { 79 | Quartz.kCGImagePropertyDPIHeight: resolution, 80 | Quartz.kCGImagePropertyDPIWidth: resolution 81 | } 82 | writeImage (image, url, type, options) 83 | del page 84 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/pdf2tiff.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | """ 5 | PDF2TIFF : Creates a bitmap image from each page of each PDF supplied to it. 6 | by Ben Byram-Wigfield 7 | 8 | Acknowledgement is made to Jeff Laing for the basis of the script. 9 | """ 10 | import os, sys 11 | import Quartz as Quartz 12 | from LaunchServices import (kUTTypeJPEG, kUTTypeTIFF, kUTTypePNG, kCFAllocatorDefault) 13 | 14 | resolution = 300.0 #dpi 15 | scale = resolution/72.0 16 | 17 | cs = Quartz.CGColorSpaceCreateWithName(Quartz.kCGColorSpaceSRGB) 18 | whiteColor = Quartz.CGColorCreate(cs, (1, 1, 1, 1)) 19 | # Options: kCGImageAlphaNoneSkipLast (no trans), kCGImageAlphaPremultipliedLast 20 | transparency = Quartz.kCGImageAlphaNoneSkipLast 21 | 22 | #Save image to file 23 | def writeImage (image, url, type, options): 24 | destination = Quartz.CGImageDestinationCreateWithURL(url, type, 1, None) 25 | Quartz.CGImageDestinationAddImage(destination, image, options) 26 | Quartz.CGImageDestinationFinalize(destination) 27 | return 28 | 29 | def getFilename(filepath): 30 | i=0 31 | newName = filepath 32 | while os.path.exists(newName): 33 | i += 1 34 | newName = filepath + " %02d"%i 35 | return newName 36 | 37 | if __name__ == '__main__': 38 | 39 | for filename in sys.argv[1:]: 40 | pdf = Quartz.CGPDFDocumentCreateWithProvider(Quartz.CGDataProviderCreateWithFilename(filename)) 41 | numPages = Quartz.CGPDFDocumentGetNumberOfPages(pdf) 42 | shortName = os.path.splitext(filename)[0] 43 | prefix = os.path.splitext(os.path.basename(filename))[0] 44 | folderName = getFilename(shortName) 45 | try: 46 | os.mkdir(folderName) 47 | except: 48 | print "Can't create directory '%s'"%(folderName) 49 | sys.exit() 50 | 51 | # For each page, create a file 52 | for i in range (1, numPages+1): 53 | page = Quartz.CGPDFDocumentGetPage(pdf, i) 54 | if page: 55 | #Get mediabox 56 | mediaBox = Quartz.CGPDFPageGetBoxRect(page, Quartz.kCGPDFMediaBox) 57 | x = Quartz.CGRectGetWidth(mediaBox) 58 | y = Quartz.CGRectGetHeight(mediaBox) 59 | x *= scale 60 | y *= scale 61 | r = Quartz.CGRectMake(0,0,x, y) 62 | # Create a Bitmap Context, draw a white background and add the PDF 63 | writeContext = Quartz.CGBitmapContextCreate(None, int(x), int(y), 8, 0, cs, transparency) 64 | Quartz.CGContextSaveGState (writeContext) 65 | Quartz.CGContextScaleCTM(writeContext, scale,scale) 66 | Quartz.CGContextSetFillColorWithColor(writeContext, whiteColor) 67 | Quartz.CGContextFillRect(writeContext, r) 68 | Quartz.CGContextDrawPDFPage(writeContext, page) 69 | Quartz.CGContextRestoreGState(writeContext) 70 | # Convert to an "Image" 71 | image = Quartz.CGBitmapContextCreateImage(writeContext) 72 | # Create unique filename per page 73 | outFile = folderName +"/" + prefix + " %03d.tif"%i 74 | url = Quartz.CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, outFile, len(outFile), False) 75 | # kUTTypeJPEG, kUTTypeTIFF, kUTTypePNG 76 | type = kUTTypeTIFF 77 | # See the full range of image properties on Apple's developer pages. 78 | options = { 79 | Quartz.kCGImagePropertyDPIHeight: resolution, 80 | Quartz.kCGImagePropertyDPIWidth: resolution 81 | } 82 | writeImage (image, url, type, options) 83 | del page 84 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/pdf2txt.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # coding: utf-8 3 | 4 | # PDF2TXT: Output text content of a PDF file to a new text file 5 | # by Ben Byram-Wigfield v.1.0 6 | 7 | import os, sys 8 | from Quartz import PDFDocument, kCGPDFContextAllowsCopying, kCGPDFContextUserPassword, kCGPDFContextOwnerPassword 9 | from CoreFoundation import (NSURL, NSString) 10 | 11 | # Can't seem to import this constant, so manually creating it. 12 | NSUTF8StringEncoding = 4 13 | 14 | def main(): 15 | for filename in sys.argv[1:]: 16 | inputfile =filename.decode('utf-8') 17 | shortName = os.path.splitext(filename)[0] 18 | outputfile = shortName+" text.txt" 19 | pdfURL = NSURL.fileURLWithPath_(inputfile) 20 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 21 | if pdfDoc : 22 | pdfString = NSString.stringWithString_(pdfDoc.string()) 23 | pdfString.writeToFile_atomically_encoding_error_(outputfile, True, NSUTF8StringEncoding, None) 24 | 25 | if __name__ == "__main__": 26 | main() 27 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/removePage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | # REMOVEPAGE v.1.0 : Removes the first page of any PDF file(s) sent as arguments. 5 | # Unless there's only one page. 6 | 7 | # by Ben Byram-Wigfield. 8 | 9 | from Quartz import PDFDocument 10 | import sys 11 | from Foundation import NSURL 12 | 13 | def removePage(filename): 14 | filename = filename.decode('utf-8') 15 | pdfURL = NSURL.fileURLWithPath_(filename) 16 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 17 | if pdfDoc: 18 | pageNum = pdfDoc.pageCount() 19 | if pageNum > 1: 20 | pdfDoc.removePageAtIndex_(0) 21 | pdfDoc.writeToFile_(filename) 22 | return 23 | 24 | if __name__ == '__main__': 25 | for filename in sys.argv[1:]: 26 | removePage(filename) 27 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/rinsePDF.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # RINSE PDF v1.0 : This script will re-save a PDF, which may fix some errors in the PDF data. 4 | # by Ben Byram-Wigfield 5 | 6 | import sys 7 | import os 8 | import Quartz as Quartz 9 | from CoreFoundation import NSURL 10 | 11 | 12 | for inputfile in sys.argv[1:]: 13 | outfile = inputfile 14 | # To save with a new name, uncomment the lines below. 15 | # prefix = os.path.splitext(inputfile) 16 | # outfile = prefix[0] + 'rinsed.pdf' 17 | pdfURL = NSURL.fileURLWithPath_(inputfile) 18 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 19 | pdfDoc.writeToFile_(outfile) 20 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/rotate.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # coding=utf-8 3 | # Produces new PDF file with all pages rotated by 90 degrees. 4 | # by Ben Byram-Wigfield v2.1 5 | 6 | # There are two ways to rotate a PDF page/file. 7 | # 1: Create a new PDF context, graphically transform each page of the original and save the file. 8 | # 2: Adjust the 'rotation' parameter in each page. 9 | # This is the 2nd way, which is easier. 10 | # It also preserves DocInfo and other metadata. 11 | 12 | import sys 13 | import os 14 | from Quartz import PDFDocument 15 | from CoreFoundation import NSURL 16 | 17 | if __name__ == '__main__': 18 | 19 | for filename in sys.argv[1:]: 20 | filename = filename.decode('utf-8') 21 | shortName = os.path.splitext(filename)[0] 22 | outFilename = shortName + "+90.pdf" 23 | pdfURL = NSURL.fileURLWithPath_(filename) 24 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 25 | pages = pdfDoc.pageCount() 26 | for p in range(0, pages): 27 | page = pdfDoc.pageAtIndex_(p) 28 | existingRotation = page.rotation() 29 | newRotation = existingRotation + 90 30 | page.setRotation_(newRotation) 31 | 32 | pdfDoc.writeToFile_(outFilename) 33 | 34 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/splitPDF.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | """ 4 | SPLITPDF v2.0 : Takes an existing PDF and creates individual page documents in a new folder. 5 | by Ben Byram-Wigfield 6 | 7 | New tool rebuilt using PDFKit, instead of Core Graphics. 8 | 9 | """ 10 | import os, sys 11 | import Quartz as Quartz 12 | from LaunchServices import (kUTTypeJPEG, kUTTypeTIFF, kUTTypePNG, kCFAllocatorDefault) 13 | from CoreFoundation import (CFAttributedStringCreate, CFURLCreateFromFileSystemRepresentation, NSURL) 14 | 15 | 16 | def createPDFDocumentWithPath(path): 17 | path = path.decode('utf-8') 18 | pdfURL = NSURL.fileURLWithPath_(path) 19 | if pdfURL: 20 | return Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 21 | 22 | def getFilename(filepath): 23 | i=0 24 | newName = filepath 25 | while os.path.exists(newName): 26 | i += 1 27 | newName = filepath + " %02d"%i 28 | return newName 29 | 30 | def strip(filename): 31 | # 32 | pdf = createPDFDocumentWithPath(filename) 33 | numPages = pdf.pageCount() 34 | shortName = os.path.splitext(filename)[0] 35 | prefix = os.path.splitext(os.path.basename(filename))[0] 36 | metaDict = pdf.documentAttributes() 37 | folderName = getFilename(shortName) 38 | try: 39 | os.mkdir(folderName) 40 | except: 41 | print "Can't create directory '%s'"%(folderName) 42 | sys.exit() 43 | 44 | # For each page, create a file. Index starts at ZERO!!! 45 | # You won't get leading zeros in filenames beyond 99. 46 | for i in range (1, numPages+1): 47 | page = pdf.pageAtIndex_(i-1) 48 | if page: 49 | newDoc = Quartz.PDFDocument.alloc().initWithData_(page.dataRepresentation()) 50 | outFile = folderName +"/" + prefix + " %03d.pdf"%i 51 | newDoc.writeToFile_withOptions_(outFile, metaDict) 52 | 53 | if __name__ == "__main__": 54 | for filename in sys.argv[1:]: 55 | strip(filename) 56 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/tint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | # 4 | # TINT: v1.1 5 | # Overlays a tint over the entire PDF. 6 | # by Ben Byram-Wigfield 7 | 8 | import sys 9 | import os 10 | import Quartz as Quartz 11 | from Foundation import NSURL, kCFAllocatorDefault 12 | 13 | # Loads in PDF document 14 | def createPDFDocumentWithPath(path): 15 | url = NSURL.fileURLWithPath_(path) 16 | return Quartz.CGPDFDocumentCreateWithURL(url) 17 | 18 | # Creates a Context for drawing 19 | def createOutputContextWithPath(path, dictarray): 20 | url = NSURL.fileURLWithPath_(path) 21 | return Quartz.CGPDFContextCreateWithURL(url, None, dictarray) 22 | 23 | 24 | def makeRectangle(x, y, xSize, ySize, color, alpha): 25 | red, green, blue = color[:] 26 | Quartz.CGContextSetRGBFillColor (writeContext, red, green, blue, alpha) 27 | Quartz.CGContextFillRect (writeContext, Quartz.CGRectMake(x, y, xSize, ySize)) 28 | return 29 | 30 | # Gets DocInfo from input file to pass to output. 31 | # PyObjC returns Keywords in an NSArray; they must be tupled. 32 | def getDocInfo(file): 33 | # file = file.decode('utf-8') 34 | pdfURL = NSURL.fileURLWithPath_(file) 35 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 36 | if pdfDoc: 37 | metadata = pdfDoc.documentAttributes() 38 | if "Keywords" in metadata: 39 | keys = metadata["Keywords"] 40 | mutableMetadata = metadata.mutableCopy() 41 | mutableMetadata["Keywords"] = tuple(keys) 42 | return mutableMetadata 43 | else: 44 | return metadata 45 | 46 | def tint(filename): 47 | global writeContext 48 | # The color of the tint. Sepia is 0.44, 0.26, 0.08. Some transparency may help. 49 | red = 0.5 50 | green = 0.3 51 | blue = 0.1 52 | alpha = 0.5 53 | myColor=[red, green, blue] 54 | 55 | writeContext = None 56 | 57 | shortName = os.path.splitext(filename)[0] 58 | outFilename = shortName + "+tint.pdf" 59 | metaDict = getDocInfo(filename) 60 | 61 | writeContext = createOutputContextWithPath(outFilename, metaDict) 62 | readPDF = createPDFDocumentWithPath(filename) 63 | 64 | if writeContext != None and readPDF != None: 65 | numPages = Quartz.CGPDFDocumentGetNumberOfPages(readPDF) 66 | for pageNum in range(1, numPages + 1): 67 | page = Quartz.CGPDFDocumentGetPage(readPDF, pageNum) 68 | if page: 69 | mediaBox = Quartz.CGPDFPageGetBoxRect(page, Quartz.kCGPDFMediaBox) 70 | if Quartz.CGRectIsEmpty(mediaBox): 71 | mediaBox = None 72 | Quartz.CGContextBeginPage(writeContext, mediaBox) 73 | Quartz.CGContextSetBlendMode(writeContext, Quartz.kCGBlendModeDifference) 74 | Quartz.CGContextDrawPDFPage(writeContext, page) 75 | makeRectangle(0, 0, mediaBox.size.width, mediaBox.size.height, myColor, alpha) 76 | Quartz.CGContextEndPage(writeContext) 77 | Quartz.CGPDFContextClose(writeContext) 78 | del writeContext 79 | 80 | else: 81 | print ("A valid input file and output file must be supplied.") 82 | sys.exit(1) 83 | if __name__ == "__main__": 84 | for filename in sys.argv[1:]: 85 | 86 | tint(filename) 87 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/trimPDF.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # coding=utf-8 3 | 4 | # TRIM PDF v.1.0 : Crop the mediabox to the size of the trimbox, if different. 5 | # This lets you crop a page containing printers crop marks to the trimmed page size. 6 | # by Ben Byram-Wigfield v1.0 7 | 8 | import sys 9 | import os 10 | from Quartz import PDFDocument, kPDFDisplayBoxMediaBox, kPDFDisplayBoxTrimBox, CGRectEqualToRect 11 | from CoreFoundation import NSURL 12 | 13 | mediabox = kPDFDisplayBoxMediaBox 14 | trimbox = kPDFDisplayBoxTrimBox 15 | 16 | def trimPDF(filename): 17 | hasBeenChanged = False 18 | filename = filename.decode('utf-8') 19 | shortName = os.path.splitext(filename)[0] 20 | outFilename = shortName + " TPS.pdf" 21 | pdfURL = NSURL.fileURLWithPath_(filename) 22 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 23 | if pdfDoc: 24 | pages = pdfDoc.pageCount() 25 | for p in range(0, pages): 26 | page = pdfDoc.pageAtIndex_(p) 27 | mediaBoxSize = page.boundsForBox_(mediabox) 28 | trimBoxSize = page.boundsForBox_(trimbox) 29 | if not CGRectEqualToRect(mediaBoxSize, trimBoxSize): 30 | page.setBounds_forBox_(trimBoxSize, mediabox) 31 | hasBeenChanged = True 32 | if hasBeenChanged: 33 | pdfDoc.writeToFile_(outFilename) 34 | 35 | if __name__ == '__main__': 36 | for filename in sys.argv[1:]: 37 | trimPDF(filename) 38 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Scripts/watermark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # WATERMARK: Superimposed text on pages of PDF documents. 5 | # By Ben Byram-Wigfield v1.5 6 | # Options for position, size, font, text and opacity are below. 7 | # With thanks to user Hiroto on Apple Support Communities. 8 | 9 | import sys, os, math 10 | import Quartz as Quartz 11 | from CoreText import (kCTFontAttributeName, CTFontCreateWithName, CTLineDraw, CTLineCreateWithAttributedString, kCTFontAttributeName, CTLineGetImageBounds) 12 | from CoreFoundation import (CFAttributedStringCreate, CFURLCreateFromFileSystemRepresentation, kCFAllocatorDefault, NSURL) 13 | from AppKit import NSFontManager 14 | 15 | 16 | # Creates a PDF Object from incoming file. 17 | def createPDFDocumentWithPath(path): 18 | url = NSURL.fileURLWithPath_(path) 19 | return Quartz.CGPDFDocumentCreateWithURL(url) 20 | 21 | # Creates a Context for drawing 22 | def createOutputContextWithPath(path, dictarray): 23 | url = NSURL.fileURLWithPath_(path) 24 | return Quartz.CGPDFContextCreateWithURL(url, None, dictarray) 25 | 26 | # Gets DocInfo from input file to pass to output. 27 | # PyObjC returns Keywords in an NSArray; they must be tupled. 28 | def getDocInfo(file): 29 | file = file.decode('utf-8') 30 | pdfURL = NSURL.fileURLWithPath_(file) 31 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 32 | if pdfDoc: 33 | metadata = pdfDoc.documentAttributes() 34 | if "Keywords" in metadata: 35 | keys = metadata["Keywords"] 36 | mutableMetadata = metadata.mutableCopy() 37 | mutableMetadata["Keywords"] = tuple(keys) 38 | return mutableMetadata 39 | else: 40 | return metadata 41 | 42 | # Closes the Context 43 | def contextDone(context): 44 | if context: 45 | Quartz.CGPDFContextClose(context) 46 | del context 47 | 48 | def drawWatermarkText(writeContext, line, xOffset, yOffset, angle, opacity): 49 | if line: 50 | rect = CTLineGetImageBounds(line, writeContext) 51 | imageWidth = rect.size.width 52 | imageHeight = rect.size.height 53 | Quartz.CGContextSaveGState(writeContext) 54 | Quartz.CGContextSetAlpha(writeContext, opacity) 55 | Quartz.CGContextTranslateCTM(writeContext, xOffset, yOffset) 56 | Quartz.CGContextRotateCTM(writeContext, angle * math.pi / 180) 57 | Quartz.CGContextSetTextPosition(writeContext, 0.0, 0.0) 58 | CTLineDraw(line, writeContext) 59 | Quartz.CGContextRestoreGState(writeContext) 60 | # return 61 | 62 | # Check that the selected font is active, else use Helvetica Bold. 63 | def selectFont(typeface, pointSize): 64 | manager = NSFontManager.sharedFontManager() 65 | fontList = list(manager.availableFonts()) 66 | if typeface not in fontList: 67 | typeface = 'Helvetica-Bold' 68 | 69 | return CTFontCreateWithName(typeface, pointSize, None) 70 | 71 | def getFilename(filepath, suffix): 72 | fullname = filepath + suffix + ".pdf" 73 | i=0 74 | while os.path.exists(fullname): 75 | i += 1 76 | fullname = filepath + suffix + " %02d.pdf"%i 77 | return fullname 78 | 79 | if __name__ == '__main__': 80 | 81 | # OPTIONS: Set the distance from bottom left of page; 82 | # Set the angle and opacity of text 83 | # Font must be the PostScript name (i.e. no spaces) (See Get Info in FontBook) 84 | xOffset, yOffset, angle, opacity = 110.0, 200.0, 45.0, 0.5 85 | font = selectFont('Helvetica-Bold', 150.0) 86 | text = "SAMPLE" 87 | 88 | for filename in sys.argv[1:]: 89 | print filename 90 | shortName = os.path.splitext(filename)[0] 91 | outFilename = getFilename(shortName, " WM") 92 | pdf = createPDFDocumentWithPath(filename) 93 | metaDict = getDocInfo(filename) 94 | writeContext = createOutputContextWithPath(outFilename, metaDict) 95 | pages = Quartz.CGPDFDocumentGetNumberOfPages(pdf) 96 | 97 | if pdf: 98 | for i in range(1, (pages+1)): 99 | page = Quartz.CGPDFDocumentGetPage(pdf, i) 100 | if page: 101 | mbox = Quartz.CGPDFPageGetBoxRect(page, Quartz.kCGPDFMediaBox) 102 | # if Quartz.CGRectIsEmpty(mbox): mbox = None 103 | Quartz.CGContextBeginPage(writeContext, mbox) 104 | Quartz.CGContextDrawPDFPage(writeContext, page) 105 | astr = CFAttributedStringCreate(kCFAllocatorDefault, text, { kCTFontAttributeName : font }) 106 | line = CTLineCreateWithAttributedString(astr) 107 | drawWatermarkText(writeContext, line, xOffset , yOffset, angle, opacity) 108 | Quartz.CGContextEndPage(writeContext) 109 | del pdf 110 | contextDone(writeContext) 111 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Services/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Automator_Services/.DS_Store -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Services/Add Blank Page.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Automator_Services/Add Blank Page.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Services/Add Index Numbers to PDFs.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Automator_Services/Add Index Numbers to PDFs.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Services/Add Page Numbering.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Automator_Services/Add Page Numbering.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Services/Count PDF Pages.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Automator_Services/Count PDF Pages.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Services/Images to PDF.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Automator_Services/Images to PDF.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Services/Join PDFs.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Automator_Services/Join PDFs.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Services/Make PDF-X.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Automator_Services/Make PDF-X.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Services/PDF 2 TIFF.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Automator_Services/PDF 2 TIFF.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Services/README.md: -------------------------------------------------------------------------------- 1 | Here is a collection of MacOS Automator workflows, designed to be used as 'Services'. Download them, unzip them, and install into USER/Library/Services. They will then be available from the Services menu of the Finder or on a right-click when PDFs are selected. 2 | 3 | Note that if files other than PDF documents are within the selection, the service will not appear in the menu. 4 | 5 | ***PRO TIP:*** The files are passed to the service in the order that they appear in the Finder. So re-ordering the files (e.g. ascending, descending, Name, Date modified, etc) will alter the order that they are processed. This may be useful/relevant when performing actions like Combining PDFs or Adding Index Numbers. 6 | 7 | Some of the scripts inside the workflows have options that can be altered. Double-click on the workflow to open it in Automator, and just edit the text accordingly. 8 | 9 | These workflows were created on MacOS Sierra (10.12). They should work on most OS versions before that, but if they are not recognized, you can 'roll your own' in Automator using the bare scripts. The instructions are in the _Automator_Scripts_ folder. 10 | 11 | ### For MacOS 10.14 or later, use the Quick Actions workflows instead. # 12 | -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Services/Rotate PDF pages.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Automator_Services/Rotate PDF pages.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Services/Split PDF pages.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Automator_Services/Split PDF pages.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Automator_Services/Watermark PDF.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Automator_Services/Watermark PDF.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/PDF_Services/ReadMe.md: -------------------------------------------------------------------------------- 1 | ## PDF Services 2 | 3 | Copy these files to {user}/Library/PDF Services. You may need to create the folder "PDF Services" in your user Library if it isn't there. The scripts will then be available from the PDF button of the print menu. 4 | 5 | NOTICE: As of Big Sur, Apple has increased security so that python scripts won't work as PDF Services. The scripts can be revised for use in Automator workflows. This seems to have been fixed in Monterey. 6 | 7 | 8 | ### Booklet Imposition (booklet.py) 9 | This script is set to work as a PDF Service. However, it could easily be adjusted to work as an Automator workflow. It takes the input PDF file and lays out the pages on a larger sheet, in booklet spread page order. It checks for page rotation and adjusts if necessary. There's an option to arrange for 4pp signatures (stacked sheets, not gathered). The script brings up a Save dialog. 10 | Booklet sheet size is set in the script (default is A3). Other settings and options, such as creep, can also be set. 11 | 12 | ### Save As PDF/X (Save As PDF-X.py) 13 | This replaces the PDF Service that Apple removed from MacOS , which saved the PDF after applying a filter that makes the PDF conform to PDF/X-3 spec. It will even bring up a Save file dialog. Apple's built-in PDF/X filter is a very minimal attempt at compliance with the standard, so you may want to use a better one. 14 | Some better Quartz Filters can be found here: 15 | https://github.com/benwiggy/QuartzFilters 16 | The script will look for a Filter named "Better PDF-X3.qfilter", and fall back to the System one if not found. 17 | 18 | ### Save PDF from ~Stupid~ iWork (Save_PDF_from_iWork.py) 19 | iWork apps (Numbers, Pages, KeyNote) have an annoying feature/bug that they do not remove their file extension from saved PDFs. (E.g. "MyFile.number.pdf".) Using this PDF Service will strip the iWork extension before saving the PDF. (E.g. "MyFile.pdf"). 20 | 21 | ### Watermark with PDF (watermark PDF.py) 22 | This will superimpose one PDF on top of another. Ideal for adding 'letterheads' to any printed output from any app. You will need to supply the filepath and name of the template PDF. 23 | 24 | More details about PDF Services can be found here: 25 | https://developer.apple.com/library/content/documentation/Printing/Conceptual/PDF_Workflow/pdfwf_concepts/pdfwf_concepts.html 26 | -------------------------------------------------------------------------------- /legacy (python 2)/PDF_Services/Save As PDF-X.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | # SAVE AS PDF-X: PDF Service to apply Quartz filter and move PDF to designated folder 5 | # by Ben Byram-Wigfield v1.4 6 | 7 | # $1 is filename; $3 is complete temp filepath and name. 8 | # $2 is loads of CUPS parameters. 9 | 10 | import os 11 | import sys 12 | from Quartz.CoreGraphics import PDFDocument, QuartzFilter 13 | from Foundation import NSURL 14 | from AppKit import NSSavePanel, NSApp 15 | 16 | 17 | def save_dialog(directory, filename): 18 | panel = NSSavePanel.savePanel() 19 | panel.setTitle_("Save PDF-X3 document") 20 | myUrl = NSURL.fileURLWithPath_isDirectory_(directory, True) 21 | panel.setDirectoryURL_(myUrl) 22 | panel.setNameFieldStringValue_(filename) 23 | NSApp.activateIgnoringOtherApps_(True) 24 | ret_value = panel.runModal() 25 | if ret_value: 26 | return panel.filename() 27 | else: 28 | return '' 29 | 30 | 31 | def main(argv): 32 | (title, options, pathToFile) = argv[:] 33 | 34 | # Set the default location where the PDFs will go (you'll need to make sure this exists) 35 | 36 | destination = os.path.expanduser("~/Desktop/") 37 | 38 | # Set the filepath of the filter. 39 | # Check for custom user filter; otherwise use the Not-Very-Good System filter. 40 | filterpath = os.path.expanduser("~/Library/Filters/Better PDFX-3.qfilter") 41 | if not os.path.exists(filterpath): 42 | filterpath = "/System/Library/Filters/Create Generic PDFX-3 Document.qfilter" 43 | 44 | title += ".pdf" 45 | outputfile = save_dialog(destination, title) 46 | 47 | if outputfile != "": 48 | 49 | pdfURL = NSURL.fileURLWithPath_(pathToFile) 50 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 51 | filterURL = NSURL.fileURLWithPath_(filterpath) 52 | value = QuartzFilter.quartzFilterWithURL_(filterURL) 53 | options = { 'QuartzFilter': value } 54 | pdfDoc.writeToFile_withOptions_(outputfile, options) 55 | 56 | # Delete original PDF from spool folder 57 | os.remove(pathToFile) 58 | 59 | if __name__ == "__main__": 60 | main(sys.argv[1:]) 61 | -------------------------------------------------------------------------------- /legacy (python 2)/PDF_Services/Save_PDF_from_iWork.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | # SAVE PDF FROM STUPID iWORK 5 | # PDF Service to strip iWork file extension before saving PDF to designated folder 6 | # by Ben Byram-Wigfield v1.0 7 | 8 | # Save this file in ~/Library/PDF Services. It will then be available in the 9 | # PDF button of the print menu. 10 | 11 | import os 12 | import sys 13 | import Quartz as Quartz 14 | from Foundation import NSURL 15 | from AppKit import NSSavePanel, NSApp 16 | 17 | 18 | def save_dialog(directory, filename): 19 | panel = NSSavePanel.savePanel() 20 | panel.setTitle_("Save PDF document") 21 | myUrl = NSURL.fileURLWithPath_isDirectory_(directory, True) 22 | panel.setDirectoryURL_(myUrl) 23 | panel.setNameFieldStringValue_(filename) 24 | NSApp.activateIgnoringOtherApps_(True) 25 | ret_value = panel.runModal() 26 | if ret_value: 27 | return panel.filename() 28 | else: 29 | return '' 30 | 31 | 32 | # $1 is filename; $3 is complete temp filepath and name. 33 | # $2 is loads of CUPS parameters. 34 | 35 | def main(argv): 36 | (title, options, pathToFile) = argv[:] 37 | 38 | # Set the default location where the PDFs will go (you'll need to make sure this exists) 39 | 40 | destination = os.path.expanduser("~/Desktop/") 41 | 42 | 43 | stripTitle = (os.path.splitext(title)[0]) 44 | stripTitle += ".pdf" 45 | outputfile = save_dialog(destination, stripTitle) 46 | 47 | # Copy file to selected location. 48 | if outputfile != "": 49 | 50 | pdfURL = NSURL.fileURLWithPath_(pathToFile) 51 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 52 | if pdfDoc: 53 | pdfDoc.writeToFile_(outputfile) 54 | 55 | # Delete original PDF from spool folder 56 | os.remove(pathToFile) 57 | 58 | if __name__ == "__main__": 59 | main(sys.argv[1:]) 60 | -------------------------------------------------------------------------------- /legacy (python 2)/PDF_Services/booklet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # ---------------------------------------------------------------- 5 | # PDF Booklet Imposition Script for MacOS 6 | # by Ben Byram-Wigfield v.2.1 7 | # Feel free to use, modify and pass on with acknowledgement. 8 | 9 | # 1. Set OPTIONS below for output folder, sheet size, and creep 10 | # 2. Install into ~/Library/PDF Services 11 | # 3. It will then appear as an option in the PDF button of the Print dialog. 12 | # (if it has executable flags set.) 13 | 14 | # Script can be configured for stacking 4pp signatures or gathering booklet spreads. 15 | # ---------------------------------------------------------------- 16 | 17 | # Beware of indexes starting from 0 and 1...!!! CGPDFDocument starts page count at 1. 18 | 19 | import os, sys 20 | import copy 21 | import Quartz as Quartz 22 | from Foundation import (NSURL, CFURLCreateFromFileSystemRepresentation, kCFAllocatorDefault) 23 | from AppKit import NSSavePanel, NSApp 24 | 25 | 26 | # Uncomment the sheet size you want. 27 | A3 = [[0,0], [1190.55, 841.88]] 28 | # A4 = [[0,0], [841.88, 595.28]] 29 | # USLetter = [[0,0], [792, 612]] 30 | # Tabloid = [[0,0], [1224, 792]] 31 | 32 | # OPTIONS 33 | # Change this to one of the sizes listed above, if you want. 34 | sheetSize = A3 35 | # Set the default location for saving the files. 36 | destination = os.path.expanduser("~/Desktop") 37 | # Set file suffix 38 | suffix = " booklet.pdf" 39 | # If hasSignatures, sheets will be arranged for stacking in 4pp sections. 40 | hasSignatures = False 41 | pagesPerSheet = 4 # Not sure what will happen if this is changed. 42 | creep = 0.5 # in points. NB: Eventually, the pages will collide. 43 | imposedOrder = [] 44 | 45 | # FUNCTIONS 46 | 47 | def save_dialog(directory, filename): 48 | panel = NSSavePanel.savePanel() 49 | panel.setTitle_("Save PDF booklet") 50 | myUrl = NSURL.fileURLWithPath_isDirectory_(directory, True) 51 | panel.setDirectoryURL_(myUrl) 52 | panel.setNameFieldStringValue_(filename) 53 | NSApp.activateIgnoringOtherApps_(True) 54 | ret_value = panel.runModal() 55 | if ret_value: 56 | return panel.filename() 57 | else: 58 | return '' 59 | 60 | def createPDFDocumentWithPath(path): 61 | return Quartz.CGPDFDocumentCreateWithURL(CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, path, len(path), False)) 62 | 63 | # Creates a Context for drawing 64 | def createOutputContextWithPath(path, dictarray): 65 | return Quartz.CGPDFContextCreateWithURL(Quartz.CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, path, len(path), False), None, dictarray) 66 | 67 | 68 | def imposition(pageRange): 69 | for i in range(1, (len(pageRange)/2), 2): 70 | # First we do recto 71 | imposedOrder.append(pageRange[i*-1]) 72 | imposedOrder.append(pageRange[i-1]) 73 | # And now we do verso 74 | imposedOrder.append(pageRange[i]) 75 | imposedOrder.append(pageRange[(i+1)*-1]) 76 | 77 | return imposedOrder 78 | 79 | def getRotation(pdfpage): 80 | displayAngle = 0 81 | rotValue = Quartz.CGPDFPageGetRotationAngle(pdfpage) 82 | mediaBox = Quartz.CGPDFPageGetBoxRect(pdfpage, Quartz.kCGPDFMediaBox) 83 | if not Quartz.CGRectIsEmpty(mediaBox): 84 | x = Quartz.CGRectGetWidth(mediaBox) 85 | y = Quartz.CGRectGetHeight(mediaBox) 86 | if (x > y): displayAngle = -90 87 | displayAngle -= rotValue 88 | return displayAngle 89 | 90 | # Gets DocInfo from input file to pass to output. 91 | def getDocInfo(file): 92 | file = file.decode('utf-8') 93 | pdfURL = NSURL.fileURLWithPath_(file) 94 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 95 | return pdfDoc.documentAttributes() 96 | 97 | def contextDone(context): 98 | if context: 99 | Quartz.CGPDFContextClose(context) 100 | del context 101 | 102 | # MAIN 103 | def main(argv): 104 | (title, options, pathToFile) = argv[:] 105 | shortName = os.path.splitext(title)[0] 106 | # If you want to save to a consistent location, use: 107 | # writeFilename = os.path.join(destination, shortName + suffix) 108 | writeFilename = save_dialog(destination, shortName + suffix) 109 | writeFilename = writeFilename.encode('utf-8') 110 | leftPage = copy.deepcopy(sheetSize) 111 | shift = sheetSize[1][0]/2 112 | leftPage[1][0] = shift 113 | rightPage = copy.deepcopy(leftPage) 114 | rightPage[0][0] = shift 115 | blanks = 0 116 | 117 | # Initiate new PDF, get source PDF data, number of pages. 118 | metaDict = getDocInfo(pathToFile) 119 | writeContext = createOutputContextWithPath(writeFilename, metaDict) 120 | source = createPDFDocumentWithPath(pathToFile) 121 | totalPages = Quartz.CGPDFDocumentGetNumberOfPages(source) 122 | 123 | # Add blank pages to round up to multiple of pages per sheet. 124 | UnsortedOrder = range(1, totalPages+1) 125 | if totalPages%pagesPerSheet: 126 | blanks = pagesPerSheet - (totalPages%pagesPerSheet) 127 | for i in range(blanks): 128 | UnsortedOrder.append(0) 129 | totalPages = len(UnsortedOrder) 130 | 131 | if hasSignatures: 132 | signatureSize = pagesPerSheet 133 | else: 134 | signatureSize = totalPages 135 | 136 | for something in range(0, totalPages, signatureSize): 137 | imposition(UnsortedOrder[something:(something+signatureSize)]) 138 | 139 | # For each side of the sheet, we must... 140 | # ... create a PDF page, take two source pages and place them differently, then close the page. 141 | # If the source page number is 0, then move on without drawing. 142 | Sides = totalPages/2 143 | count = 0 144 | for n in range(Sides): 145 | Quartz.CGContextBeginPage(writeContext, sheetSize) 146 | for position in [leftPage, rightPage]: 147 | if imposedOrder[count]: 148 | page = Quartz.CGPDFDocumentGetPage(source, imposedOrder[count]) 149 | Quartz.CGContextSaveGState(writeContext) 150 | # Check PDF page rotation AND mediabox orientation. 151 | angle = getRotation(page) 152 | Quartz.CGContextConcatCTM(writeContext, Quartz.CGPDFPageGetDrawingTransform(page, Quartz.kCGPDFMediaBox, position, angle, True)) 153 | # Uncomment next line to draw box round each page 154 | # Quartz.CGContextStrokeRectWithWidth(writeContext, leftPage, 2.0) 155 | Quartz.CGContextDrawPDFPage(writeContext, page) 156 | Quartz.CGContextRestoreGState(writeContext) 157 | count += 1 158 | Quartz.CGContextEndPage(writeContext) 159 | 160 | # Set creep for next sheet. 161 | if count%4 == 0: 162 | leftPage[0][0] += creep 163 | rightPage[0][0] -= creep 164 | 165 | # Do tidying up 166 | contextDone(writeContext) 167 | 168 | # Delete original PDF from spool folder 169 | os.remove(pathToFile) 170 | 171 | if __name__ == "__main__": 172 | main(sys.argv[1:]) 173 | -------------------------------------------------------------------------------- /legacy (python 2)/PDF_Services/watermark PDF.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | # 4 | # Merge v. 0.1 5 | # Merges two PDFs 6 | 7 | import sys 8 | import os 9 | import Quartz as Quartz 10 | from Foundation import NSURL, kCFAllocatorDefault 11 | from AppKit import NSSavePanel, NSApp 12 | 13 | # OPTIONS 14 | # Change this filepath to the PDF you want to use a letterhead / template: 15 | watermark = os.path.expanduser("/System/Library/Assistant/UIPlugins/FMF.siriUIBundle/Contents/Resources/person.pdf") 16 | destination = os.path.expanduser("~/Desktop") # Default destination 17 | suffix = " wm.pdf" # Use ".pdf" if no actual suffix required. 18 | 19 | # FUNCTIONS 20 | 21 | def save_dialog(directory, filename): 22 | panel = NSSavePanel.savePanel() 23 | panel.setTitle_("Save PDF booklet") 24 | myUrl = NSURL.fileURLWithPath_isDirectory_(directory, True) 25 | panel.setDirectoryURL_(myUrl) 26 | panel.setNameFieldStringValue_(filename) 27 | NSApp.activateIgnoringOtherApps_(True) 28 | ret_value = panel.runModal() 29 | if ret_value: 30 | return panel.filename() 31 | else: 32 | return '' 33 | 34 | # Loads in PDF document 35 | def createPDFDocumentWithPath(path): 36 | return Quartz.CGPDFDocumentCreateWithURL(Quartz.CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, path, len(path), False)) 37 | 38 | # Creates a Context for drawing 39 | def createOutputContextWithPath(path, dictarray): 40 | return Quartz.CGPDFContextCreateWithURL(Quartz.CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, path, len(path), False), None, dictarray) 41 | 42 | # Gets DocInfo from input file to pass to output. 43 | # PyObjC returns Keywords in an NSArray; they must be tupled. 44 | def getDocInfo(file): 45 | file = file.decode('utf-8') 46 | pdfURL = NSURL.fileURLWithPath_(file) 47 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 48 | if pdfDoc: 49 | metadata = pdfDoc.documentAttributes() 50 | if "Keywords" in metadata: 51 | keys = metadata["Keywords"] 52 | mutableMetadata = metadata.mutableCopy() 53 | mutableMetadata["Keywords"] = tuple(keys) 54 | return mutableMetadata 55 | else: 56 | return metadata 57 | 58 | def main(argv): 59 | (title, options, pathToFile) = argv[:] 60 | shortName = os.path.splitext(title)[0] 61 | # If you want to save to a consistent location, use: 62 | # writeFilename = os.path.join(destination, shortName + suffix) 63 | writeFilename = save_dialog(destination, shortName + suffix) 64 | writeFilename = writeFilename.encode('utf-8') 65 | shortName = os.path.splitext(pathToFile)[0] 66 | metaDict = getDocInfo(pathToFile) 67 | writeContext = createOutputContextWithPath(writeFilename, metaDict) 68 | readPDF = createPDFDocumentWithPath(pathToFile) 69 | mergePDF = createPDFDocumentWithPath(watermark) 70 | 71 | if writeContext != None and readPDF != None: 72 | numPages = Quartz.CGPDFDocumentGetNumberOfPages(readPDF) 73 | for pageNum in xrange(1, numPages + 1): 74 | page = Quartz.CGPDFDocumentGetPage(readPDF, pageNum) 75 | mergepage = Quartz.CGPDFDocumentGetPage(mergePDF, 1) 76 | if page: 77 | mediaBox = Quartz.CGPDFPageGetBoxRect(page, Quartz.kCGPDFMediaBox) 78 | if Quartz.CGRectIsEmpty(mediaBox): 79 | mediaBox = None 80 | Quartz.CGContextBeginPage(writeContext, mediaBox) 81 | Quartz.CGContextSetBlendMode(writeContext, Quartz.kCGBlendModeOverlay) 82 | Quartz.CGContextDrawPDFPage(writeContext, page) 83 | Quartz.CGContextDrawPDFPage(writeContext, mergepage) 84 | Quartz.CGContextEndPage(writeContext) 85 | Quartz.CGPDFContextClose(writeContext) 86 | del writeContext 87 | 88 | else: 89 | print "A valid input file and output file must be supplied." 90 | sys.exit(1) 91 | 92 | if __name__ == "__main__": 93 | main(sys.argv[1:]) 94 | -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/Add Blank Page to End.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Quick_Actions/Add Blank Page to End.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/Add Index Numbers.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Quick_Actions/Add Index Numbers.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/Add Metadata.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Quick_Actions/Add Metadata.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/Add Page Numbering.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Quick_Actions/Add Page Numbering.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/Count PDF Pages.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Quick_Actions/Count PDF Pages.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/Crop to Trim Marks.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Quick_Actions/Crop to Trim Marks.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/Encrypt PDF.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Quick_Actions/Encrypt PDF.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/Export Text.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Quick_Actions/Export Text.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/Graph Paper.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Quick_Actions/Graph Paper.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/Images to PDF.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Quick_Actions/Images to PDF.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/Join PDFs.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Quick_Actions/Join PDFs.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/Make Booklet.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Quick_Actions/Make Booklet.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/Make PDF-X.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Quick_Actions/Make PDF-X.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/PDF 2 PNG.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Quick_Actions/PDF 2 PNG.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/PDF 2 TIFF.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Quick_Actions/PDF 2 TIFF.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/Readme.md: -------------------------------------------------------------------------------- 1 | 2 | QUICK ACTIONS 3 | 4 | Unzip these workflow files and save them to the {user}/Library/Services folder. If the Services folder does not exist in the Library, then you must create it. 5 | They will then be available in the Quick Actions panel when you select a PDF. (Or an Image file, in the case of the Image to PDF workflow). 6 | -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/Rotate PDF pages.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Quick_Actions/Rotate PDF pages.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/Split PDF pages.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Quick_Actions/Split PDF pages.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/Tint PDF.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Quick_Actions/Tint PDF.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Quick_Actions/Watermark with Text.workflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benwiggy/PDFsuite/bcafc7f8b3fc287db4aa5d5df54ea55def2c4e33/legacy (python 2)/Quick_Actions/Watermark with Text.workflow.zip -------------------------------------------------------------------------------- /legacy (python 2)/Shell_Scripts/ReadMe.md: -------------------------------------------------------------------------------- 1 | ## Shell scripts 2 | ### Apply Quartz Filter (quartzfilter.py) 3 | This replaces Apple's own _quartzfilter_ command, which was removed from OS X (in Snow Leopard). (Also, there was a sample script in Xcode, which used now-deprecated APIs.) Like its predecessor, it takes three arguments: the input file, the path to the quartz filter, and an output filename. 4 | If the filter is supplied as a name only, without full filepath (e.g. "Sepia Tone.qfilter"), the script will search for installed filters of that name and get the complete filepath. 5 | It could be easily modified along the lines of the other scripts, to apply one fixed filter to all files given as arguments. _(Like the Save As PDF-X.py script in PDF Services.)_ 6 | 7 | ### Create Table of Contents (createPDFOutlines.py) 8 | This script automates the addition of entries ('outlines') in the Table of Contents of a PDF file. Currently, the filepath of the PDF (and the output) must be set in the script, along with the page numbers and names of the bookmarks. 9 | 10 | ### Get PDF Outlines (getPDFOutlines.py) 11 | This script returns the Table of Contents data from a PDF file, as a PDFmark text file. The results are the name of the entry, the page number, the type of outline, possibly with other metadata. 12 | 13 | ### Creator (creator.py) 14 | This script alters PDFs, changing the "Creator" metadata to the value supplied. Other metadata keys are supplied, allowing the script to be easily modified for other metadata values. If no output file is set, it will overwrite the input file. 15 | creator.py -c CreatorName -i inputfile [-o outputfile] 16 | 17 | ### Get Info (getInfo.py) 18 | This script outputs all the available PDF metadata for a file: Author, Creator, etc, Number of Pages, Version number, flags for encryption and security. 19 | 20 | ### Make PDF from Clipboard (getPDFclip.py) 21 | This script takes image data from the MacOS clipboard, and saves it to a named PDF file. If the file already exists, the PDF is added as a new page to the existing file. (Thus making an impromptu Scrapbook of saved image data.) 22 | 23 | ### List Quartz Filters (listFilters.py) 24 | This script returns the internal name and filepath of all Quartz Filters installed. These can be in any of the three Library/Filters folders (user, root, system), or the PDF Services folders. 25 | 26 | ### PDF Text Search (pdfsearch.py) 27 | This script provides the necessary functionality to search a PDF for a given text string. Results may depend on the way that the text has been encoded and formatted. 28 | 29 | ### Page Layout (pagelayout.py) 30 | This script provides functions to allow the drawing of rectangles, circles, lines, and text on a PDF page, with colours and transparency, using simple, one-line commands. It saves the results to file called "Test.pdf" on the user's Desktop. It is simply a "proof of concept" for the creation of graphical items using a simple text description. (PostScript it aint!) 31 | -------------------------------------------------------------------------------- /legacy (python 2)/Shell_Scripts/createPDFoutlines.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # CREATE PDF OUTLINES v.1.0 : Add a simple list of Bookmarks to a PDF. 5 | # by Ben Byram-Wigfield 6 | 7 | from Foundation import NSURL, NSString 8 | import Quartz as Quartz 9 | import sys 10 | 11 | # You will need to change these filepaths to a local test pdf and an output file. 12 | infile = "/path/to/file.pdf" 13 | outfile = '/path/to/output.pdf' 14 | 15 | def getOutline(page, label): 16 | # Create Destination 17 | myPage = myPDF.pageAtIndex_(page) 18 | pageSize = myPage.boundsForBox_(Quartz.kCGPDFMediaBox) 19 | x = 0 20 | y = Quartz.CGRectGetMaxY(pageSize) 21 | pagePoint = Quartz.CGPointMake(x,y) 22 | myDestination = Quartz.PDFDestination.alloc().initWithPage_atPoint_(myPage, pagePoint) 23 | myLabel = NSString.stringWithString_(label) 24 | myOutline = Quartz.PDFOutline.alloc().init() 25 | myOutline.setLabel_(myLabel) 26 | myOutline.setDestination_(myDestination) 27 | return myOutline 28 | 29 | pdfURL = NSURL.fileURLWithPath_(infile) 30 | myPDF = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 31 | if myPDF: 32 | # Create Outlines. Add the Page Index (from 0) and label in pairs here: 33 | myTableOfContents = [ 34 | (0, 'Page 1'), 35 | (1, 'Page 2'), 36 | (2, 'Page 3') 37 | ] 38 | allMyOutlines = [] 39 | for index, outline in myTableOfContents: 40 | allMyOutlines.append(getOutline(index, outline)) 41 | 42 | # Create a root Outline and add each outline 43 | rootOutline = Quartz.PDFOutline.alloc().init() 44 | for index, value in enumerate(allMyOutlines): 45 | rootOutline.insertChild_atIndex_(value, index) 46 | myPDF.setOutlineRoot_(rootOutline) 47 | myPDF.writeToFile_(outfile) 48 | 49 | 50 | -------------------------------------------------------------------------------- /legacy (python 2)/Shell_Scripts/creator.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # coding=utf-8 3 | 4 | # CREATOR : Add [Creator] metadata to a PDF file. 5 | # by Ben Byram-Wigfield 6 | 7 | # creator -c - i [-o ] 8 | # 9 | import sys 10 | import os 11 | import getopt 12 | import Quartz.CoreGraphics as Quartz 13 | 14 | from CoreFoundation import NSURL 15 | 16 | def main(argv): 17 | inputfile = "" 18 | outputfile = "" 19 | value="" 20 | try: 21 | opts, args = getopt.getopt(argv,"hc:i:o:",["creator=", "input=", "output="]) 22 | except getopt.GetoptError: 23 | print 'creator.py -c -i -o ' 24 | sys.exit(2) 25 | for opt, arg in opts: 26 | if opt == '-h': 27 | print 'creator.py -c -i -o ' 28 | print 'longnames are: --creator, --input, --output' 29 | print "If no output is specified, the input will be over-written." 30 | sys.exit() 31 | elif opt in ("-c", "--creator"): 32 | value = arg.decode('utf-8') 33 | elif opt in ("-i", "--input"): 34 | inputfile = arg.decode('utf-8') 35 | elif opt in ("-o", "--output"): 36 | outputfile = arg.decode('utf-8') 37 | 38 | if outputfile == "": outputfile = inputfile 39 | pdfURL = NSURL.fileURLWithPath_(inputfile) 40 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 41 | 42 | # Default value option: 43 | # if value == "": value = "Uncle Bob Silly" 44 | options = { Quartz.kCGPDFContextCreator: value } 45 | pdfDoc.writeToFile_withOptions_(outputfile, options) 46 | 47 | if __name__ == "__main__": 48 | main(sys.argv[1:]) 49 | 50 | """ 51 | Other Dict keys include: 52 | 53 | kCGPDFContextAuthor (string) 54 | kCGPDFContextTitle 55 | kCGPDFContextOwnerPassword 56 | kCGPDFContextUserPassword 57 | kCGPDFContextAllowsPrinting (boolean) 58 | kCGPDFContextAllowsCopying (boolean) 59 | 60 | kCGPDFContextMediaBox (CGRect) 61 | kCGPDFContextCropBox (CGRect) 62 | kCGPDFContextBleedBox (CGRect) 63 | kCGPDFContextTrimBox (CGRect) 64 | kCGPDFContextArtBox (CGRect) 65 | 66 | kCGPDFContextOutputIntent 67 | kCGPDFContextOutputIntents 68 | kCGPDFContextSubject 69 | kCGPDFContextKeywords 70 | kCGPDFContextEncryptionKeyLength 71 | 72 | kCGPDFXOutputIntentSubtype 73 | kCGPDFXOutputConditionIdentifier 74 | kCGPDFXOutputCondition 75 | kCGPDFXRegistryName 76 | kCGPDFXInfo 77 | kCGPDFXDestinationOutputProfile 78 | 79 | See the Apple Documentation page on Auxiliary Dictionary Keys for PDF Context for more. 80 | 81 | """ 82 | -------------------------------------------------------------------------------- /legacy (python 2)/Shell_Scripts/getInfo.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Gets PDF metadata for any PDF file provided as an argument 5 | # by Ben Byram-Wigfield v1.2 6 | # 7 | 8 | import sys 9 | from Quartz import PDFDocument 10 | from Foundation import NSURL 11 | 12 | if __name__ == '__main__': 13 | 14 | for filename in sys.argv[1:]: 15 | filename = filename.decode('utf-8') 16 | pdfURL = NSURL.fileURLWithPath_(filename) 17 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 18 | if pdfDoc: 19 | print "URL:", pdfDoc.documentURL() # Might be nice to Unicode this. 20 | metadata = pdfDoc.documentAttributes() 21 | for key in metadata: 22 | print "{}: {}".format(key, metadata[key]) 23 | print "Number of Pages:", pdfDoc.pageCount() 24 | print "Is Encrypted:", pdfDoc.isEncrypted() 25 | print "Is Locked:", pdfDoc.isLocked() 26 | print "Allows Copying:", pdfDoc.allowsCopying() 27 | print "Allows Printing:", pdfDoc.allowsPrinting() 28 | print "Version: {}.{}".format(pdfDoc.majorVersion(),pdfDoc.minorVersion()) 29 | else: print "Cannot get this file. (Not a PDF? / Bad filename?)" 30 | -------------------------------------------------------------------------------- /legacy (python 2)/Shell_Scripts/getPDFOutlines.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | # GET PDF OUTLINES v. 1.4 5 | # by Ben Byram-Wigfield 6 | # This script will produce rudimentary PDFmark data from any PDF file(s) given as arguments, 7 | # for PDF Outlines (bookmarks, ToCs) with page destinations. (Not Actions.) 8 | 9 | from Foundation import NSURL 10 | import Quartz as Quartz 11 | import sys 12 | 13 | 14 | def getDestination(thisOutline): 15 | thisDestination=thisOutline.destination() 16 | if thisDestination: 17 | stringDestination= str(thisDestination).split(",") 18 | OutlineType = stringDestination[0] 19 | pageNum = stringDestination[1].split("= ")[1] 20 | pageNum = str(int(pageNum)+1) 21 | return OutlineType, pageNum 22 | 23 | 24 | def recurseOutlines(thisOutline): 25 | # print (thisOutline.index()) 26 | print ("[ /Title (" + thisOutline.label() +")") 27 | Otype, pageNum = getDestination(thisOutline) 28 | print (" /Page " + pageNum) 29 | 30 | # View type is unnecessary when using output to create new bookmarks. 31 | Otype = "qwe" # Duff value. 32 | if Otype == "XYZ": 33 | print(" /View [/" + Otype + " 0 0 0]") 34 | if Otype == "Fit": 35 | print(" /View [/" + Otype + "]") 36 | 37 | if thisOutline.numberOfChildren() != 0: 38 | print (" /Count " + str(thisOutline.numberOfChildren())) 39 | print(" /OUT pdfmark\n") 40 | for n in range(thisOutline.numberOfChildren()): 41 | recurseOutlines(thisOutline.childAtIndex_(n)) 42 | else: 43 | print(" /OUT pdfmark\n") 44 | 45 | def getOutlines(infile): 46 | pdfURL = NSURL.fileURLWithPath_(infile) 47 | myPDF = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 48 | if myPDF: 49 | outline = Quartz.PDFOutline.alloc().init() 50 | outline = myPDF.outlineRoot() 51 | if outline: 52 | if outline.numberOfChildren() != 0: 53 | for n in range(outline.numberOfChildren()): 54 | recurseOutlines(outline.childAtIndex_(n)) 55 | else: 56 | print("No Outlines in this PDF.") 57 | 58 | if __name__ == '__main__': 59 | for filename in sys.argv[1:]: 60 | getOutlines(filename) 61 | -------------------------------------------------------------------------------- /legacy (python 2)/Shell_Scripts/getPDFclip.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # getPDFclip v.1.2 : Get PDF from Clipboard image data. 4 | # by Ben Byram-Wigfield. 5 | # This script saves a PDF with a copy of any image data found on the Mac Clipboard. 6 | 7 | # If Clipboard.pdf exists, the image is added as an extra page. 8 | 9 | from AppKit import NSPasteboard, NSPasteboardTypePDF, NSTIFFPboardType, NSPICTPboardType, NSImage 10 | from Foundation import NSURL 11 | import Quartz as Quartz 12 | import os, syslog 13 | 14 | # Change this to whatever filepath you want: 15 | outfile=os.path.expanduser("~/Desktop/Clipboard.pdf") 16 | 17 | 18 | myFavoriteTypes = [NSPasteboardTypePDF, NSTIFFPboardType, NSPICTPboardType, 'com.adobe.encapsulated-postscript'] 19 | pb = NSPasteboard.generalPasteboard() 20 | best_type = pb.availableTypeFromArray_(myFavoriteTypes) 21 | if best_type: 22 | clipData = pb.dataForType_(best_type) 23 | if clipData: 24 | image = NSImage.alloc().initWithPasteboard_(pb) 25 | if image: 26 | page = Quartz.PDFPage.alloc().initWithImage_(image) 27 | if os.path.exists(outfile): 28 | pdfURL = NSURL.fileURLWithPath_(outfile) 29 | myFile = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 30 | if myFile: 31 | pagenum = myFile.pageCount() 32 | myFile.insertPage_atIndex_(page, pagenum) 33 | print "Image added to Clipboard file." 34 | 35 | else: 36 | pageData = page.dataRepresentation() 37 | myFile = Quartz.PDFDocument.alloc().initWithData_(pageData) 38 | myFile.writeToFile_(outfile) 39 | print "Clipboard file created." 40 | 41 | else: 42 | print ("No clipboard image data was retrieved.") 43 | # print ("These types were available:") 44 | # print (pb.types()) 45 | -------------------------------------------------------------------------------- /legacy (python 2)/Shell_Scripts/listFilters.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | # LIST FILTERS v.1.0 5 | # by Ben Byram-Wigfield 6 | # This will list all Quartz Filters known to the OS. 7 | # Note that the filter name is an internal value and not necessarily the filename. 8 | 9 | 10 | import Quartz as Quartz 11 | from Foundation import NSURL, NSString 12 | 13 | def listFilters(): 14 | Filters = (Quartz.QuartzFilterManager.filtersInDomains_(None)) 15 | for eachFilter in Filters: 16 | print eachFilter.localizedName(), ':', eachFilter.url().fileSystemRepresentation() 17 | return 18 | 19 | 20 | listFilters() 21 | 22 | -------------------------------------------------------------------------------- /legacy (python 2)/Shell_Scripts/pagelayout.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | # by Ben Byram-Wigfield v. 0.5 5 | # TO DO: 6 | # Add bitmap image or PDF file. 7 | # Include Rounded Rectangles and other BezierPaths. 8 | 9 | import os, sys 10 | import Quartz as Quartz 11 | from CoreText import (kCTFontAttributeName, CTFontCreateWithName, CTLineDraw, CTLineCreateWithAttributedString, kCTFontAttributeName, CTLineGetImageBounds) 12 | from CoreFoundation import (CFAttributedStringCreate, CFURLCreateFromFileSystemRepresentation, kCFAllocatorDefault) 13 | from math import pi as PI 14 | 15 | pageSize = [[0.,0.], [595.28, 841.88]] # A4 16 | whiteSwatch = [1.,1.,1.] 17 | redSwatch = [1.,0.,0.] 18 | blueSwatch = [0.,0.,1.] 19 | greenSwatch = [0.,1.,0.] 20 | blackSwatch = [0.,0.,0.] 21 | 22 | # Use inches instead of points e.g. "inch(1.5)" 23 | def inch(x): 24 | return 72.0*x 25 | 26 | # Use centimetres instead of points e.g. "cm(2.5)" 27 | def cm(x): 28 | return 28.25*x 29 | 30 | 31 | def makeRectangle(x, y, xSize, ySize, color, alpha): 32 | red, green, blue = color[:] 33 | Quartz.CGContextSetRGBFillColor (writeContext, red, green, blue, alpha) 34 | Quartz.CGContextFillRect (writeContext, Quartz.CGRectMake(x, y, xSize, ySize)) 35 | return 36 | 37 | 38 | def centerText(y, text, font, pointSize): 39 | typeStyle = CTFontCreateWithName(font, pointSize, None) 40 | astr = CFAttributedStringCreate(kCFAllocatorDefault, text, { kCTFontAttributeName : typeStyle }) 41 | line = CTLineCreateWithAttributedString(astr) 42 | textWidth = astr.size().width 43 | 44 | if line: 45 | x = (pageSize[1][0]-textWidth)/2 46 | # Quartz.CGContextSetAlpha(writeContext, opacity) 47 | Quartz.CGContextSetTextPosition(writeContext, x, y) 48 | CTLineDraw(line, writeContext) 49 | 50 | return 51 | 52 | def line(x, y, xSize, ySize, stroke, color, alpha): 53 | red, green, blue = color[:] 54 | Quartz.CGContextSetLineWidth(writeContext, stroke) 55 | Quartz.CGContextSetRGBStrokeColor(writeContext, red, green, blue, alpha) 56 | Quartz.CGContextMoveToPoint(writeContext, x, y) 57 | Quartz.CGContextAddLineToPoint(writeContext, x+xSize, y+ySize) 58 | Quartz.CGContextStrokePath(writeContext) 59 | return 60 | 61 | def circle(x, y, radius, color, alpha): 62 | red, green, blue = color[:] 63 | Quartz.CGContextSetRGBStrokeColor(writeContext, red, green, blue, alpha) 64 | Quartz.CGContextSetRGBFillColor(writeContext, red, green, blue, alpha) 65 | Quartz.CGContextAddArc(writeContext, x, y, radius, 0, 2*PI, 1) 66 | Quartz.CGContextClosePath(writeContext) 67 | Quartz.CGContextFillPath(writeContext) 68 | Quartz.CGContextSetLineWidth(writeContext, 2) 69 | Quartz.CGContextStrokePath(writeContext) 70 | return 71 | 72 | def addImage(x, y, path): 73 | # CGContextDrawImage(writeContext, rect, CGImageRef image) 74 | return 75 | 76 | def contextDone(context): 77 | if context: 78 | Quartz.CGPDFContextClose(context) 79 | del context 80 | 81 | def main(argv): 82 | global writeContext 83 | writeFilename = os.path.expanduser("~/Desktop/Test.pdf") 84 | writeContext = Quartz.CGPDFContextCreateWithURL(CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, writeFilename, len(writeFilename), False), pageSize, None) 85 | Quartz.CGContextBeginPage(writeContext, pageSize) 86 | 87 | 88 | # HERE IS WHERE YOU WRITE YOUR PAGE! 89 | # ------------------------------------------------------------------ 90 | 91 | makeRectangle(100., 100., 400., 50., redSwatch, 0.75) 92 | makeRectangle(100., 700., 400., 50., greenSwatch, 0.75) 93 | line(100, 300, 400, 200, 12, blueSwatch, 1) 94 | circle(300.,400., 150., blueSwatch, 0.5) 95 | centerText(600, "Sample Text", "Helvetica-Bold", 12.0) 96 | 97 | # ------------------------------------------------------------------ 98 | 99 | Quartz.CGContextEndPage(writeContext) 100 | # Do tidying up 101 | contextDone(writeContext) 102 | 103 | if __name__ == "__main__": 104 | main(sys.argv[1:]) 105 | -------------------------------------------------------------------------------- /legacy (python 2)/Shell_Scripts/password.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # PASSWORD v1.0: Concept script to unlock encrypted PDFs, (if you know the password) 4 | # by Ben Byram-Wigfield 5 | # Currently, the script doesn't do anything with the unlocked data. But the code could be 6 | # added to other PDFSuite scripts in order to process locked PDFs. 7 | 8 | from Foundation import NSAppleScript, NSURL 9 | import Quartz as Quartz 10 | import sys 11 | 12 | # Sneaky call to AppleScript to get input from a dialog. 13 | def getTextFromDialog(): 14 | textOfMyScript = """ 15 | tell application "System Events" 16 | set myWords to "This PDF is protected" & return & "Please enter the password:" 17 | set theResponse to (display dialog myWords with title "Encrypted PDF" default answer "" buttons {"Cancel", "Continue"} default button 2 with icon 0 with hidden answer) 18 | end tell 19 | """ 20 | myScript = NSAppleScript.initWithSource_(NSAppleScript.alloc(), textOfMyScript) 21 | results, err = myScript.executeAndReturnError_(None) 22 | # results is an NSAppleEventDescriptor, which describes two things: 23 | # The button pressed (1), and the text returned (2). 24 | 25 | if not err: 26 | try: 27 | returnedInput = results.descriptorAtIndex_(2).stringValue() 28 | except: 29 | return None 30 | else: 31 | if returnedInput: 32 | return returnedInput 33 | else: 34 | return None 35 | else: 36 | print err 37 | return None 38 | 39 | 40 | def checkLock(infile): 41 | pdfURL = NSURL.fileURLWithPath_(infile) 42 | myPDF = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 43 | if myPDF: 44 | if myPDF.isLocked: 45 | print "Locked" 46 | password = getTextFromDialog() 47 | if myPDF.unlockWithPassword_(password): 48 | print infile, "Unlocked!" 49 | else: 50 | print "Unable to unlock", infile 51 | else: 52 | print "No PDF data retrieved from", infile 53 | 54 | if __name__ == '__main__': 55 | 56 | for filename in sys.argv[1:]: 57 | checkLock(filename) 58 | -------------------------------------------------------------------------------- /legacy (python 2)/Shell_Scripts/pdfsearch.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | # PDF TEXT SEARCH v1.0 4 | # by Ben Byram-Wigfield 5 | 6 | # Minimal function for searching text in a PDF for a string. 7 | # Useful safety tip: PDFKit's page index starts at zero 8 | 9 | import sys 10 | from Quartz import PDFDocument 11 | from Foundation import NSURL 12 | 13 | def pdfSearch(filepath, searchString): 14 | pdfURL = NSURL.fileURLWithPath_(filepath) 15 | pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL) 16 | if pdfDoc: 17 | searchResults = (pdfDoc.findString_withOptions_(searchString, 0)) 18 | if searchResults: 19 | for result in searchResults: 20 | eachPage = result.pages() 21 | print ("\'"+ searchString+"\' was found on page: "+str(pdfDoc.indexForPage_(eachPage[0])+1)) 22 | else: 23 | print("Nothing found.") 24 | else: 25 | print("Not a valid PDF.") 26 | return 27 | 28 | if __name__ == "__main__": 29 | # Set the filepath and searchString to your desired values 30 | filepath = '/Users/ben/Desktop/Untitled.pdf' 31 | searchString = 'office' 32 | pdfSearch(filepath, searchString) -------------------------------------------------------------------------------- /legacy (python 2)/Shell_Scripts/quartzfilter.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # coding: utf-8 3 | 4 | # QUARTZFILTER v.1.4: Script to apply a MacOS Quartz Filter to a PDF file. 5 | # by Ben Byram-Wigfield 6 | # 7 | # quartzfilter.py 8 | # 9 | # The script will accept the bare name of a filter (with .qfilter) if file path not given. 10 | # E.g. quartzfilter.py /path/to/myPDF.pdf 'Sepia Tone.qfilter' /path/to/output.pdf 11 | 12 | import os, getopt, sys 13 | import Quartz as Quartz 14 | from Foundation import NSURL 15 | 16 | def checkFilter(name): 17 | if not os.path.split(name)[0]: 18 | Filters = (Quartz.QuartzFilterManager.filtersInDomains_(None)) 19 | found = False 20 | for eachFilter in Filters: 21 | filterPath = eachFilter.url().fileSystemRepresentation() 22 | if name == os.path.split(filterPath)[1]: 23 | found = True 24 | if found: 25 | return name 26 | else: 27 | if os.path.exists(name): 28 | return name 29 | 30 | def main(argv): 31 | inputfile = "" 32 | outputfile = "" 33 | filter = "" 34 | 35 | try: 36 | opts, args = getopt.getopt(sys.argv[1:], "ifo", ["input", "filter", "output"]) 37 | except getopt.GetoptError as err: 38 | print(err) 39 | usage() 40 | sys.exit(2) 41 | 42 | if len(args) != 3: 43 | print("Not enough arguments") 44 | sys.exit(2) 45 | 46 | inputfile =args[0].decode('utf-8') 47 | if not inputfile: 48 | print ('Unable to open input file') 49 | sys.exit(2) 50 | 51 | filter = args[1].decode('utf-8') 52 | filter = checkFilter(filter) 53 | if not filter: 54 | print ('Unable to find Quartz Filter') 55 | sys.exit(2) 56 | 57 | outputfile = args[2].decode('utf-8') 58 | if not outputfile: 59 | print ('No valid output file specified') 60 | sys.exit(2) 61 | # You could just take the inputfile as the outputfile if not explicitly given. 62 | # outputfile = inputfile 63 | 64 | pdfURL = NSURL.fileURLWithPath_(inputfile) 65 | pdfDoc = Quartz.PDFDocument.alloc().initWithURL_(pdfURL) 66 | filterURL = NSURL.fileURLWithPath_(filter) 67 | value = Quartz.QuartzFilter.quartzFilterWithURL_(filterURL) 68 | dict = { 'QuartzFilter': value } 69 | pdfDoc.writeToFile_withOptions_(outputfile, dict) 70 | 71 | if __name__ == "__main__": 72 | main(sys.argv[1:]) 73 | --------------------------------------------------------------------------------