如何使用本机macOS工具以编程方式旋转PDF中的所有页面



给定一个文件路径,我需要在该文件路径上旋转PDF中的所有页面,不使用macOS中不包含的任何内容。它的包装器将是AppleScript,所以我也可以访问命令行,因此可以访问macOS默认安装的所有脚本语言,但不需要brew,例如Python的pip

PDF页面旋转可以使用标准预览应用程序执行。

打开PDF文档后,点击1或2次制表键(取决于您的预览版本),即可使用"cmd a"选择侧栏上的所有页面。然后点击"cmd R"(或L)将所有选定页面向右(或向左)旋转90%,最后点击"cmdw"关闭窗口。预览默认情况下会要求保存更改,然后只需按enter键即可保存。

您可以使用System Events中的Keysstroke指令在Applescript中模拟所有这些键。

要将此子程序用于许多PDF文件,您有两个解决方案,仍在Applescript中:

1) 选择一个文件夹并循环浏览该文件夹的所有PDF文件以应用此子程序。

2) 在"Finder"中选择您的文件,您的脚本将直接使用所有Finder选择的文件来应用相同的子程序。

这样的GUI脚本并不完美,但预览是不可编写脚本的。。。不幸的是)-:

据我所知,macOS没有任何一个本机命令行Unix可执行文件,可以旋转PDF中的所有页面(同时保持基于文本的页面)。sip可以旋转单个页面PDF,但生成的PDF封装的图像,而不是文本(如果一开始是文本基础)。此外,不确定是否有办法只使用普通AppleScript,然后通过UI脚本默认Preview应用程序,而不使用Apple ScriptObjC(Cocoa-AppleScriptython等。

使用第三方命令行实用程序可能是最简单的,但您说过只能使用macOS的默认部分来完成。因此,我将提供一个AppleScript解决方案,该解决方案使用UI脚本Preview默认应用程序,在没有AppleScriptObjC或没有第三方实用程序等其他方法的情况下可以使用

此解决方案,如提供的(和编码的),假设PreviewPDF文档的默认应用程序,并使用它旋转PDF中的所有页面。它也被设置为Automator工作流。(尽管还有其他方法可以合并下面显示的AppleScript代码。)

首先,在Finder中,复制目标PDF文档并使用这些文档。

Automator中,创建一个新的工作流文档,添加以下操作

  • 获取指定的查找器项
    • 将复制的目标PDF文档添加到此操作
  • 运行AppleScript脚本
    • 用下面的代码替换默认代码

AppleScript代码

on run {input}
set thisLong to 0.25 -- # The value of 'thisLong' is decimal seconds delay between keystrokes, adjust as necessary.
set theRotation to "r" -- # Valid values are 'l' or 'r' for Rotate Left or Rotate Right.
set theViewMenuCheckedList to {}
set theMenuItemChecked to missing value
repeat with thisItem in input
tell application "Finder" to open file thisItem -- # By default, in this use case, the PDF file will open in Preview.
delay 1 --  # Adjust as necessary. This is the only 'delay' not defined by the value of 'thisLong'.
tell application "System Events"
perform action "AXRaise" of window 1 of application process "Preview" -- # Just to make sure 'window 1' is front-most.
delay thisLong
--  # Ascertain which of the first six 'View' menu items is checked.
set theViewMenuCheckedList to (value of attribute "AXMenuItemMarkChar" of menu items 1 thru 6 of menu 1 of menu bar item 5 of menu bar 1 of application process "Preview")
repeat with i from 1 to 6
if item i in theViewMenuCheckedList is not missing value then
set theMenuItemChecked to i as integer
exit repeat
end if
end repeat
--  # Process keystrokes based on which 'View' menu item is checked.
--  # This is being done so the subsequent keystroke ⌘A 'Select All' 
--  # occurs on the 'Thumbnails', not the body of the document.
if theMenuItemChecked is not 2 then
repeat with thisKey in {"2", "1", "2"}
keystroke thisKey using {option down, command down}
delay thisLong
end repeat
else
repeat with thisKey in {"1", "2"}
keystroke thisKey using {option down, command down}
delay thisLong
end repeat
end if
repeat with thisKey in {"a", theRotation as text, "s"} -- # {Select All, Rotate Direction, Save}
keystroke thisKey using {command down}
delay thisLong
end repeat
keystroke theMenuItemChecked as text using {option down, command down} -- # Resets the 'View' menu to the original view.
delay thisLong
keystroke "w" using {command down} -- # Close Window.
end tell
end repeat
end run

注意:

  • 由于此脚本使用UI脚本,当从Automator(或脚本编辑器)运行时,必须将运行的应用程序添加到系统首选项安全性&隐私可访问性以便正确运行。保存为应用程序时,需要添加保存的应用程序
  • 此外,对于UI脚本,可能需要更改delay命令的值,以便在您的系统上使用(和/或根据需要添加其他delay指令,尽管在这种情况下不需要其他delay命令)。不用说,首先在一组几个文档上测试这一点,以确保thisLong的值集在您的系统上有效。在我的系统上,这是按编码工作的
  • 以这种方式使用UI Scripting时,一旦任务启动,就必须让系统自行处理文件。尝试多任务只会将焦点从手头的任务上移开,并导致它失败
  • 如果您需要旋转一次以上,请将额外的theRotation as text,添加到:

    repeat with thisKey in {"a", theRotation as text, "s"} -- # {Select All, Rotate Direction, Save}
    

    示例:

    repeat with thisKey in {"a", theRotation as text, theRotation as text, "s"}
    

您可以使用Cocoa AppleScript中的PDFPage的方法。

看看https://developer.apple.com/documentation/pdfkit/pdfdocument?language=objc和https://developer.apple.com/documentation/pdfkit/pdfpage?language=objc


这是脚本:

use scripting additions
use framework "Foundation"
set thisPath to POSIX path of (choose file of type {"pdf"} with prompt "Choose a PDF") --  a POSIX path, not an alias or an HFSPath
set thisDoc to current application's PDFDocument's alloc()'s initWithURL:(current application's NSURL's fileURLWithPath:thisPath)
set pCount to thisDoc's pageCount()
repeat with i from 0 to (pCount - 1)
((thisDoc's pageAtIndex:i)'s setRotation:90) -- rotate to 90,  the rotation must be a positive or negative multiple of 90: (0, 90, 180, 270 or -90 -180 -270)
end repeat
thisDoc's writeToFile:thisPath -- save --

注意:如果页面的旋转0(无旋转),则此脚本将正常工作,否则您必须获得页面的旋转并进行计算。


这里有一个向右或向左旋转页面的示例:

use scripting additions
use framework "Foundation"
set thisPath to POSIX path of (choose file of type {"pdf"} with prompt "Choose a PDF") --  a POSIX path, not an alias or an HFSPath
my rotatePages(thisPath, 90) -- rotate right , use -90 to rotate left
on rotatePages(thisPath, r)
set thisDoc to current application's PDFDocument's alloc()'s initWithURL:(current application's NSURL's fileURLWithPath:thisPath)
set pCount to thisDoc's pageCount()
repeat with i from 0 to (pCount - 1)
set thisPage to (thisDoc's pageAtIndex:i)
(thisPage's setRotation:((thisPage's |rotation|()) + r)) -- add 90 to the current rotation, note: at 360, the value of the rotation will be set to 0, not to 360
end repeat
thisDoc's writeToFile:thisPath -- save --
end rotatePages

您现在可以使用Swift作为脚本语言。Shortcuts.app包括在其";运行Shell脚本"行动

以下脚本将旋转其参数中作为文件名提供的PDF的所有页面。

#!/usr/bin/swift
// ROTATE PAGES: Rotate all pages of selected PDFs by 90˚
import Foundation
import Quartz
func newURL(filepath: String, newbit: String) -> String {
var newname = filepath
while (FileManager.default.fileExists(atPath: newname)) {
newname = (newname as NSString).deletingPathExtension
// Could be improved with incremental number added to filename
newname += newbit
}
return newname
}
func rotatePage(filepath: String) -> Void {
let pdfURL = URL(fileURLWithPath: filepath)
let newFilepath = newURL(filepath: filepath, newbit: " +90.pdf")
if let pdfDoc = PDFDocument.init(url: pdfURL) {
let pages = pdfDoc.pageCount
for p in (0...pages) {
let page = pdfDoc.page(at: p)
var newRotation: Int = 90
if let existingRotation = page?.rotation {
newRotation = (existingRotation + 90) as Int
}
page?.rotation = newRotation
}
pdfDoc.write(toFile: newFilepath)
}

return
}
// "main"
if CommandLine.argc > 1 {
for (index, args) in CommandLine.arguments.enumerated() {
if index > 0 {
rotatePage(filepath: args)

}
}
}

这是我使用的解决方案。

  • Automator将PDF分离到与原始PDF相同级别的新文件夹中的各个页面
  • 使用图像事件旋转单页PDF的某些AppleScript
  • 第二个Automator工作流,将单独的PDF重新缝合在一起
  • 控制一切的AppleScript,根据需要使用do shell scriptopen的每个Automator工作流

MacOS附带python,您可以在Automator中使用它(运行shell脚本-设置为python,将输入作为参数传递)来创建一个在Finder中处理PDF的服务。

#!/usr/bin/python
# coding=utf-8
import sys
import os
from Quartz import PDFDocument
from CoreFoundation import NSURL
if __name__ == '__main__':
for filename in sys.argv[1:]:
filename = filename.decode('utf-8')
shortName = os.path.splitext(filename)[0]
pdfURL = NSURL.fileURLWithPath_(filename)
pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL)
pages = pdfDoc.pageCount()
for p in range(0, pages):
page = pdfDoc.pageAtIndex_(p)
existingRotation = page.rotation()
newRotation = existingRotation + 90
page.setRotation_(newRotation)
pdfDoc.writeToFile_(filename)

你可以在这个GitHub网站上找到大量类似的脚本和Automator工作流

最新更新