Sunday, November 27, 2022

#1 2022-09-05 09:47:57 am

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 1511

Crop a PDF Document

The script included below crops all pages of a PDF document and creates a new PDF with the cropped pages. To test this script, open it to a script editor and run.

The user is prompted to enter a crop rectangle, which is in points and consists of an x coordinate, y coordinate, width, and height. Just as a point of information, there are 72 points in an inch and 2.83 points in a millimeter.

It should be noted that this script does not crop a PDF page in the sense that a bitmap image can be cropped. It instead sets the PDF's crop box, which generally defines the area of the PDF that will be displayed and printed by a PDF viewer application. This is what happens when a PDF is cropped by the Preview application.

Applescript:

-- revised 2022.11.19

use framework "Foundation"
use framework "Quartz"
use scripting additions

on main()
   set sourceFile to POSIX path of (choose file with prompt "Choose a PDF file to crop" of type {"pdf"})
   set sourceFile to current application's |NSURL|'s fileURLWithPath:sourceFile
   set theDocument to (current application's PDFDocument's alloc()'s initWithURL:sourceFile)
   
   set {x, y, w, h} to getBounds(sourceFile, theDocument)
   set cropRectangle to text returned of (display dialog "Enter the crop rectangle consisting of the x coordinate, y coordinate, width, and height." default answer (x as text) & " " & y & " " & w & " " & h with title "PDF Crop" buttons {"Cancel", "Crop"} cancel button 1 default button 2)
   set cropRectangle to current application's NSString's stringWithString:cropRectangle
   set cropRectangle to ((cropRectangle's componentsSeparatedByString:space)'s valueForKey:"integerValue") as list
   
   try
       cropPDF(sourceFile, theDocument, cropRectangle)
   on error
       display alert "The PDF could not be cropped" message "Normally this occurs when the PDF cannot be read or when the crop rectangle lacks necessary data" as critical
       error number -128
   end try
end main

on getBounds(sourceFile, theDocument)
   set aPage to (theDocument's pageAtIndex:0)
   set {{x, y}, {w, h}} to (aPage's boundsForBox:(current application's kPDFDisplayBoxMediaBox))
   return {x as integer, y as integer, w as integer, h as integer}
end getBounds

on cropPDF(sourceFile, theDocument, cropRectangle)
   set {a2, b2, c2, d2} to cropRectangle
   set sourceFolder to sourceFile's URLByDeletingLastPathComponent
   set sourceFileName to (sourceFile's URLByDeletingPathExtension())'s lastPathComponent()
   set targetFileName to sourceFileName's stringByAppendingString:" (cropped).pdf"
   set targetFile to sourceFolder's URLByAppendingPathComponent:targetFileName
   set sourcePageCount to theDocument's pageCount()
   
   repeat with i from 0 to (sourcePageCount - 1)
       set aPage to (theDocument's pageAtIndex:(i))
       set {{a1, b1}, {c1, d1}} to (aPage's boundsForBox:(current application's kPDFDisplayBoxMediaBox))
       set pageSize to {{a2, (d1 - b2 - d2)}, {c2, d2}}
       (aPage's setBounds:pageSize forBox:(current application's kPDFDisplayBoxCropBox))
       (theDocument's removePageAtIndex:i)
       (theDocument's insertPage:aPage atIndex:i)
   end repeat
   
   theDocument's writeToURL:targetFile
end cropPDF

main()

Last edited by peavine (2022-11-19 07:07:24 pm)


2018 Mac mini - macOS Monterey - Script Debugger 8

Offline

 

#2 2022-09-07 06:49:47 pm

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 1511

Re: Crop a PDF Document

This script differs from the above in that it works on a PDF selected in a Finder window, allows the user to specify the pages to be cropped, and uses Shane's Dialog Toolkit Plus script library, which can be downloaded from:

https://latenightsw.com/freeware/

This script was tested on Monterey only and will not work with every PDF. The dialog displayed by the script shows the bounds of the first page of the existing PDF, which can be helpful in setting the crop rectangle.

Applescript:

-- revised 2022.11.19

use framework "Foundation"
use framework "Quartz"
use script "Dialog Toolkit Plus" version "1.1.0"
use scripting additions

on main()
   tell application "Finder" to set sourceFiles to selection as alias list
   if (count sourceFiles) ≠ 1 then errorAlert("Multiple or no files selected")
   set sourceFile to POSIX path of item 1 of sourceFiles
   if sourceFile does not end with ".pdf" then errorAlert("The selected file does not have a PDF extension")
   set sourceFile to current application's |NSURL|'s fileURLWithPath:sourceFile
   set sourceDoc to (current application's PDFDocument's alloc()'s initWithURL:sourceFile)
   
   try
       set pageOneBounds to (sourceDoc's pageAtIndex:(0))'s boundsForBox:(current application's kPDFDisplayBoxMediaBox)
   on error
       errorAlert("The selected PDF could not be read")
   end try
   
   set {pageNumbers, cropRectangle} to displayDialog(pageOneBounds)
   
   set pageNumbers to current application's NSString's stringWithString:pageNumbers
   set pageNumbers to (pageNumbers's componentsSeparatedByString:space)
   checkNumbers(pageNumbers)
   set pageNumbers to pageNumbers's valueForKey:"integerValue"
   
   set cropRectangle to current application's NSString's stringWithString:cropRectangle
   set cropRectangle to (cropRectangle's componentsSeparatedByString:space)
   checkNumbers(cropRectangle)
   set cropRectangle to cropRectangle's valueForKey:"integerValue"
   
   try
       cropPDF(sourceFile, sourceDoc, pageNumbers, cropRectangle)
   on error
       errorAlert("The PDF could not be cropped. This normally results from invalid page numbers or crop rectangle.")
   end try
end main

on displayDialog(theBounds)
   set dialogWidth to 310
   set verticalSpace to 12
   set theBounds to "0 0 " & ((item 1 of item 2 of theBounds) as integer) & space & ((item 2 of item 2 of theBounds) as integer)
   set {theButtons, minWidth} to create buttons {"Cancel", "Crop"} cancel button 1 default button 2
   set {cropField, theTop} to create field theBounds placeholder text theBounds bottom 0 field width dialogWidth
   set {cropLabel, theTop} to create label "Enter the crop rectangle, consisting of its x and y point of origin and its width and height:" bottom theTop + verticalSpace max width dialogWidth control size regular size
   set {pagesField, theTop} to create field "0" placeholder text "" bottom (theTop + verticalSpace) field width dialogWidth
   set {pagesLabel, theTop} to create label "Enter space-separated page numbers or 0 to crop all pages:" bottom theTop + verticalSpace max width dialogWidth control size regular size
   set allControls to {cropField, cropLabel, pagesField, pagesLabel}
   set {buttonName, controlsResults} to display enhanced window "PDF Crop" buttons theButtons acc view width dialogWidth acc view height theTop acc view controls allControls initial position {} without align cancel button
   return {item 3, item 1} of controlsResults
end displayDialog

on checkNumbers(theArray) -- from Nigel
   set thePredicate to current application's NSPredicate's predicateWithFormat:"self MATCHES '[0-9]++'"
   set theResult to ((theArray's filteredArrayUsingPredicate:thePredicate)'s isEqualToArray:(theArray))
   if theResult as boolean is false then errorAlert("The page numbers or crop rectangle contained no or nonnumerical characters")
end checkNumbers

on cropPDF(sourceFile, sourceDoc, pageNumbers, cropRectangle)
   set {aC, bC, cC, dC} to cropRectangle
   
   set sourceFolder to sourceFile's URLByDeletingLastPathComponent
   set sourceFileName to (sourceFile's URLByDeletingPathExtension())'s lastPathComponent()
   set targetFileName to sourceFileName's stringByAppendingString:" (cropped).pdf"
   set targetFile to sourceFolder's URLByAppendingPathComponent:targetFileName
   set targetDoc to current application's PDFDocument's new()
   
   set sourcePageCount to sourceDoc's pageCount()
   set targetPageCount to 0
   
   if pageNumbers's isEqualToArray:{0} then
       set pageNumbers to current application's NSMutableArray's new()
       repeat with i from 1 to sourcePageCount
           (pageNumbers's addObject:i)
       end repeat
   end if
   
   repeat with i in pageNumbers
       set aPage to (sourceDoc's pageAtIndex:((i as integer) - 1))
       set {{aE, bE}, {cE, dE}} to (aPage's boundsForBox:(current application's kPDFDisplayBoxMediaBox))
       set pageSize to {{aC, (dE - bC - dC)}, {cC, dC}}
       (aPage's setBounds:pageSize forBox:(current application's kPDFDisplayBoxCropBox))
       (targetDoc's insertPage:aPage atIndex:targetPageCount)
       set targetPageCount to targetPageCount + 1
   end repeat
   
   targetDoc's writeToURL:targetFile
end cropPDF

on errorAlert(dialogMessage)
   display alert "An error has occurred" message dialogMessage as critical
   error number -128
end errorAlert

main()

Last edited by peavine (2022-11-19 03:31:02 pm)


2018 Mac mini - macOS Monterey - Script Debugger 8

Offline

 

#3 2022-11-20 08:04:40 am

peavine
Member
From:: Prescott, Arizona
Registered: 2018-09-04
Posts: 1511

Re: Crop a PDF Document

I mentioned in post 1 that the above scripts do not actually crop images in a PDF and instead define the area of the PDF that will be displayed and printed by a PDF viewer application. With recent versions of macOS, the sips utility can be used for this purpose, and the image in the PDF (if there is one) is in fact cropped. However, sips only works on the first page of a PDF, so this approach is of limited utility.

I ran some tests and in all instances the crop width and height were 500 x 500.

PDF made from PNG image with ASObjC
original 448 KB 1920 x 2074 lossless
cropped 55 KB 500 x 500 lossy

PDF made from JPEG image with Finder
original 2.6 MB 4032 x 2268 lossy
cropped 46 KB 500 x 500 lossy

PDF made with Epson scanner in color document mode
original 295 KB 2550 x 3300 lossy
cropped 53 KB 500 x 500 lossy

A page of Shane's ASObjC book (text only)
original 66 KB (contains no images) MediaBox [0 0 612 792]
cropped 52 KB 500 x 500 lossy MediaBox [0 0 240 240]

The script is:

Applescript:

set inPDF to POSIX path of (choose file of type {"pdf"})
set outPDF to POSIX path of (path to desktop) & "out.pdf"
do shell script "sips --cropOffset 1 1 --cropToHeightWidth 500 500 -o " & quoted form of outPDF & " " & quoted form of inPDF

Last edited by peavine (2022-11-21 07:06:57 am)


2018 Mac mini - macOS Monterey - Script Debugger 8

Offline

 

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)