LoginSignup
40
62

LaTeX と Python で作る 1 ポイントたりとも表示崩れしない最強の帳票印刷ソリューション

Posted at

元ネタ

元ネタはこちらです(以下「Figma と PHP」で略します)。

読んでなるほどと思いました。このように、誰かが苦労したおかげで後続が楽になるので感謝です。何が問題点として生じるのか、どんな解決方法が考えられるのか、が予め判明しているだけでもだいぶ楽になります。
反面、$\LaTeX$ の方が実装は簡易ではないかと思ったので、それを実践してみました。

条件と問題点

Figma と PHP の 44 ページに以下のような条件があります

  • 改めて、満たしたい条件
  • ミリ単位で細かく帳票をデザインしたい。
  • 帳票デザインの保守性を維持するためにはビジュアルデザイン必須
  • 印刷時に見た目が一切崩れない

さらに、次のような障害を次々とクリアしていってます。

  1. 文字参照の問題
  2. 枠からのはみ出しの問題
  3. 右寄せの問題
  4. 自動折り返しの問題
  5. 連票の問題(簡易な解説のみ)

これらを $\LaTeX$ と Python でも対処します。

アイデアの骨子

既存の様式 PDF に文字(列)をスーパーインポーズ(オーバーレイ)するだけ

  1. 様式 PDF と上書き用テンプレート tex ファイル (overlay.tex) を用意する。
    様式 PDF は Figma や Word などで作ってもよく、すべてを LaTeX に依存しなくても良い。
  2. overlay.tex 側で様式 PDF を読み込む
  3. overlay.tex には動的に値を入れたいところは %_顧客名_% とかプレースホルダーを書いておく
  4. Python のスクリプトでプレースホルダーを実データで置換する
  5. 置換された overlay.tex をタイプセットして PDF にする

なおこの方法は、予めフォーマットが印刷してある帳票用紙に文字等を印字していく場合でも同じです。つまり白紙からすべて印刷するのではなく、印刷時点でスーパーインポーズする方法。その場合、デザインが終わったら様式 PDF を読み込まないようにします。

Figma と PHP の条件に応じる

  • ポイント単位1で細かく帳票をデザインできる
    600DPI の印刷機を前提にすると 0.12 pt/ 1 dot なので小数第2位までの精度で解像度は充分とする(1 mm = 2.83 pt)
  • $\LaTeX$ は WYSIWYG ではないので条件成就は不可能。なのであきらめる
    が、極力ヴィジュアルデザインに寄せて位置決めできるように工夫する
  • 印刷時の見た目が一切崩れないかどうかは、印刷機の性能に依存する。(結果的には成果物の PDF をプリントするだけだから)

帳票作成のための概要はこちらをご覧ください。

実践

既存のフォーマットはこちらからとってきました。2 1文字ずつの位置決めがめんどくさそうだったからです。

Python スクリプト

以下のスクリプトを使います。帳票の種類ごとにスクリプトを分けることになりそうですが、汎用的な一つのスクリプトで無理して対応するよりはメンテナンスが楽そうです。

write.py
#!/usr/bin/env python
# -*- encoding: UTF-8 -*-

def main():
    with open('overlay.tex', 'r') as f: # (1)
        overlay = f.read()

    data = {
            '番号[0]': 0,
            '番号[1]': 1,
            '番号[2]': 2,
            '番号[3]': 3,
            '番号[4]': 4,
            '番号[5]': 5,
            '番号[6]': 6,
            '番号[7]': 7,
            '番号[8]': 8,
            '番号[9]': 9,
            '発生状況': '寿限無 寿限無 五却のすりきれ 海砂利水魚の 水行末 雲来末 風来末 食う寝る処に住む処 藪ら柑子の藪柑子 パイポパイポ パイポのシューリンガン シューリンガンのグーリンダイ グーリンダイのポンポコピーのポンポコナーの 長久命の 長助',
            '年月日': '1234567',
            'NUM数値1': 123,
            'NUM数値2': 123456,
            'NUM数値3': 123456789,
            '職名': '徳川次郎三郎源朝臣家康',
            } # (2)

    for key, value in data.items():
        placeholder = "%_" + key + "_%" # (3)
        if 'NUM' in key: # (4)
            value = f"{value:,}"
        if key in '職名氏名': # (5)
            width = 77 / len(value)
            if width >= 11:
                FONTSIZE = 11
            else:
                FONTSIZE = width
            SKIP = FONTSIZE * 1.2
            overlay = overlay.replace('%_FONTSIZE_%', str(FONTSIZE))
            overlay = overlay.replace('%_SKIP_%', str(SKIP))
        overlay = overlay.replace(placeholder, str(value)) # (6)

    with open('sample.tex', 'w') as f: # (7)
         f.write(overlay)

if __name__ == "__main__":
    main()
  • 手順は (1) テンプレートを読み込む、(2) (SQLなどの)データを dict の形に変換する、(3) プレースホルダーを決める、(6)プレースホルダーを実データで置換する、(7) タイプセッタ用ファイルに書き出す、です。
  • write.py overlay.tex データ、すべて UTF8 で作れば、文字参照の問題は生じません。 (2)(3) のようにプレースホルダーに和文が使われていても問題ありません。これで、文字参照の問題は解決です。
  • (4) 数値の 3 桁区切りは、Python 側にて変換します。LaTeX 側でやるより簡便です。
  • (5) 枠からのはみ出し問題に対処します。ここでは 11 ポイントのフォントサイズを基準にして 7 文字を超えた場合に、フォントサイズを線形に小さくする計算をしています。
  • 実行します。python write.py && uplatex sample && dvipdfmx sample

TeX ファイル

位置決め補助のための PDF 作成

overlay.tex
\documentclass[uplatex,dvipdfmx,
head_space=1pt,foot_space=0pt,gutter=0pt,fore-edge=0pt, % (1)
jafontsize=11pt,
paper=a4]{jlreq} % (2)
\usepackage[deluxe,expert,uplatex,jis2004]{otf} % (3)
\usepackage{color}
\usepackage{bxpapersize} % (4)
\usepackage{jlreq-trimmarks} % (5)
\jlreqtrimmarkssetup{trimmarks_width={1pt}, color=my, banner={}} % (5)
\usepackage[abs]{overpic} %(6)
\usepackage{anyfontsize} % (7)

\makeatletter
\def\kintou#1#2{\leavevmode\hbox to #1{\@tfor\temp:=#2\do{\temp\hfil}\unskip}} %% 和文・欧文の均等
\makeatother

\setlength{\parindent}{0pt} % (8)
\pagestyle{empty} % (9)

\begin{document}
\begin{overpic}[grid]{001094540.pdf} % (10)
\sffamily\gtfamily
% (11)
\end{overpic}
\end{document}

この $\TeX$ ファイルをタイプセット(uplatex overlay && dvipdfmx overlay)すると次のような PDF が出力されます。グリッドラインとトンボが入り、WYSIWIG じゃないハンディキャップを少しは補えるはずです。

  • (1) 理屈上は上下左右のマージンをゼロにすべきなのですが、なぜか上のマージン位置がずれてしまったので 1pt 追加してます。3(原因わかりません)
  • (2)(3) 日本語を扱うとき定番の jlreq と otf です。 shebang と同じく呪文だと思っておきましょう。
  • (4) 用紙サイズをよしなに設定してくれます。LaTeX の「アレなデフォルト」 傾向と対策 を参照されたし
  • (5) トンボが入り、用紙が拡大します
  • (6) このパッケージが肝です。PDF を読み込みます。[abs] オプションは座標を絶対位置で処理します。詳しくは overpic – Combine LATEX commands over included graphics を参照されたし
  • (7) フォントサイズを絶対値で変更するパッケージです
  • (8) 帳票は文章じゃないのでインデントをゼロにします
  • (9) 単票なのでノンブルもなし
  • (10) ここで様式 PDF を読み込んで、[grid] オプションで グリッドラインを表示します
    A4 サイズが W596pt × H842pt なので細かなグリッドになってます。4
    ここをただの picture 環境にすれば5スーパーインポーズする文字だけの成果物になります。
  • (11) ここにスーパーインポーズする命令を追加していきます

スーパーインポーズの命令(プレースホルダの指定)

overlay.tex
\documentclass[uplatex,dvipdfmx,
head_space=1pt,foot_space=0pt,gutter=0pt,fore-edge=0pt, % (1)
jafontsize=11pt,
paper=a4]{jlreq} % (2)
\usepackage[deluxe,expert,uplatex,jis2004]{otf} % (3)
\usepackage{color}
\usepackage{bxpapersize} % (4)
\usepackage{jlreq-trimmarks} % (5)
\jlreqtrimmarkssetup{trimmarks_width={1pt}, color=my, banner={}} % (5)
\usepackage[abs]{overpic} %(6)
\usepackage{anyfontsize} % (7)

\makeatletter
\def\kintou#1#2{\leavevmode\hbox to #1{\@tfor\temp:=#2\do{\temp\hfil}\unskip}} %% 和文・欧文の均等
\makeatother

\setlength{\parindent}{0pt} % (8)
\pagestyle{empty} % (9)

\begin{document}
\begin{overpic}[grid]{001094540.pdf} % (10)
\sffamily\gtfamily
\color{red}\ebseries

%%% 労働保険番号
\put(61,722){\huge% % (12)
%_番号[0]_%%
}
\put(78,722){\huge%
%_番号[1]_%%
}
\put(95,722){\huge%
%_番号[2]_%%
}
\put(112,722){\huge%
%_番号[3]_%%
}
\put(129,722){\huge%
%_番号[4]_%%
}
\put(146,722){\huge%
%_番号[5]_%%
}
\put(163,722){\huge%
%_番号[6]_%%
}
\put(180,722){\huge%
%_番号[7]_%%
}
\put(197,722){\huge%
%_番号[8]_%%
}
\put(214.5,722){\huge% % (13)
%_番号[9]_%%
}
\put(231.5,722){\huge%
%_番号[0]_%%
}
\put(248.5,722){\huge%
%_番号[1]_%%
}
\put(265.5,722){\huge%
%_番号[2]_%%
}
\put(283,722){\huge%
%_番号[3]_%%
}

\mdseries
%%% 労働者の住所
\put(45,496){\parbox[t]{495pt}{\large\rmfamily\mcfamily% % (14)
%_発生状況_%%
}}
%%% 受付年月日
\put(423.5,759){\large\kintou{109pt}{% % (15)
%_年月日_%%
}}

%%% 受付年月日
\put(360,570){\makebox[0pt][r]{\large% % (16)
%_NUM数値1_%%
}}
\put(360,555){\makebox[0pt][r]{\large% % (16)
%_NUM数値2_%%
}}
\put(360,537){\makebox[0pt][r]{\large% % (16)
%_NUM数値3_%%
}}
\put(466,555){\fontsize{%  % (17)
%_FONTSIZE_%%
}{%
%_SKIP_%%
}\selectfont%
%_職名_%%
}
\put(466,537){% % (17)
%_職名_%%
}
\end{overpic}
\end{document}

python write.py && uplatex sample && dvipdfmx sampleを実行するとこうなります。

  • 左はグリッドあり(10)、トンボあり(5)
  • 中央はグリッドなし、トンボなしの成果物
  • 右はフォーマットもなし(印刷時にフォーマット済みの帳票用紙に記載することを想定)
  • (12) 座標 (61, 722) にフォントサイズ huge でテキスト %_NUM[0]_% を書けという意味です。
  • (13) 小数点座標 (214.5, 722) も問題なし。数桁の小数でも問題ありませんが、せいぜい小数点以下 2 桁が現実的でしょう。
  • (14) 座標 (45, 496) に幅 495pt の parbox を書けという意味です。parbox はその幅の中で自動的に文字列を折り返します。フォントサイズ large の明朝体でテキスト %_発生状況_% を書き、場合によっては折り返せ(改行)という意味です。これで、自動折り返しの問題は解決です。
  • (15) 1文字づつ枠内に配置せずに、均等割つけを利用して位置決めしてみました。6
  • (16) 右寄せの方法です。座標(360,*)の位置に 0pt 幅のボックス7を作り、その中に右寄せでテキスト %_NUM数値*_% を書けという意味です。360pt のグリッドラインにピッタリ沿っているのを確認してみてください。これで右寄せの問題は解決です。
  • (17) 枠内に収まるようにフォントサイズを調整する部分です8。大きさ %_FONTSIZE_% で行送りが %_SKIP_% のフォントに変更せよという意味です。職名欄で対応し、氏名欄は比較用です。 これで、枠からのはみ出しの問題は解決9です。

連票(はペンディング)

  • (勝手に使ってよさそうな)ベースにする様式 PDF が見つからなかったからです
  • ゼロから $\LaTeX$ で作る方が簡単ですが、予めフォーマット済みの帳票用紙に配置することを想定したいからです
  • 技術的に困難だからではないです(位置決めはちょっと苦労するかも)
  1. ミリ単位で作成してもポイントに変換され、変換誤差も生じるので、ポイントのまま

  2. ただし PDF は Encrypted: yes (print:yes copy:no change:no addNotes:yes algorithm:AES) になってるので適宜解除してください。
    そのままだと、タイプセット時にワーニングが出ます
    extractbb:warning: PDF document is encrypted.
    extractbb:warning: 001094540.pdf does not look like a PDF file...

  3. 位置決めそのものには影響しません。overpic は、ベース画像 (PDF) の座標を使い用紙の座標ではないからです。予めフォーマットされた帳票用紙に印字する際に影響があります。

  4. X軸の番号が重なってしまいます。角度をつける(90°とか60°とか)オプションはないようです。

  5. \begin{picture}{596,842)〜\end{picture}

  6. 等幅フォントでないとうまくいかないはずです。和欧混植だともっとうまくいかないはずです。

  7. makebox の前後に文字列が続かないので幅のないもので差し支えないのです。

  8. scalebox とどちらが簡便だろうか?フォントサイズ基準?縮小率基準?

  9. 本当は汎用性がないので、解決です。フォントサイズ、フォントファミリー、フォントシリーズによって、文字列の幅が変化します。なので、$\LaTeX$側で処理するのが本筋だと考えてます。が、マクロを組まないと……

40
62
0

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
40
62