Combine open documents in Preview app, into a single PDF

I’ve been trying to see how I can do this with a Service in Preview, but I’m afraid it’s way beyond my skill level.

I’d like to be able to combine all open windows in Preview app, into a single PDF. These will contain jpegs, text, PDFs and Word doc files which have been opened in Preview.

Can anyone give me some pointers?

The Preview.app has pure scriptability, so you should use GUI scripting.

  1. Loop thru each window of Preview.app. Get document property of each window.
  2. For each document use GUI scripting and Save as PDF… menu item of menu File. Save each new PDF to temporary items of user domain.
  3. Then use following script, written by Takaaki Naganoya. Since you do not want to select PDF files, but you can indicate their exact list in temporary items of user domain, then you do not need much from this script. Therefore, edit it as you need:

-- Combining PDFs in Chronological Order
-- Created 2018-05-26 by Takaaki Naganoya
-- 2018 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "Quartz"

property PDFDocument : a reference to current application's PDFDocument
property NSString : a reference to current application's NSString
property NSUUID : a reference to current application's NSUUID
property |NSURL| : a reference to current application's |NSURL|
property NSArray : a reference to current application's NSArray
property NSPredicate : a reference to current application's NSPredicate
property NSFileManager : a reference to current application's NSFileManager
property NSURLPathKey : a reference to current application's NSURLPathKey
property NSMutableArray : a reference to current application's NSMutableArray
property NSSortDescriptor : a reference to current application's NSSortDescriptor
property NSURLIsPackageKey : a reference to current application's NSURLIsPackageKey
property NSURLIsDirectoryKey : a reference to current application's NSURLIsDirectoryKey
property NSURLTypeIdentifierKey : a reference to current application's NSURLTypeIdentifierKey
property NSURLContentModificationDateKey : a reference to current application's NSURLContentModificationDateKey
property NSDirectoryEnumerationSkipsHiddenFiles : a reference to current application's NSDirectoryEnumerationSkipsHiddenFiles

set inFiles to (choose file of type {"pdf"} with prompt "Choose your PDF files:" with multiple selections allowed)
if inFiles = {} then return

-- Extract PDF only from the specified file alias list
set filRes1 to filterAliasListByUTI(inFiles, "com.adobe.pdf") of me

-- Find the parent folder in one of the selected files and create the path to the target fileу
set outPathTarg to POSIX path of (first item of filRes1)
set pathString to NSString's stringWithString:outPathTarg
set newPath to (pathString's stringByDeletingLastPathComponent()) as string
set destPosixPath to newPath & "/" & ((NSUUID's UUID()'s UUIDString()) as string) & ".pdf"

combinePDFsAndSaveIt(filRes1, destPosixPath) of me

on combinePDFsAndSaveIt(inFiles, destPosixPath)
	set inFilesSorted to my filesInListSortFromOldToNew(inFiles)
	
	-- Get URL of first PDF
	set inNSURL to |NSURL|'s fileURLWithPath:(POSIX path of item 1 of inFilesSorted)
	set theDoc to PDFDocument's alloc()'s initWithURL:inNSURL
	
	-- Repeat loop for the rest of the PDF
	set oldDocCount to theDoc's pageCount()
	set inFilesSorted to rest of inFilesSorted
	repeat with aFile in inFilesSorted
		set inNSURL to (|NSURL|'s fileURLWithPath:(POSIX path of aFile))
		set newDoc to (PDFDocument's alloc()'s initWithURL:inNSURL)
		set newDocCount to newDoc's pageCount()
		repeat with i from 1 to newDocCount
			set thePDFPage to (newDoc's pageAtIndex:(i - 1)) -- zero-based indexes
			(theDoc's insertPage:thePDFPage atIndex:oldDocCount)
			set oldDocCount to oldDocCount + 1
		end repeat
	end repeat
	
	set outNSURL to |NSURL|'s fileURLWithPath:destPosixPath
	(theDoc's writeToURL:outNSURL)
end combinePDFsAndSaveIt

on filesInListSortFromOldToNew(aliasList)
	set keysToRequest to {NSURLPathKey, NSURLIsPackageKey, NSURLIsDirectoryKey, NSURLContentModificationDateKey}
	
	set valuesNSArray to NSMutableArray's array()
	repeat with i in aliasList
		set oneNSURL to (|NSURL|'s fileURLWithPath:(POSIX path of i))
		(valuesNSArray's addObject:(oneNSURL's resourceValuesForKeys:keysToRequest |error|:(missing value)))
	end repeat
	
	set theNSPredicate to NSPredicate's predicateWithFormat_("%K == NO OR %K == YES", NSURLIsDirectoryKey, NSURLIsPackageKey)
	set valuesNSArray to valuesNSArray's filteredArrayUsingPredicate:theNSPredicate
	
	set theDescriptor to NSSortDescriptor's sortDescriptorWithKey:(NSURLContentModificationDateKey) ascending:true
	set theSortedNSArray to valuesNSArray's sortedArrayUsingDescriptors:{theDescriptor}
	
	-- Extract only Posix paths and convert to AppleScript list
	return (theSortedNSArray's valueForKey:(NSURLPathKey)) as list
end filesInListSortFromOldToNew

-- Return from the list of file aliases a list of POSIX paths that match the specified UTI
on filterAliasListByUTI(aList, targUTI)
	set newList to {}
	repeat with i in aList
		set j to POSIX path of i
		set tmpUTI to my retUTIfromPath(j)
		set utiRes to my filterUTIList({tmpUTI}, targUTI)
		if utiRes ≠ {} then set the end of newList to j
	end repeat
	return newList
end filterAliasListByUTI

-- Find a UTI file with the specified POSIX path
on retUTIfromPath(aPOSIXPath)
	set aURL to |NSURL|'s fileURLWithPath:aPOSIXPath
	set {theResult, theValue} to aURL's getResourceValue:(reference) forKey:NSURLTypeIdentifierKey |error|:(missing value)
	if theResult = true then
		return theValue as string
	else
		return theResult
	end if
end retUTIfromPath

-- Determine if the UTI list includes the specified UTI
on filterUTIList(aUTIList, aUTIstr)
	set anArray to NSArray's arrayWithArray:aUTIList
	set aPred to NSPredicate's predicateWithFormat_("SELF UTI-CONFORMS-TO %@", aUTIstr)
	set bRes to (anArray's filteredArrayUsingPredicate:aPred) as list
	return bRes
end filterUTIList

Would be fine to concatenate your script with this small one:

set tempFolder to path to temporary items as text
set posixPaths to {}
tell application "Preview"
	set theDocs to documents
	repeat with i from 1 to count theDocs
		set aPDF to tempFolder & "newPDF#" & i & ".pdf" as «class furl»
		set end of posixPaths to POSIX path of aPDF
		save theDocs's item i in aPDF
	end repeat
end tell
posixPaths

This one saves the open documents as PDF files and return the list of the POSIX path of the created PDFs.
So the full job would be done in a single call.

I was not sure of the behavior of the instruction designed to create the PDFs. I tested it with several documents and not only it worked but with Numbers files the PDF was better than the one created by the export command of Preview.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) samedi 26 octobre 2019 19:47:01

Here the synthesis of the two proposed scripts


-- Combining PDFs in Chronological Order
-- Created 2018-05-26 by Takaaki Naganoya
-- 2018 Piyomaru Software

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "Quartz"

property PDFDocument : a reference to current application's PDFDocument
property NSString : a reference to current application's NSString
property NSUUID : a reference to current application's NSUUID
property |NSURL| : a reference to current application's |NSURL|
property NSArray : a reference to current application's NSArray
property NSPredicate : a reference to current application's NSPredicate
--property NSFileManager : a reference to current application's NSFileManager
property NSURLPathKey : a reference to current application's NSURLPathKey
property NSMutableArray : a reference to current application's NSMutableArray
property NSSortDescriptor : a reference to current application's NSSortDescriptor
property NSURLIsPackageKey : a reference to current application's NSURLIsPackageKey
property NSURLIsDirectoryKey : a reference to current application's NSURLIsDirectoryKey
--property NSURLTypeIdentifierKey : a reference to current application's NSURLTypeIdentifierKey
property NSURLContentModificationDateKey : a reference to current application's NSURLContentModificationDateKey
--property NSDirectoryEnumerationSkipsHiddenFiles : a reference to current application's NSDirectoryEnumerationSkipsHiddenFiles


# Some new instructions by Yvan KOENIG 2019/10/26
set sourceFolder to (choose folder) as text

set filRes1 to {}
tell application "Preview"
	set theDocs to documents
	repeat with i from 1 to count theDocs
		set theDoc to theDocs's item i
		set sourcePDF to path of theDoc # returns a POSIX path
		if sourcePDF ends with ".pdf" then
			# No need to re-code an existing PDF, just duplicate it
			set docName to name of theDoc
			set aPDF to sourceFolder & docName as «class furl»
			try # useful if the original is stored in sourceFolder
				tell application "Finder" to duplicate (sourcePDF as «class furl») to (sourceFolder as «class furl»)
			end try
		else
			set aPDF to (sourceFolder & my dateTimeStamp() & "_" & i & ".pdf") as «class furl»
			save theDoc in aPDF
		end if
		set end of filRes1 to POSIX path of aPDF
	end repeat
end tell

set outPathTarg to POSIX path of sourceFolder

# end of new code

set pathString to NSString's stringWithString:outPathTarg
set newPath to (pathString's stringByDeletingLastPathComponent()) as string
set destPosixPath to newPath & "/" & ((NSUUID's UUID()'s UUIDString()) as string) & ".pdf"

combinePDFsAndSaveIt(filRes1, destPosixPath) of me

on combinePDFsAndSaveIt(inFiles, destPosixPath)
	set inFilesSorted to my filesInListSortFromOldToNew(inFiles)
	
	-- Get URL of first PDF
	set inNSURL to |NSURL|'s fileURLWithPath:(POSIX path of item 1 of inFilesSorted)
	set theDoc to PDFDocument's alloc()'s initWithURL:inNSURL
	
	-- Repeat loop for the rest of the PDF
	set oldDocCount to theDoc's pageCount()
	set inFilesSorted to rest of inFilesSorted
	repeat with aFile in inFilesSorted
		set inNSURL to (|NSURL|'s fileURLWithPath:(POSIX path of aFile))
		set newDoc to (PDFDocument's alloc()'s initWithURL:inNSURL)
		set newDocCount to newDoc's pageCount()
		repeat with i from 1 to newDocCount
			set thePDFPage to (newDoc's pageAtIndex:(i - 1)) -- zero-based indexes
			(theDoc's insertPage:thePDFPage atIndex:oldDocCount)
			set oldDocCount to oldDocCount + 1
		end repeat
	end repeat
	
	set outNSURL to |NSURL|'s fileURLWithPath:destPosixPath
	(theDoc's writeToURL:outNSURL)
end combinePDFsAndSaveIt

on filesInListSortFromOldToNew(aliasList)
	set keysToRequest to {NSURLPathKey, NSURLIsPackageKey, NSURLIsDirectoryKey, NSURLContentModificationDateKey}
	
	set valuesNSArray to NSMutableArray's array()
	repeat with i in aliasList
		set oneNSURL to (|NSURL|'s fileURLWithPath:(POSIX path of i))
		(valuesNSArray's addObject:(oneNSURL's resourceValuesForKeys:keysToRequest |error|:(missing value)))
	end repeat
	
	set theNSPredicate to NSPredicate's predicateWithFormat_("%K == NO OR %K == YES", NSURLIsDirectoryKey, NSURLIsPackageKey)
	set valuesNSArray to valuesNSArray's filteredArrayUsingPredicate:theNSPredicate
	
	set theDescriptor to NSSortDescriptor's sortDescriptorWithKey:(NSURLContentModificationDateKey) ascending:true
	set theSortedNSArray to valuesNSArray's sortedArrayUsingDescriptors:{theDescriptor}
	
	-- Extract only Posix paths and convert to AppleScript list
	return (theSortedNSArray's valueForKey:(NSURLPathKey)) as list
end filesInListSortFromOldToNew

on dateTimeStamp()
	tell (current date) to return (((its year) * 10000 + (its month) * 100 + (its day)) as text) & "_" & text 2 thru -1 of ((1000000 + (its hours) * 10000 + (its minutes) * 100 + (its seconds)) as text)
end dateTimeStamp

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) samedi 26 octobre 2019 22:17:33

Edited the way to get the name of the document.