Skip to content

Instantly share code, notes, and snippets.

@piyo-ko
Created July 10, 2019 14:02
Show Gist options
  • Save piyo-ko/a4af837cd1aa8a660d85164854a4cea6 to your computer and use it in GitHub Desktop.
Save piyo-ko/a4af837cd1aa8a660d85164854a4cea6 to your computer and use it in GitHub Desktop.
見開きでスキャンして作られたファイルを、本の1ページがファイルの1ページになるように変換すると、縦持ちのタブレット端末で見やすい

見開きでスキャンして作られたファイルを、本の1ページがファイルの1ページになるように変換する

何がしたいの?

見開き状態の本をスキャンして作られたPDFファイル (具体的には国立国会図書館デジタルコレクションから「全コマダウンロード」したPDFファイル) を、本の1ページがファイルの1ページになるように変換したい。

また、変換の際には、必要なページだけを指定して抽出したいし、カラー画像をグレースケール画像にしたい。

そして、makeを実行するだけ以上の変換が行われるようにしたい。

なぜそうしたいの?

デジタルコレクションにある続国訳漢文大成の『資治通鑑』などを気が向いたときに少しずつ読んでいるが、手持ちの機器 (13インチのノートパソコンおよび9.7インチiPad) では拡大なしでは見づらい (私の視力ではルビや注がよく見えない)。 そもそもこれは見開きでスキャンされたデータなので、本の縦長のページが左右に並んだものがファイルの横長の1ページとなっているわけだが、iPadを縦長にしてその1画面に本の1ページが映る (ファイルの横長の1ページの右半分ないし左半分だけが表示される) くらいにまで拡大して、ちょうど見やすい大きさなのだ。これはつまり、13インチのノートパソコンでは、見やすい大きさまで拡大すると1行を読むために上下スクロールが必要になり、それはそれで読みづらい、ということでもある。縦持ちのiPadで読みたい所以である。

ところが、iPadのブックAppだと、タップしてページを移動するたびに拡大率がリセットされるようなので、毎ページ毎ページ、ピンチアウトせねばならず、そのたびに読書の流れが中断されてしまう。

以上のような事情なので、本の1ページがファイルの1ページになるように、元の「全コマダウンロード」したPDFファイルを変換したい。

また、そもそも『資治通鑑』を最初から順にすべて読破するのは難しそうなので、ひとまず興味のある巻を拾い読みしたい。 よって指定した範囲のページだけ抽出して変換できるようにしたい。

もちろん、元のファイルの全体を変換して、その中から好きなところだけ読めばいい、という考え方もある。 だが、分厚い本だと、見開きになった右ページと左ページの境界の位置が、本の始めの方と終わりの方でややずれていたりする。すると、一律な切り出し範囲を設定しづらい (大きめのマージンをとれば良いという考えも可能だが)。 幸いというか、『資治通鑑』の各巻は、マージンを少なくとった設定を一律に適用しても一部のページだけ文字が切り出し範囲外にはみ出るということがない程度には、ページ数が少ない。 したがって、指定した範囲のページだけ抽出して変換できるようにすることは、望ましいと言えるだろう。

また、スキャンされた本を読む場合においては、少なくとも私は、どうもグレイスケール表示にした方が目が疲れないようである。今まではブックAppを使う前にアクセシビリティの設定でグレイスケール表示に切り替えていたが、これはこれで面倒だ。よって、最初から画像 (本のスキャン画像) をグレイスケールのものに変換しておきたい。

なお、以上の目的を達成するためのツールは色々とあるのだろうが、Makefileを書く練習もしたいので、makeの実行による変換方法を考えた。

事前にインストールが必要なものは?

私の使っているOSは macOS Mojaveで、使っているシェルはbashである。以下ではその環境を前提として書く。

  • Homebrewをインストールしておく。
  • $ brew install qpdfによりqpdfをインストールしておく。
  • $ brew install imagemagickによりImageMagickをインストールしておく (convertコマンドを使いたいから)。
  • $ brew install xpdfによりxpdfをインストールしておく (pdfimagesコマンドを使いたいから)。
  • MacTeXをインストールしておく (好みの問題だが、変換時のオプションの値を記録した表紙をつけたくて、その作成にXeLaTeXを使っているので。表紙が不要ならMacTeXも不要)。

用意するファイルは?

  • 見開き状態の本をスキャンして作られたPDFファイル (変換元ファイル) を適当なディレクトリに置く。以下、そこがカレントディレクトリだとして説明する。たとえば、『続国訳漢文大成 経子史部 第六巻』を「全コマダウンロード」してkokuyaku_kanbun_vol_06.pdfと名前を付けたものを、変換元ファイルとして使う。
  • 作業用ディレクトリを用意する。たとえば、カレントディレクトリの配下にtmpというディレクトリを用意する。
  • カレントディレクトリには、Makefileと、その中でincludeしているcommon_config.mkおよびconfig_for_individual_run.mkも置く。
  • 作業用ディレクトリには、表紙ページ作成用のLaTeXソースファイル (たとえばcover.texというファイル名) も置く。なお、そもそも表紙を用意するかどうか、また、用意するにしてもXeLaTeXを使うかどうかは、好みの問題なので、好みに合わせてMakefileを書き換えるとよい。
  • 以上の例をまとめると以下のようになる。なお、作業用ディレクトリ名とLaTeXソースファイル名 (のうち拡張子を除いた部分) は、common_config.mkの変数定義と合わせておく。
./kokuyaku_kanbun_vol_06.pdf

./Makefile
./common_config.mk
./config_for_individual_run.mk
./tmp/cover.tex

具体的な実行手順は?

./config_for_individual_run.mkの変数定義を適宜所望のものに書き換え、makeを実行する。具体的な実行手順はこれだけである。

なお、CROPRIGTHTCROPLEFTの設定値は、その都度試行錯誤が必要になる (何度か設定を変えながらmakeしては変換結果のPDFファイルを確認する必要がある) かもしれない。 その設定に際しては以下のような点を考慮すべきである。

  • 左ページと右ページの境界線は、必ずしも変換元ファイルの各ページの真ん中と一致するとは限らない。場合によっては、右ページの左端が中心線より左となり、左ページの右端が中心線より右となるよう、切り取り範囲を指定すべきである。
  • 左右の端に見える小口の部分まで含めてスキャンされている場合、左端・右端の小口のある辺りは捨てるように切り取るべきかもしれない。

-終わり-

#固定で構わない (はずの) 変数
## 解像度
DENSITY = 144
## 作業用一時ディレクトリ
TEMPORARYDIR = tmp
## 作業用一時ファイルの名前 (の一部)
EXTRACTED = extracted.pdf
EACHPAGEPREFIX = each_page
HALFPAGEPREFIX = half
COVER = cover
DEFTITLESTRING = deftmptitlestring.tex
DEFSETTINGSSTRING = defsettingsstring.tex
# 毎回見直すべき変数
## 基本設定
ORIGPDF = kokuyaku_kanbun_vol_06.pdf
STARTPAGE = 140
ENDPAGE = 158
VOLUME = 99
## 巻の途中で本の位置をずらしてスキャンしたと思しき場合、
## CROPRIGTHT と CROPLEFT を変えてそれぞれの部分について make する
## 必要があり、そういう例外的な場合に FILENAMESUFFIX = _a などと
## 設定して、後で
## $ qpdf --empty --pages vol_111_a.pdf vol_111_b.pdf -- vol_111.pdf
## のように結合する。通常は FILENAMESUFFIX には何も設定しなくてよい。
FILENAMESUFFIX =
RESULTANTPDF = vol_$(shell printf "%03d" $(VOLUME))$(FILENAMESUFFIX).pdf
## pdfimages した結果の JPEG 画像は 2339 × 1607 ピクセル。
## 2339/2 = 1169.5
##
## 切り出し範囲指定の仕方は色々あるが、わかりやすいのは以下の二つ。
## [scale-x]%x[scale-y]%+[offset-x]+[offset-y]
## [width]x[height]+[offset-x]+[offset-y]
## 左右のページで、幅と高さを揃えておくとよい。
##
## 右ページ切り出し範囲指定の例
## 50%x100%+1170+0
## 870x1160+1180+240
CROPRIGTHT = 870x1160+1180+240
## 左ページ切り出し範囲指定の例
## 50%x100%+0+0
## 870x1160+360+240
CROPLEFT = 870x1160+330+240
## 最初の見開きの右ページを捨てるか (1: 捨てる, 0: 残す)
DISCARDFIRSTRIGHTPAGE = 1
## 最後の見開きの左ページを捨てるか (1: 捨てる, 0: 残す)
DISCARDLASTLEFTPAGE = 0
\documentclass[10pt,a5paper]{article}
%置き場所は README.md を参照。
\usepackage{xeCJK}
\setmainfont{Optima}
\setCJKmainfont{HiraMinProN-W3}
\XeTeXlinebreaklocale "ja"
\XeTeXlinebreakskip=0em plus 4em minus 0.01em
\XeTeXlinebreakpenalty=0
\def\<{\@ifstar{\zx@hwback\nobreak}{\zx@hwback\relax}}
\def\zx@hwback#1{\leavevmode#1\hskip-.5em\relax}
\usepackage{setspace}
\usepackage[left=1cm,right=1cm,top=3cm,bottom=2.3cm]{geometry}
\input{deftmptitlestring.tex}
\title{\vspace{-2cm}\tmptitlestring}
\author{}
\usepackage[yyyymmdd]{datetime}
\renewcommand{\dateseparator}{-}
\date{\vspace{-1cm}\today}
\begin{document}
\maketitle
\setmainfont{CourierNewPSMT}
\renewcommand{\arraystretch}{1.5}
\begin{table}[htbp]
\begin{center}
\begin{tabular}{|r|l|}
\hline
\multicolumn{2}{|c|}{オプション設定値一覧}\\
\hline
\input{defsettingsstring.tex}
\hline
\end{tabular}
\end{center}
\end{table}
\end{document}
# 毎回見直すべき変数
include config_for_individual_run.mk
#ここからは固定で構わない (はずの) 変数
include common_config.mk
all: $(RESULTANTPDF)
# 所望のページを抜き出し、JPEG画像に変換
$(TEMPORARYDIR)/$(EACHPAGEPREFIX)*.jpg: $(ORIGPDF)
pdfimages -f $(STARTPAGE) -l $(ENDPAGE) -j $(ORIGPDF) $(TEMPORARYDIR)/$(EACHPAGEPREFIX)
# ls で名前順にソートしたときに右ページが左ページより前に来るよう、
# 右ページのファイル名の接尾辞を _0 とし、
# 左ページのファイル名の接尾辞を _1 としている。
# 右ページを切り出す
$(TEMPORARYDIR)/$(HALFPAGEPREFIX)_*_0.jpg: $(TEMPORARYDIR)/$(EACHPAGEPREFIX)*.jpg
cd $(TEMPORARYDIR); (ls -1 $(EACHPAGEPREFIX)* | xargs -I {} convert -density $(DENSITY) -colorspace Gray {} -crop $(CROPRIGTHT) +repage $(HALFPAGEPREFIX)_{}_0.jpg)
# 必要に応じて最初の見開きの右ページを捨てる
ifeq ($(DISCARDFIRSTRIGHTPAGE), 1)
rm -f $$(ls -1 $(TEMPORARYDIR)/$(HALFPAGEPREFIX)*0.jpg | head -n 1)
endif
# 左ページを切り出す
$(TEMPORARYDIR)/$(HALFPAGEPREFIX)_*_1.jpg: $(TEMPORARYDIR)/$(EACHPAGEPREFIX)*.jpg
cd $(TEMPORARYDIR); (ls -1 $(EACHPAGEPREFIX)* | xargs -I {} convert -density $(DENSITY) -colorspace Gray {} -crop $(CROPLEFT) +repage $(HALFPAGEPREFIX)_{}_1.jpg)
# 必要に応じて最後の見開きの左ページを捨てる
ifeq ($(DISCARDLASTLEFTPAGE), 1)
rm -f $$(ls -1r $(TEMPORARYDIR)/$(HALFPAGEPREFIX)*1.jpg | head -n 1)
endif
# JPEG から PDF に変換
$(TEMPORARYDIR)/$(HALFPAGEPREFIX)*.pdf: $(TEMPORARYDIR)/$(HALFPAGEPREFIX)_*_0.jpg $(TEMPORARYDIR)/$(HALFPAGEPREFIX)_*_1.jpg
ls -1 $(TEMPORARYDIR)/$(HALFPAGEPREFIX)*.jpg | sed s/.jpg$$// | xargs -I {} convert {}.jpg {}.pdf
# 表紙を作る
$(TEMPORARYDIR)/$(DEFTITLESTRING):
echo "\\\\newcommand{\\\\tmptitlestring}{『資治通鑑』巻$(VOLUME)}" > $(TEMPORARYDIR)/$(DEFTITLESTRING)
$(TEMPORARYDIR)/$(DEFSETTINGSSTRING):
echo "ORIGPDF & $(ORIGPDF)\\\\\\\\" | sed s/_/\\\\_/g > $(TEMPORARYDIR)/$(DEFSETTINGSSTRING)
echo "STARTPAGE & $(STARTPAGE)\\\\\\\\" >> $(TEMPORARYDIR)/$(DEFSETTINGSSTRING)
echo "ENDPAGE & $(ENDPAGE)\\\\\\\\" >> $(TEMPORARYDIR)/$(DEFSETTINGSSTRING)
echo "RESULTANTPDF & $(RESULTANTPDF)\\\\\\\\" | sed s/_/\\\\_/g >> $(TEMPORARYDIR)/$(DEFSETTINGSSTRING)
echo "DENSITY & $(DENSITY)\\\\\\\\" >> $(TEMPORARYDIR)/$(DEFSETTINGSSTRING)
echo "CROPRIGTHT & $(CROPRIGTHT)\\\\\\\\" | sed s/%/\\\\%/g >> $(TEMPORARYDIR)/$(DEFSETTINGSSTRING)
echo "CROPLEFT & $(CROPLEFT)\\\\\\\\" | sed s/%/\\\\%/g >> $(TEMPORARYDIR)/$(DEFSETTINGSSTRING)
echo "DISCARDFIRSTRIGHTPAGE & $(DISCARDFIRSTRIGHTPAGE)\\\\\\\\" >> $(TEMPORARYDIR)/$(DEFSETTINGSSTRING)
echo "DISCARDLASTLEFTPAGE & $(DISCARDLASTLEFTPAGE)\\\\\\\\" >> $(TEMPORARYDIR)/$(DEFSETTINGSSTRING)
$(TEMPORARYDIR)/$(COVER).pdf: $(TEMPORARYDIR)/$(COVER).tex $(TEMPORARYDIR)/$(DEFTITLESTRING) $(TEMPORARYDIR)/$(DEFSETTINGSSTRING)
cd $(TEMPORARYDIR); xelatex $(COVER).tex
# 仕上げ
$(RESULTANTPDF): $(TEMPORARYDIR)/$(COVER).pdf $(TEMPORARYDIR)/$(HALFPAGEPREFIX)*.pdf
qpdf --empty --pages $(TEMPORARYDIR)/$(COVER).pdf $$(ls -1 $(TEMPORARYDIR)/$(HALFPAGEPREFIX)*.pdf | tr "\n" " ") -- $(RESULTANTPDF)
.PHONY: clean
clean:
rm -f $(TEMPORARYDIR)/$(EXTRACTED)
rm -f $(TEMPORARYDIR)/$(EACHPAGEPREFIX)*
rm -f $(TEMPORARYDIR)/$(HALFPAGEPREFIX)*
rm -f $(TEMPORARYDIR)/$(DEFTITLESTRING)
rm -f $(TEMPORARYDIR)/$(DEFSETTINGSSTRING)
rm -f $(TEMPORARYDIR)/*.aux $(TEMPORARYDIR)/*.log $(TEMPORARYDIR)/*.pdf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment