PowerShellでPDFをPocketMod形式に再レイアウト (w/iTextSharp)

PowerShellからiTextSharpを呼び出してPDFをPocketMod形式にレイアウトし直します。

PocketMod形式については、
https://pocketmod.com
https://repocketmod.com
あたりをご参照あれ。
（"PocketMod"でググる方が早いと思いますが、一応）

どこかのサイトにPDFからPocketMod形式に変換するツールがあったのですが、使用しているWindows環境では動かなくなってしまっていたので、サラッと作ってみました。

このスクリプトはPDFを8ページを１ページに割り付けている(8 in 1)だけなので、itextsharpを使ったPDFの加工例として見て貰えれば良いかと。

# 下準備

まず始めに itextsharp.dll を入手してください。

https://www.nuget.org/packages/iTextSharp/

nugetするのが本筋ですが、スクリプトを置くフォルダにlibというサブフォルダを作って、その下に itextsharp.dll を入れる方式です。
よって、下記スクリプトもこの前提です。

# スクリプト

以下を ConvertTo-PocketMod.ps1 として保存。

```powershell:ConvertTo-PocketMod.ps1
# Convert PDF To PocketMod style
param(
    [parameter(mandatory=$true)][string]$sourceDataPath,
    [parameter(mandatory=$true)][string]$destinationPath
)

function ConvertTo-PocketMod($sourceDataPath, $destinationPath)
{
    # path of itextsharp.dll
    [System.Reflection.Assembly]::LoadFrom((Join-Path (Split-Path $script:MyInvocation.MyCommand.Path) "\lib\itextsharp.dll")) | Out-Null

    $pr = New-Object iTextSharp.text.pdf.PdfReader([string]$sourceDataPath)
    if($pr.IsEncrypted()){
        Write-Error("Encrypted: ", $sourceDataPath)
    }

    try {
        # Output page size
        $dst = [iTextSharp.text.PageSize]::A4
        $doc = New-Object iTextSharp.text.Document($dst)

        $fs = New-Object System.IO.FileStream([string]$destinationPath, [System.IO.FileMode]::Create)
        $pw = [iTextSharp.text.pdf.PdfWriter]::GetInstance($doc, $fs)

        $doc.Open()

        [float]$rads = (180 * [math]::PI) / 180
        
        for($page = 1; $page -le $pr.NumberOfPages; $page++) {

            if(($page % 8) -eq 1){
                $doc.NewPage() | Out-Null
            }

            $src = $pr.GetPageSize($page)
            if($src.Width -gt $src.Height){
                $isLandscape = $TRUE
            }
            else{
                $isLandscape = $FALSE
            }
            if($isLandscape -eq $TRUE){
                [float]$scale = $dst.Height / ($src.Height * 4)
                [float]$offset = (($dst.Width / 2) - ($scale * $src.Width)) / 2
            }
            else{
                [float]$scale = $dst.Height / ($src.Width * 4)
                [float]$offset = (($dst.Width / 2) - ($scale * $src.Height)) / 2
            }

            # For scaling and rotation
            $transRotate = New-Object iTextSharp.awt.geom.AffineTransform
            $transRotate.SetToIdentity()
            $transrotate.Scale($scale, $scale)

            # For position
            $transAdjust = New-Object iTextSharp.awt.geom.AffineTransform
            $transadjust.SetToIdentity()

            # Affine translation of PocketMod style
            [float]$px = $dst.Width / 2
            [float]$py = $dst.Height / 4

            $rot = $pr.GetPageRotation($page)

            if(($isLandscape -eq $TRUE) -or (($rot -eq 90) -or ($rot -eq 270))){
                switch($page % 8){

                    # Left side
                    # Rotate 180 degrees
                    1 {
                        $transAdjust.Translate($px - $offset, $py * 4)
                        $transRotate.Rotate(-$rads)
                    }
                    0 {
                        $transAdjust.Translate($px - $offset, $py * 3)
                        $transRotate.Rotate(-$rads)
                    }
                    7 {
                        $transAdjust.Translate($px - $offset, $py * 2)
                        $transRotate.Rotate(-$rads)
                    }
                    6 {
                        $transAdjust.Translate($px - $offset, $py)
                        $transRotate.Rotate(-$rads)
                    }

                    # Right side
                    # No rotation
                    2 {
                        $transAdjust.Translate($px + $offset, $py * 3)
                    }
                    3 {
                        $transAdjust.Translate($px + $offset, $py * 2)
                    }
                    4 {
                        $transAdjust.Translate($px + $offset, $py)
                    }
                    5 {
                        $transAdjust.Translate($px + $offset, 0)
                    }
                }
            }
            else{
                switch($page % 8){

                    # Left side
                    # Rotate counterclockwise 90 degrees
                    1 {
                        $transAdjust.Translate($px - $offset, $py * 3)
                        $transRotate.Rotate($rads)
                    }
                    0 {
                        $transAdjust.Translate($px - $offset, $py * 2)
                        $transRotate.Rotate($rads)
                    }
                    7 {
                        $transAdjust.Translate($px - $offset, $py)
                        $transRotate.Rotate($rads)
                    }
                    6 {
                        $transAdjust.Translate($px - $offset, 0)
                        $transRotate.Rotate($rads)
                    }

                    # Right side
                    # Rotate clockwise 90 degrees
                    2 {
                        $transAdjust.Translate($px + $offset, $py * 4)
                        $transRotate.Rotate(-$rads)
                    }
                    3 {
                        $transAdjust.Translate($px + $offset, $py * 3)
                        $transRotate.Rotate(-$rads)
                    }
                    4 {
                        $transAdjust.Translate($px + $offset, $py * 2)
                        $transRotate.Rotate(-$rads)
                    }
                    5 {
                        $transAdjust.Translate($px + $offset, $py)
                        $transRotate.Rotate(-$rads)
                    }
                }
            }

            $finalTrans = New-Object iTextSharp.awt.geom.AffineTransform
            $finalTrans.SetToIdentity()
            $finalTrans.Concatenate($transAdjust)
            $finalTrans.Concatenate($transRotate)

            $importedPage = $pw.GetImportedpage($pr, $page)

            $pcb = $pw.DirectContent
            $pcb.AddTemplate($importedPage, $finalTrans)

            # Draw guide line for folding
            if((($page % 8) -eq 0) -or ($page -eq $pr.NumberOfPages)){

                $pcb.SetLineWidth([float]0.01)

                #Draw outside border
                $pcb.MoveTo(0, 0)
                $pcb.LineTo($dst.Width, 0)
                $pcb.LineTo($dst.Width, $dst.Height)
                $pcb.LineTo(0, $dst.Height)
                $pcb.LineTo(0, 0)
                $pcb.Stroke()

                $pcb.MoveTo(0, $dst.Height * 3 / 4)
                $pcb.LineTo($dst.Width, $dst.Height * 3 / 4)
                $pcb.Stroke()

                $pcb.MoveTo(0, $dst.Height * 2 / 4)
                $pcb.LineTo($dst.Width, $dst.Height * 2 / 4)
                $pcb.Stroke()

                $pcb.MoveTo(0, $dst.Height * 1 / 4)
                $pcb.LineTo($dst.Width, $dst.Height * 1 / 4)
                $pcb.Stroke()

                $pcb.MoveTo($dst.Width / 2, 0)
                $pcb.LineTo($dst.Width / 2, $dst.Height * 1 / 4)
                $pcb.Stroke();

                $pcb.MoveTo($dst.Width / 2, $dst.Height * 3 / 4)
                $pcb.LineTo($dst.Width / 2, $dst.Height);
                $pcb.Stroke();

                $pcb.SetLineDash(3, 3)
                $pcb.MoveTo($dst.Width / 2, $dst.Height * 1 / 4)
                $pcb.LineTo($dst.Width / 2, $dst.Height * 3 / 4)
                $pcb.Stroke()

                $pcb.SetLineDash(0)
            
            }
        }

        $doc.Close()
        $pw.Close()
        $fs.Close()
        $pr.Close()
    }
    catch {
        Write-Error("Error: " + $_.Exception)
    }
}

ConvertTo-PocketMod (Convert-Path $sourceDataPath) $destinationPath

```

itextsharp.dllを置いた場所によっては、10行目のLoadFromのくだりを書き換える必要があります。

あと、19行目で出力ページサイズをA4にハードコーディングしているので、こちらも必要に応じて書き換えてください。

# 使用例

PowerShellを立ち上げて、

```powershell
PS> ConvertTo-PocketMod.ps1 元PDFファイル (Join-Path $PWD 出力先PDFファイル)
```

みたいに使います。
※PowerShellは基本絶対パス指定なので、相対パスを使いたい時はConvert-PathやJoin-Pathを組み合わせるようです
※Convert-Pathは指定したフォルダやファイルが存在しないとエラーになるので注意

# あとがき

ホントはCubePDFあたりにPocketMod形式の出力があればいいんですけどね。

PowerShellとiTextSharpとを組み合わせると、PDFをページ単位に分割するスクリプトや右綴じに変換するスクリプトなんてのも簡単に書けそうですね。
是非トライしてみてください。

一応、拙作の例を提示しておきます。
* [PowerShellでPDFをページごとに分割](https://qiita.com/acknpop/items/de56f3f83403cc0e1985)
* [PowerShellでPDFを右綴じに変換](https://qiita.com/acknpop/items/e85c91947a407bc5a91a)