Tambourine作業メモ

主にスキル習得のためにやった作業のメモ。他人には基本的に無用のものです。

自炊PDFファイルの綴じ方を右から左に変えるコマンドラインツールを作る

ScanSnapで本をスキャンして、タブレットで読むのは最高である。買ったけど読んでない、いわゆる積ん読本が400gたらずのタブレットの中に100冊以上積まれているかと思うと、たまらない快感を覚えて気を失いそうになる。どうしてそんなに買ったのか。

それはさておき、本を裁断してスキャンすると、デフォルトではPDFは右開きになる。つまり、横書きの本のように左側が閉じられる。

(ちなみに、上のわかりやすい絵は 【製本】右綴じ・左綴じの理由と冊子作成時のポイント – 冊子製本お役立ちコラム からパクってきた。感謝をこめてリンクをしておく)

技術書も買うのでもちろん全てではないが、日本人なので多くの本が右綴じだ。これをPDF編集ソフトで変更してやる必要がある。これがなかなかやっかいである。

まず、日本以外で作られたPDF編集ソフトにはそもそも綴じ方の変更機能がなかったりする。右綴じする本が世の中にあることを知らんのだ。嘆かわしい。

王道は、Adobe Acrobatだ。しかし、いまやAcrobatはサブスクで、月額1500円する。もちろん、カバーの裏の白紙を削除したり、カバー裏を最後にオマケに付けたりするのでページの削除や入れ換え機能も使う。しかし、それに年間18000円以上払うのはキツい。

そんな人の為に、徳島謹製JustSystemのJUST PDF、ソースネクストからはいきなりPDFという国産ソフトウェアがある。どちらも数千円で変える。ちゃんと綴じ方が変えられる。ありがたい。

www.justsystems.com

www.sourcenext.com

ありがたいのだが・・・この2つのソフトウェア、どちらも微妙に不安定。だいたい同じぐらい不安定だ。今はいきなりPDFを使っていて、先日、バージョン12にバージョンアップした。たった3000円でバージョンアップできて素晴らしいのだが、文書の最後のページを削除しようとすると時々クラッシュする何年も前からあるバグは健在で、かつ、時々ズームボタンが勝手に連打され、どんどん本が拡大表示されていくという素敵なバグが追加されていた。文句をいうような値段ではないのだが、微妙にストレスが溜まる。まあ、どちらも本の自炊をやっている個人ユーザーのために作っているソフトではないのだ。会社の中で書類をやり取りすることを念頭に作られている。何百ページもあるPDFを扱うと不安定なのは、ユースケースからズレているからだ。

それに、そもそも大量にスキャンした何十冊ものPDFをひとつずつ開いて、綴じ方を変えて(プロパティの奥なのでクイックメニューみたいなのにも登録できないのだ)、表紙の裏の白紙を削除して閉じるというのは地味に大変な作業だ。いや、もう何千冊とやってきたんだが。

というわけで、スキャン結果を確認したり(たまに間違えて2冊連続でスキャンして、1ファイルに2冊分入っていたりするのだ)、途中でジャムっちゃって2つのファイルに分かれちゃったのを合体させたりはするので、いきなりPDFが不要にはならないんだが、手作業が減ればストレスも減る。なんとかコマンドラインツールで一括処理出来ないものだろうか・・・というのが今回やりたいことだ。

とりあえず、10年以上前にC#は書いたことがあるが(VB.NETもあるぞ)、他に何にも知識がない状態でWindowsのコマンドラインツールを作っていこう。

すごいもので、私が若かりし頃、大学生協の棚の上に学割ですら10万円近い値札を付けられてでっかい箱に入って後光をきらめかせていたあのVisual Studioも、いまやコミュニティ版はタダだ。特になにも考えることなくダウンロードする。インストール時にASP.net作るのかとか、Pythonを書くのか(VSでPython書くものなのか、最近は)とか、いろいろ聞かれるが、コマンドラインアプリにだけチェックを入れておく。まあ、やりたくなったら足せるのだろう。F#は追加しておこう(笑)

在りし日のVisual Studioのデカい箱

起動して、プロジェクトを作成して、Hello, Worldまではすぐに出来た。スムーズである。VSのインストールがものの数分で終わるというのが信じられない。

さて、PDFを触らなければならないが、当然、何かしらのライブラリを拾ってくることになるのだろう。最近はこういうとき、AIに相談するものらしい。Perplexityに「PDFの綴じ方を変えるWindowsのコンソールアプリケーションを作成したいです。PDF編集を行うライブラリを推薦してください」とお願いしてみた。

すると、iText7とPDFSharpのどっちかがいいんじゃね?とのこと。とりあえず、iText7なるものを探してみる。

どうやら、これのようだ。なんか最新は9になっているぞ?

iText Suite .NET (C#) | iText PDF

使い方がさっぱりわからない。Perplexityが出してくれたサンプルを頼りにどうにかするか・・・。

とりあえず、インストールしないことには始まらない。Visual StudioのNuGetのパッケージマネージャコンソールというところから、適当にコマンドを打ってみる。コンソールを起動すると、get-help NuGet してみてはいかがかというのでしてみたところ、Install-Packageというコマンドを打てば良いのではないかという結論になった。やってみよう。

PM> Install-Package iText


'.NETFramework,Version=v4.7.2' を対象とするプロジェクト 'ScannedPdfTuner' に関して、パッケージ 'iText.9.0.0' の依存関係情報の収集を試行しています
依存関係情報の収集に 42 sec かかりました
DependencyBehavior 'Lowest' でパッケージ 'iText.9.0.0' の依存関係の解決を試行しています
依存関係情報の解決に 0 ms かかりました
パッケージ 'iText.9.0.0' をインストールするアクションを解決しています
パッケージ 'iText.9.0.0' をインストールするアクションが解決されました
  GET https://api.nuget.org/v3-flatcontainer/newtonsoft.json/13.0.1/newtonsoft.json.13.0.1.nupkg
  GET https://api.nuget.org/v3-flatcontainer/system.buffers/4.5.1/system.buffers.4.5.1.nupkg
  GET https://api.nuget.org/v3-flatcontainer/system.runtime.compilerservices.unsafe/5.0.0/system.runtime.compilerservices.unsafe.5.0.0.nupkg
  GET https://api.nuget.org/v3-flatcontainer/system.memory/4.5.4/system.memory.4.5.4.nupkg
  GET https://api.nuget.org/v3-flatcontainer/system.numerics.vectors/4.5.0/system.numerics.vectors.4.5.0.nupkg
  GET https://api.nuget.org/v3-flatcontainer/microsoft.extensions.logging.abstractions/5.0.0/microsoft.extensions.logging.abstractions.5.0.0.nupkg
  GET https://api.nuget.org/v3-flatcontainer/microsoft.extensions.dependencyinjection.abstractions/5.0.0/microsoft.extensions.dependencyinjection.abstractions.5.0.0.nupkg
  GET https://api.nuget.org/v3-flatcontainer/microsoft.extensions.primitives/5.0.0/microsoft.extensions.primitives.5.0.0.nupkg
  OK https://api.nuget.org/v3-flatcontainer/system.memory/4.5.4/system.memory.4.5.4.nupkg 17 ミリ秒
  OK https://api.nuget.org/v3-flatcontainer/newtonsoft.json/13.0.1/newtonsoft.json.13.0.1.nupkg 18 ミリ秒
  OK https://api.nuget.org/v3-flatcontainer/system.runtime.compilerservices.unsafe/5.0.0/system.runtime.compilerservices.unsafe.5.0.0.nupkg 93 ミリ秒
  OK https://api.nuget.org/v3-flatcontainer/microsoft.extensions.logging.abstractions/5.0.0/microsoft.extensions.logging.abstractions.5.0.0.nupkg 142 ミリ秒
  OK https://api.nuget.org/v3-flatcontainer/microsoft.extensions.dependencyinjection.abstractions/5.0.0/microsoft.extensions.dependencyinjection.abstractions.5.0.0.nupkg 144 ミリ秒
  OK https://api.nuget.org/v3-flatcontainer/system.numerics.vectors/4.5.0/system.numerics.vectors.4.5.0.nupkg 147 ミリ秒
  OK https://api.nuget.org/v3-flatcontainer/system.buffers/4.5.1/system.buffers.4.5.1.nupkg 150 ミリ秒
  OK https://api.nuget.org/v3-flatcontainer/microsoft.extensions.primitives/5.0.0/microsoft.extensions.primitives.5.0.0.nupkg 172 ミリ秒
コンテンツ ハッシュ ORj7Zh81gC69TyvmcUm9tSzytcy8AVousi+IVRAI8nLieQjOFryRusSFh7+aLk16FN9pQNqJAiMd7BTKINK0kA== の https://api.nuget.org/v3/index.json から Microsoft.Extensions.DependencyInjection.Abstractions 5.0.0 がインストールされました。
  GET https://api.nuget.org/v3-flatcontainer/microsoft.extensions.options/5.0.0/microsoft.extensions.options.5.0.0.nupkg
コンテンツ ハッシュ NxP6ahFcBnnSfwNBi2KH2Oz8Xl5Sm2krjId/jRR3I7teFphwiUoUeZPwTNA21EX+5PtjqmyAvKaOeBXcJjcH/w== の https://api.nuget.org/v3/index.json から Microsoft.Extensions.Logging.Abstractions 5.0.0 がインストールされました。
  GET https://api.nuget.org/v3-flatcontainer/system.diagnostics.diagnosticsource/5.0.0/system.diagnostics.diagnosticsource.5.0.0.nupkg
コンテンツ ハッシュ cI/VWn9G1fghXrNDagX9nYaaB/nokkZn0HYAawGaELQrl8InSezfe9OnfPZLcJq3esXxygh3hkq2c3qoV3SDyQ== の https://api.nuget.org/v3/index.json から Microsoft.Extensions.Primitives 5.0.0 がインストールされました。
  GET https://api.nuget.org/v3-flatcontainer/system.threading.tasks.extensions/4.5.4/system.threading.tasks.extensions.4.5.4.nupkg
コンテンツ ハッシュ 1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw== の https://api.nuget.org/v3/index.json から System.Memory 4.5.4 がインストールされました。
  OK https://api.nuget.org/v3-flatcontainer/microsoft.extensions.options/5.0.0/microsoft.extensions.options.5.0.0.nupkg 16 ミリ秒
  GET https://api.nuget.org/v3-flatcontainer/microsoft.bcl.asyncinterfaces/5.0.0/microsoft.bcl.asyncinterfaces.5.0.0.nupkg
  OK https://api.nuget.org/v3-flatcontainer/system.diagnostics.diagnosticsource/5.0.0/system.diagnostics.diagnosticsource.5.0.0.nupkg 19 ミリ秒
コンテンツ ハッシュ Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg== の https://api.nuget.org/v3/index.json から System.Buffers 4.5.1 がインストールされました。
  GET https://api.nuget.org/v3-flatcontainer/microsoft.extensions.dependencyinjection/5.0.0/microsoft.extensions.dependencyinjection.5.0.0.nupkg
コンテンツ ハッシュ QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ== の https://api.nuget.org/v3/index.json から System.Numerics.Vectors 4.5.0 がインストールされました。
  GET https://api.nuget.org/v3-flatcontainer/system.valuetuple/4.5.0/system.valuetuple.4.5.0.nupkg
コンテンツ ハッシュ ZD9TMpsmYJLrxbbmdvhwt9YEgG5WntEnZ/d1eH8JBX9LBp+Ju8BSBhUGbZMNVHHomWo2KVImJhTDl2hIgw/6MA== の https://api.nuget.org/v3/index.json から System.Runtime.CompilerServices.Unsafe 5.0.0 がインストールされました。
  GET https://api.nuget.org/v3-flatcontainer/microsoft.extensions.logging/5.0.0/microsoft.extensions.logging.5.0.0.nupkg
  OK https://api.nuget.org/v3-flatcontainer/microsoft.bcl.asyncinterfaces/5.0.0/microsoft.bcl.asyncinterfaces.5.0.0.nupkg 26 ミリ秒
  OK https://api.nuget.org/v3-flatcontainer/system.threading.tasks.extensions/4.5.4/system.threading.tasks.extensions.4.5.4.nupkg 38 ミリ秒
  OK https://api.nuget.org/v3-flatcontainer/system.valuetuple/4.5.0/system.valuetuple.4.5.0.nupkg 30 ミリ秒
  OK https://api.nuget.org/v3-flatcontainer/microsoft.extensions.logging/5.0.0/microsoft.extensions.logging.5.0.0.nupkg 33 ミリ秒
  OK https://api.nuget.org/v3-flatcontainer/microsoft.extensions.dependencyinjection/5.0.0/microsoft.extensions.dependencyinjection.5.0.0.nupkg 66 ミリ秒
コンテンツ ハッシュ ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A== の https://api.nuget.org/v3/index.json から Newtonsoft.Json 13.0.1 がインストールされました。
  GET https://api.nuget.org/v3-flatcontainer/itext.commons/9.0.0/itext.commons.9.0.0.nupkg
コンテンツ ハッシュ W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ== の https://api.nuget.org/v3/index.json から Microsoft.Bcl.AsyncInterfaces 5.0.0 がインストールされました。
'nuget.org' からパッケージ 'itext 9.0.0' を取得しています。
'nuget.org' からパッケージ 'itext.commons 9.0.0' を取得しています。
'nuget.org' からパッケージ 'Microsoft.Bcl.AsyncInterfaces 5.0.0' を取得しています。
'nuget.org' からパッケージ 'Microsoft.Extensions.DependencyInjection 5.0.0' を取得しています。
'nuget.org' からパッケージ 'Microsoft.Extensions.DependencyInjection.Abstractions 5.0.0' を取得しています。
'nuget.org' からパッケージ 'Microsoft.Extensions.Logging 5.0.0' を取得しています。
'nuget.org' からパッケージ 'Microsoft.Extensions.Logging.Abstractions 5.0.0' を取得しています。
'nuget.org' からパッケージ 'Microsoft.Extensions.Options 5.0.0' を取得しています。
'nuget.org' からパッケージ 'Microsoft.Extensions.Primitives 5.0.0' を取得しています。
'nuget.org' からパッケージ 'Newtonsoft.Json 13.0.1' を取得しています。
'nuget.org' からパッケージ 'System.Buffers 4.5.1' を取得しています。
'nuget.org' からパッケージ 'System.Diagnostics.DiagnosticSource 5.0.0' を取得しています。
'nuget.org' からパッケージ 'System.Memory 4.5.4' を取得しています。
'nuget.org' からパッケージ 'System.Numerics.Vectors 4.5.0' を取得しています。
'nuget.org' からパッケージ 'System.Runtime.CompilerServices.Unsafe 5.0.0' を取得しています。
'nuget.org' からパッケージ 'System.Threading.Tasks.Extensions 4.5.4' を取得しています。
'nuget.org' からパッケージ 'System.ValueTuple 4.5.0' を取得しています。
  GET https://api.nuget.org/v3-flatcontainer/itext/9.0.0/itext.9.0.0.nupkg
  OK https://api.nuget.org/v3-flatcontainer/itext.commons/9.0.0/itext.commons.9.0.0.nupkg 26 ミリ秒
コンテンツ ハッシュ CBvR92TCJ5uBIdd9/HzDSrxYak+0W/3+yxrNg8Qm6Bmrkh5L+nu6m3WeazQehcZ5q1/6dDA7J5YdQjim0165zg== の https://api.nuget.org/v3/index.json から Microsoft.Extensions.Options 5.0.0 がインストールされました。
コンテンツ ハッシュ tCQTzPsGZh/A9LhhA6zrqCRV4hOHsK90/G7q3Khxmn6tnB1PuNU0cRaKANP2AWcF9bn0zsuOoZOSrHuJk6oNBA== の https://api.nuget.org/v3/index.json から System.Diagnostics.DiagnosticSource 5.0.0 がインストールされました。
  OK https://api.nuget.org/v3-flatcontainer/itext/9.0.0/itext.9.0.0.nupkg 27 ミリ秒
コンテンツ ハッシュ MgOwK6tPzB6YNH21wssJcw/2MKwee8b2gI7SllYfn6rvTpIrVvVS5HAjSU2vqSku1fwqRvWP0MdIi14qjd93Aw== の https://api.nuget.org/v3/index.json から Microsoft.Extensions.Logging 5.0.0 がインストールされました。
コンテンツ ハッシュ okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ== の https://api.nuget.org/v3/index.json から System.ValueTuple 4.5.0 がインストールされました。
コンテンツ ハッシュ zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg== の https://api.nuget.org/v3/index.json から System.Threading.Tasks.Extensions 4.5.4 がインストールされました。
コンテンツ ハッシュ Rc2kb/p3Ze6cP6rhFC3PJRdWGbLvSHZc0ev7YlyeU6FmHciDMLrhoVoTUEzKPhN5ZjFgKF1Cf5fOz8mCMIkvpA== の https://api.nuget.org/v3/index.json から Microsoft.Extensions.DependencyInjection 5.0.0 がインストールされました。
コンテンツ ハッシュ mfQuZz4beW2GOHMrtGay5iowg6WINBkHMm7hj38el3eDvWY3yLVZGrVxdWVRhEXJIdsXsBa3lPywxspsqi3rxg== の https://api.nuget.org/v3/index.json から itext.commons 9.0.0 がインストールされました。
パッケージ 'Microsoft.Extensions.DependencyInjection.Abstractions.5.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'Microsoft.Extensions.DependencyInjection.Abstractions.5.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
パッケージ 'Microsoft.Extensions.DependencyInjection.Abstractions.5.0.0' を 'packages.config' に追加しました
'Microsoft.Extensions.DependencyInjection.Abstractions 5.0.0' が ScannedPdfTuner に正常にインストールされました
パッケージ 'Microsoft.Extensions.Logging.Abstractions.5.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'Microsoft.Extensions.Logging.Abstractions.5.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
パッケージ 'Microsoft.Extensions.Logging.Abstractions.5.0.0' を 'packages.config' に追加しました
'Microsoft.Extensions.Logging.Abstractions 5.0.0' が ScannedPdfTuner に正常にインストールされました
パッケージ 'Newtonsoft.Json.13.0.1' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'Newtonsoft.Json.13.0.1' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
コンテンツ ハッシュ Y2nDUrXDO7eZ+dI2NZ10apW818OcgGklMWpLrVZ/P16rwSxpVyzjWZQjCgZVj8liSVhVDHy6eVJOrZah5KNhhQ== の https://api.nuget.org/v3/index.json から itext 9.0.0 がインストールされました。
パッケージ 'Newtonsoft.Json.13.0.1' を 'packages.config' に追加しました
'Newtonsoft.Json 13.0.1' が ScannedPdfTuner に正常にインストールされました
パッケージ 'System.Buffers.4.5.1' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'System.Buffers.4.5.1' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
パッケージ 'System.Buffers.4.5.1' を 'packages.config' に追加しました
'System.Buffers 4.5.1' が ScannedPdfTuner に正常にインストールされました
パッケージ 'System.Numerics.Vectors.4.5.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'System.Numerics.Vectors.4.5.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
パッケージ 'System.Numerics.Vectors.4.5.0' を 'packages.config' に追加しました
'System.Numerics.Vectors 4.5.0' が ScannedPdfTuner に正常にインストールされました
パッケージ 'System.Runtime.CompilerServices.Unsafe.5.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'System.Runtime.CompilerServices.Unsafe.5.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
パッケージ 'System.Runtime.CompilerServices.Unsafe.5.0.0' を 'packages.config' に追加しました
'System.Runtime.CompilerServices.Unsafe 5.0.0' が ScannedPdfTuner に正常にインストールされました
パッケージ 'System.Memory.4.5.4' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'System.Memory.4.5.4' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
パッケージ 'System.Memory.4.5.4' を 'packages.config' に追加しました
'System.Memory 4.5.4' が ScannedPdfTuner に正常にインストールされました
パッケージ 'Microsoft.Extensions.Primitives.5.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'Microsoft.Extensions.Primitives.5.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
パッケージ 'Microsoft.Extensions.Primitives.5.0.0' を 'packages.config' に追加しました
'Microsoft.Extensions.Primitives 5.0.0' が ScannedPdfTuner に正常にインストールされました
パッケージ 'Microsoft.Extensions.Options.5.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'Microsoft.Extensions.Options.5.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
パッケージ 'Microsoft.Extensions.Options.5.0.0' を 'packages.config' に追加しました
'Microsoft.Extensions.Options 5.0.0' が ScannedPdfTuner に正常にインストールされました
パッケージ 'System.Diagnostics.DiagnosticSource.5.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'System.Diagnostics.DiagnosticSource.5.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
パッケージ 'System.Diagnostics.DiagnosticSource.5.0.0' を 'packages.config' に追加しました
'System.Diagnostics.DiagnosticSource 5.0.0' が ScannedPdfTuner に正常にインストールされました
パッケージ 'System.Threading.Tasks.Extensions.4.5.4' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'System.Threading.Tasks.Extensions.4.5.4' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
パッケージ 'System.Threading.Tasks.Extensions.4.5.4' を 'packages.config' に追加しました
'System.Threading.Tasks.Extensions 4.5.4' が ScannedPdfTuner に正常にインストールされました
パッケージ 'Microsoft.Bcl.AsyncInterfaces.5.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'Microsoft.Bcl.AsyncInterfaces.5.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
パッケージ 'Microsoft.Bcl.AsyncInterfaces.5.0.0' を 'packages.config' に追加しました
'Microsoft.Bcl.AsyncInterfaces 5.0.0' が ScannedPdfTuner に正常にインストールされました
パッケージ 'Microsoft.Extensions.DependencyInjection.5.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'Microsoft.Extensions.DependencyInjection.5.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
パッケージ 'Microsoft.Extensions.DependencyInjection.5.0.0' を 'packages.config' に追加しました
'Microsoft.Extensions.DependencyInjection 5.0.0' が ScannedPdfTuner に正常にインストールされました
パッケージ 'System.ValueTuple.4.5.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'System.ValueTuple.4.5.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
パッケージ 'System.ValueTuple.4.5.0' を 'packages.config' に追加しました
'System.ValueTuple 4.5.0' が ScannedPdfTuner に正常にインストールされました
パッケージ 'Microsoft.Extensions.Logging.5.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'Microsoft.Extensions.Logging.5.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
パッケージ 'Microsoft.Extensions.Logging.5.0.0' を 'packages.config' に追加しました
'Microsoft.Extensions.Logging 5.0.0' が ScannedPdfTuner に正常にインストールされました
パッケージ 'itext.commons.9.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'itext.commons.9.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
パッケージ 'itext.commons.9.0.0' を 'packages.config' に追加しました
'itext.commons 9.0.0' が ScannedPdfTuner に正常にインストールされました
パッケージ 'itext.9.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'itext.9.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
パッケージ 'itext.9.0.0' を 'packages.config' に追加しました
'itext 9.0.0' が ScannedPdfTuner に正常にインストールされました
NuGet の操作の実行に 7.23 sec かかりました
経過した時間: 00:00:50.8311032

さっぱりわからないが、何もマズいことは起きていなさそうだ。

Perplexityが提示してくれたサンプル通り(ほぼ。ちょっとV9で変わっているところがあるようだ)、こんな感じのコードを試しに書いてみた。

static void Main(string[] args)
{
    using (PdfDocument pdfDoc = new PdfDocument(
        new PdfReader(@"C:\Users\318535760\desktop\hoge.pdf"),
        new PdfWriter(@"C:\Users\318535760\desktop\fuga.pdf")))
    {
        PdfViewerPreferences vp = new PdfViewerPreferences();
        vp.SetDirection(
            PdfViewerPreferences.
                PdfViewerPreferencesConstants.LEFT_TO_RIGHT);
        pdfDoc.GetCatalog().SetViewerPreferences(vp);
    }
}

エラーになった。

NotSupportedException: Either com.itextpdf:bouncy-castle-adapter or com.itextpdf:bouncy-castle-fips-adapter dependency must be added in order to use BouncyCastleFactoryCreator

なんかパッケージが足りないと言っているような気がする。Bouncy Castleというのは初めて聞いたが、暗号化のライブラリらしい。パッケージ名を適当に想像してNuGetしてみる。そんなの知らないと何度か言われた後で、これなら通った。

PM> Install-Package itext.bouncy-castle-adapter


'.NETFramework,Version=v4.7.2' を対象とするプロジェクト 'ScannedPdfTuner' に関して、パッケージ 'itext.bouncy-castle-adapter.9.0.0' の依存関係情報の収集を試行しています
依存関係情報の収集に 8.42 sec かかりました
DependencyBehavior 'Lowest' でパッケージ 'itext.bouncy-castle-adapter.9.0.0' の依存関係の解決を試行しています
依存関係情報の解決に 0 ms かかりました
パッケージ 'itext.bouncy-castle-adapter.9.0.0' をインストールするアクションを解決しています
パッケージ 'itext.bouncy-castle-adapter.9.0.0' をインストールするアクションが解決されました
'nuget.org' からパッケージ 'BouncyCastle.Cryptography 2.4.0' を取得しています。
'nuget.org' からパッケージ 'itext.bouncy-castle-adapter 9.0.0' を取得しています。
  GET https://api.nuget.org/v3-flatcontainer/bouncycastle.cryptography/2.4.0/bouncycastle.cryptography.2.4.0.nupkg
  GET https://api.nuget.org/v3-flatcontainer/itext.bouncy-castle-adapter/9.0.0/itext.bouncy-castle-adapter.9.0.0.nupkg
  OK https://api.nuget.org/v3-flatcontainer/bouncycastle.cryptography/2.4.0/bouncycastle.cryptography.2.4.0.nupkg 16 ミリ秒
  OK https://api.nuget.org/v3-flatcontainer/itext.bouncy-castle-adapter/9.0.0/itext.bouncy-castle-adapter.9.0.0.nupkg 16 ミリ秒
コンテンツ ハッシュ 2IcE6/X6YMBr9dgxsSK+934zXqi7UcdsYHapaa+aD7LLHgcbEABzyVNzsTrVEcNO4XJAMLVtqk4MP8dcVNiyQw== の https://api.nuget.org/v3/index.json から itext.bouncy-castle-adapter 9.0.0 がインストールされました。
コンテンツ ハッシュ SwXsAV3sMvAU/Nn31pbjhWurYSjJ+/giI/0n6tCrYoupEK34iIHCuk3STAd9fx8yudM85KkLSVdn951vTng/vQ== の https://api.nuget.org/v3/index.json から BouncyCastle.Cryptography 2.4.0 がインストールされました。
パッケージ 'BouncyCastle.Cryptography.2.4.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'BouncyCastle.Cryptography.2.4.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
パッケージ 'BouncyCastle.Cryptography.2.4.0' を 'packages.config' に追加しました
'BouncyCastle.Cryptography 2.4.0' が ScannedPdfTuner に正常にインストールされました
パッケージ 'itext.bouncy-castle-adapter.9.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'itext.bouncy-castle-adapter.9.0.0' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
パッケージ 'itext.bouncy-castle-adapter.9.0.0' を 'packages.config' に追加しました
'itext.bouncy-castle-adapter 9.0.0' が ScannedPdfTuner に正常にインストールされました
NuGet の操作の実行に 5 sec かかりました
経過した時間: 00:00:14.7560642

なんか入ったっぽい。これで動くかな・・・お、ちゃんと綴じ方が変更された。ちなみに、手元のマンガをテストに使ったので、もう右綴じになっているものを左綴じに戻した。上のコードはLEFT_TO_RIGHTになっているけど、本来やりたいのは逆である。

さらに、ページの削除もしてもらおう。Perplexityに質問してみる。PdfDocument.RemovePage()で良いらしい。1行追加したら、ちゃんとページが削除された。これでカバーの裏の白紙を消せる。

素晴らしい。

さて、後はこれをコマンドラインツールとして使いたいので、引数とかオプションとかを扱いたい。Perplexityに「C#ってgetoptみたいなのないの?」と聞いてみたところ、System.CommandLineを使えとのこと。

using System.CommandLineと書いてみたがエラーになるので、NuGetすればいいのかな?あれ?Install-Package System.CommandLineは「そんなのないよ」というエラーになる。

NuGet Gallery | System.CommandLine 2.0.0-beta4.22272.1

を見に行ってみたところ、こいつはまだprereleaseなので、latestでは入らないみたい。やり方が書いてあったのでその通りにする。

PM> NuGet\Install-Package System.CommandLine -Version 2.0.0-beta4.22272.1


'.NETFramework,Version=v4.7.2' を対象とするプロジェクト 'ScannedPdfTuner' に関して、パッケージ 'System.CommandLine.2.0.0-beta4.22272.1' の依存関係情報の収集を試行しています
依存関係情報の収集に 1.76 sec かかりました
DependencyBehavior 'Lowest' でパッケージ 'System.CommandLine.2.0.0-beta4.22272.1' の依存関係の解決を試行しています
依存関係情報の解決に 0 ms かかりました
パッケージ 'System.CommandLine.2.0.0-beta4.22272.1' をインストールするアクションを解決しています
パッケージ 'System.CommandLine.2.0.0-beta4.22272.1' をインストールするアクションが解決されました
'nuget.org' からパッケージ 'System.CommandLine 2.0.0-beta4.22272.1' を取得しています。
  GET https://api.nuget.org/v3-flatcontainer/system.commandline/2.0.0-beta4.22272.1/system.commandline.2.0.0-beta4.22272.1.nupkg
  OK https://api.nuget.org/v3-flatcontainer/system.commandline/2.0.0-beta4.22272.1/system.commandline.2.0.0-beta4.22272.1.nupkg 79 ミリ秒
コンテンツ ハッシュ 1uqED/q2H0kKoLJ4+hI2iPSBSEdTuhfCYADeJrAqERmiGQ2NNacYKRNEQ+gFbU4glgVyK8rxI+ZOe1onEtr/Pg== の https://api.nuget.org/v3/index.json から System.CommandLine 2.0.0-beta4.22272.1 がインストールされました。
パッケージ 'System.CommandLine.2.0.0-beta4.22272.1' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しています
パッケージ 'System.CommandLine.2.0.0-beta4.22272.1' をフォルダー 'C:\Users\318535760\source\repos\ScannedPdfTuner\packages' に追加しました
パッケージ 'System.CommandLine.2.0.0-beta4.22272.1' を 'packages.config' に追加しました
'System.CommandLine 2.0.0-beta4.22272.1' が ScannedPdfTuner に正常にインストールされました
NuGet の操作の実行に 1.21 sec かかりました
経過した時間: 00:00:03.1578211

使い方はここを読む。

System.CommandLine でコマンドを定義する方法 - .NET | Microsoft Learn

多機能なので説明は面倒くさいが、特に難しいことをしようとしているわけではないので、ちまちまと書いていった。

どうしてmain関数がasyncになって、Taskというよくわからないものを返すようになってしまったのかはよくわからないのだが・・・まあ、そのままマネしておいた。

というわけで、最終的にこんな感じ。

using System;
using System.Linq;
using System.Threading.Tasks;
using System.CommandLine;
using System.IO;

using iText.Kernel.Pdf;
using Pvpc = iText.Kernel.Pdf.PdfViewerPreferences.PdfViewerPreferencesConstants;

namespace ScannedPdfTuner
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var l2rOption = new Option<bool>(
                name: "--l2r",
                description: "左綴じにします");
            l2rOption.AddAlias("-l");

            var outDirOption = new Option<String>(
                name: "--outDir",
                description: "出力先ディレクトリ名",
                getDefaultValue: () => "modify");
            outDirOption.AddAlias("-o");

            var inDirOption = new Option<String>(
                name: "--inDir",
                description: "入力元ディレクトリ名",
                getDefaultValue: () => "target");
            inDirOption.AddAlias("-i");
            
            var rootCommand = 
                new RootCommand("PDFファイルを右綴じにして、2ページ目を削除します");
            rootCommand.Add(l2rOption);
            rootCommand.Add(outDirOption);
            rootCommand.Add(inDirOption);

            rootCommand.SetHandler((l2rOptionValue, outDir, inDir) =>
            {
                // 出力先ディレクトリを空にする
                if (Directory.Exists(outDir))
                {
                    Directory.Delete(outDir, true);
                }
                Directory.CreateDirectory(outDir);

                var files = Directory.GetFiles(inDir, "*.pdf").Select((file) => Path.GetFileName(file))

                foreach(var file in files)
                {
                    using (PdfDocument pdfDoc = new PdfDocument(
                        new PdfReader(Path.Combine(inDir, file)),
                        new PdfWriter(Path.Combine(outDir, file))))
                    {
                        // 綴じ方を変える
                        PdfViewerPreferences vp = new PdfViewerPreferences();
                        vp.SetDirection(l2rOptionValue ?
                            Pvpc.LEFT_TO_RIGHT :
                            Pvpc.RIGHT_TO_LEFT);
                        pdfDoc.GetCatalog().SetViewerPreferences(vp);

                        // カバーの裏の白紙を削除する
                        pdfDoc.RemovePage(2);
                    }
                }

            },l2rOption, outDirOption, inDirOption);

            await rootCommand.InvokeAsync(args);
        }
    }
}

targetというディレクトリを作ってスキャンしたままのファイルを置き、実行すると、modifyというディレクトリに修正したPDFが出来る。

結果はこんな感じ。左がスキャンしたばかりのPDF。これを綴じ方を変えて、2ページ目を消すと読みやすくなる。

うーん、Perplexityに手伝ってもらうと速いなー。