LoginSignup
1

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-02-17

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

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

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

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

下準備

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

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

スクリプト

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

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を立ち上げて、

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

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

あとがき

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

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

一応、拙作の例を提示しておきます。
* PowerShellでPDFをページごとに分割
* PowerShellでPDFを右綴じに変換

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1