印刷所からのゲラの確認や書き込みをしたゲラをスキャンして著者に送るなど、書籍編集の仕事をしていると本当に多くの PDF ファイルを扱います。
書籍全体の横断検索用として各章ごとに分割されたゲラを全章結合したり、 スキャン時に誤って上下逆になってしまったファイルを回転させたりといった処理に pdftk を使用しているのですが、powershell のパイプライン処理と組み合わせてもっと柔軟に使えないものかと各種コマンドレットを作ってみました。
事前準備
pdftk は scoop で入手できます。非エンジニアで Windows しか触れたことない身としては神ツールだと思って感謝の気持ちで使用しています。
PS > scoop install pdftk
インストールが済んだら下記の各コマンドレットを $profile
に書き込んでおくかドットソースで読み込んでおきます。なお、基本的に複数のファイルを一括処理するためのコマンドレットなので、パイプライン末尾でのフィルタ的な使い方のみ想定しています。
複数の PDF を結合する
Get-ChildItem
で取得した作業ディレクトリ内の pdf ファイルを以下のコマンドレットにパイプすることで単一の pdf が生成されます。
コード
function Invoke-PdftkConc {
<#
.SYNOPSIS
パイプライン経由で渡されたpdfファイルを結合する
.DESCRIPTION
・pdftk.exe を使用
・pdfファイル以外は自動で無視する
.PARAMETER outName
出力するファイル名(拡張子不要)
.EXAMPLE
ls | Invoke-PdftkConc
#>
param(
[string]$outName = "concatenated_output"
)
$fullpath = Join-Path -Path $PWD.Path -ChildPath ("{0}.pdf" -f $outName)
if (Test-Path $fullpath) {
Write-Host ("'{0}.pdf' already exists!" -f $outName) -ForegroundColor Magenta
return
}
$procTarget = @()
$targetPath = @()
$input | Where-Object Extension -eq ".pdf" | ForEach-Object {
$procTarget += $_
$targetPath += "'{0}'" -f $_.Fullname
}
Write-Host "concatenating as " -NoNewline
Write-Host ("{0}.pdf" -f $outName) -ForegroundColor Green -NoNewline
Write-Host ":"
$procTarget | ForEach-Object {
Write-Host " + " -NoNewline
Write-Host $_.Name -ForegroundColor Cyan
}
try {
Invoke-Expression -Command ("pdftk {0} cat output '{1}.pdf'" -f ($targetPath -join " "), $outName)
}
catch {
Write-Host "ERROR: failed to concatanate pdfs!" -ForegroundColor Red
}
}
# Alias
Set-Alias pdfConc Invoke-PdftkConc
どのファイルをどのように結合するかなど、処理内容を色付きで表示しようとして冗長な書き方になってしまいました。単純に結合処理のみ行う場合はもっとシンプルに簡略化できるはず。
使い方
PS > ls -file | pdfConc -outName "全体結合"
concatenating as 全体結合.pdf:
+ 01.pdf
+ 02.pdf
+ 03.pdf
# 逆順に結合
PS > ls -file | sort -descending | pdfConc -outName "全体結合_逆順"
concatenating as 全体結合_逆順.pdf:
+ 03.pdf
+ 02.pdf
+ 01.pdf
複数の PDF を回転させる
Get-ChildItem
で取得した作業ディレクトリ内の pdf ファイルを以下のコマンドレットにパイプすることで、各ファイルを指定の向きに回転させて、それぞれ先頭に「rotated_」をつけて新規ファイルに書き出します。
コード
function Invoke-PdftkRotate {
<#
.SYNOPSIS
pdftk.exe を使用してパイプライン経由で渡された pdf を回転させる
.DESCRIPTION
・pdfファイル以外は自動で無視
・処理したファイルは先頭に "rotated_" を付けて保存
.PARAMETER clockwise
回転角度
.EXAMPLE
ls| Invoke-PdftkRotate
#>
param(
[ValidateSet(90, 180, 270)]$clockwise = 180
)
$direction = switch -exact ($clockwise) {
90 {"right"}
270 {"left"}
default {"down"}
}
$input | Where-Object Extension -eq ".pdf" | ForEach-Object {
try {
Invoke-Expression -Command ("pdftk '{0}' cat 1-end{1} output 'rotated_{2}'" -f $_.Fullname, $direction, $_.Name)
Write-Host ("rotating '{0}' clockwise {1} degrees..." -f $_.Name, $clockwise)
}
catch {
Write-Host ("ERROR: failed to rotate '{0}'!" -f $_.Name) -ForegroundColor Red
}
}
}
# Alias
Set-Alias pdfRotate Invoke-PdftkRotate
PDF の特定ページを抽出する
ページの抽出に関してはパイプライン入力は想定していません(複数のファイルを一括処理するような場面はめったにないため)。
コード
function Invoke-PdftkExtract {
<#
.SYNOPSIS
pdfファイルの指定範囲を抽出するコマンドレット
.DESCRIPTION
pdftk.exe を使用
.PARAMETER path
処理対象文書のパス
.PARAMETER range
抽出範囲
未指定の場合はヘルプを表示
.PARAMETER outName
出力するファイル名(拡張子不要)
.PARAMETER force
同名のファイルがあった場合も上書きして保存する
.EXAMPLE
Invoke-PdftkExtract -path ./hoge.pdf -range "1-2" -outName hogehoge
"hoge.pdf" の1~2ページを "hogehoge.pdf" に出力
.EXAMPLE
Invoke-PdftkExtract -path ./hoge.pdf -range "20-end" -outName hogehoge
"hoge.pdf" の20~最終ページを "hogehoge.pdf" に出力
.EXAMPLE
Invoke-PdftkExtract -path ./hoge.pdf -range "2 3 45 69" -outName hogehoge
"hoge.pdf" の2,3,45,69ページを "hogehoge.pdf" に出力
.EXAMPLE
Invoke-PdftkExtract -path ./hoge.pdf -range "2,5,10" -outName hogehoge
range の区切りはカンマでもよい
#>
param (
[string]$path,
[string]$range,
[string]$outName,
[switch]$force
)
if (-not $range) {
Write-Host "sample of parameter 'range':" -ForegroundColor White
Write-Host " -range '1 4 5'" -ForegroundColor Cyan
Write-Host " => extract page 1, 4 and 5."
Write-Host " -range '8 6 4'" -ForegroundColor Cyan
Write-Host " => extract page 8, 6 and 4."
Write-Host " -range '1-5 9'" -ForegroundColor Cyan
Write-Host " => extract page 1 to 5 and 9."
Write-Host " -range 5-end" -ForegroundColor Cyan
Write-Host " => extract page 5 to last page."
Write-Host " -range 1-endodd" -ForegroundColor Cyan
Write-Host " => extract each odd page."
Write-Host " -range 1-endeven" -ForegroundColor Cyan
Write-Host " => extract each even page."
return
}
$fullpath = Join-Path -Path $PWD.Path -ChildPath "$outName.pdf"
if (Test-Path $fullpath) {
if (-not $force) {
Write-Host ("'{0}.pdf' already exists!" -f $outName) -ForegroundColor Magenta
return
}
}
try {
Write-Host "extracting " -NoNewline
Write-Host ("{0}" -f $range) -NoNewline -ForegroundColor Cyan
Write-Host " as " -NoNewline
Write-Host ( "{0}.pdf" -f $outName) -ForegroundColor Green -NoNewline
Write-Host "..."
Invoke-Expression -Command ("pdftk '{0}' cat {1} output '{2}.pdf'" -f $path, $range, $outName)
}
catch {
Write-Host ("ERROR: failed to extract page from '{0}'!" -f $path) -ForegroundColor Red
}
}
# Alias
Set-Alias pdfExtract Invoke-PdftkExtract
本家の range
の指定書式を毎回忘れてしまうので、パラメータを指定しなかった場合にヘルプを表示するようにしています。
複数のファイルから一括で特定のページを抽出するような場合は以下のようにします。
PS > ls | ? Extension -eq ".pdf" | % {pdfExtract -path $_.Fullname -range 2 -outName ("extracted_" + $_.Name)}