ogochan
作りかけとは言え、Markdownエディタとしては使えるようになったので、既に実業務のドキュメントを作ることに使っています。
前回の記事のように、いい感じにリアルタイムプレビューと同期ができるようになっていて、とりあえず書くことはできます。
10数ページのドキュメントをとりあえず書き上げてPDFに変換して顧客に送ろうと思ったところで、いろいろ悩みは始まります。
サーバアプリにとっての印刷
今時のサーバアプリでの印刷は、
- サーバでPDFを生成してダウンロード
- 印刷用ページを用意してブラウザから印刷
のどちらかで実装されることが多いですね。
これがローカルのアプリだと、直接印刷ドライバを動かすようになると思いますが、サーバアプリの場合はそもそも「どんなプリンタがいるか」ということを知らないことが多いので、ポータブルな印刷形式を使って、後はローカルでよしなにしろということになっています。
ちなみに、会計システムHieronymusの場合はこのどちらでもなく、「印刷の代わりにExcelシートを出力する」ようにしています。紙が欲しければ印刷すればいいし、電子で保存したければExcelのままだと便利ですからね。まぁそれでも印刷自体をサーバがしてないという点では同じようなものです。
Markdownの印刷
Markdownを印刷するのはちょっと厄介です。
まず、Markdownは前提として
HTMLに変換される
ためのものです。
MarkdownはHTMLとは直接関係ない形式ですから、頑張ればTeXなりDocBookなりに変換して... ということが可能なはずですが、ほとんどのMarkdown処理系(レンダラ)はHTMLを出力するためのものなので、「MarkdownはHTMLのためのもの」と言っても過言ではないでしょう。
また、現在使っているMarkdownの処理系はmarkdown-itです。これには膨大な種類のプラグインがあって、Markdownを拡張しています。なので、印刷する時のレンダラもmarkdown-itを使う方が間違いがありません。
つまり、サーバでPDFを生成するためには、
Markdown → HTML → PDF
という変換が必要になります。
HTMLの印刷
HTMLをPDFに変換する方法は、事実上「ブラウザを使う」以外の方法がありません。HTMLの進化に忠実に追従して来たレンダラはブラウザのエンジンしかないためです。なので、サーバ側でヘッドレスのブラウザを動かしてやって、これでPDFで印刷というようにするわけです。
これがMarkdown以外のもの、たとえば一般の帳票印刷のようなものであれば、直接PDFを出力するためのライブラリはいろいろありますから、それを使えば問題ありません。HTML to PDFの処理だけがブラウザを動かすという大掛りなものになるということです。
それが大掛りで嫌だなと思えば、「印刷用ページ」を出力して、ローカルのブラウザで印刷してやれば同じことです。イメージとして「画面のハードコピー」を取っているような気持ちになって、ちょっとどうなのよとか思わないでもないですが、やっていることは前の方法と同じで、場所が異なるだけのことです。
そんなわけで、どちらの方法を使うにせよ、「印刷に使うHTML」を生成する必要があります。
「印刷に使うHTML」と言っても、HTML自体が異なるわけではありません。違うのはスタイルシートです。
このスタイルシートは印刷されるデバイスに合わせた設定が必要になります。たとえば、
- 背景色は白にする
ディスプレイの場合は薄い色が着いていることが多い - 紙の大きさに合わせる
メディアクエリとか使いますね - パディングやマージンを印刷物らしくする
余白少な過ぎるとダサいですね - テーブルの罫線の調整が必要かも知れません
backgroundだけでカラム分割とかしてると印刷物として見辛いかも - 余分なものは非表示にしなければなりません
UIのためのもの(メニューとか)は印刷物にはいりません - 目立たせるための装飾も変えないといけないかも知れません
リンクに色をつけたものをそのまま印刷するとハードコピー臭くてダサい
などなど、いろんな調整が必要になります。この辺は適当にググるといろんな情報が出て来ると思います。
落とし穴
今までの話はごく一般的なことで、単なる「マエセツ」に過ぎません。
これで準備できたので印刷かけようとしたら、変な現象が出ました。
何かと言えば、
縦方向の余白が正しく出るのは最初の1ページだけで、残りのページは余白0で印刷(PDF化)される
ということです。
印刷用のCSSでは、
@media print{ @page { size: A4 portrait; margin: 25mm; }
のようにして紙のサイズを指定します。この時、marginを0にすると、Chromeの時には印刷の時にデフォルトでつくヘッダやフッタはつかなくなるようです。
これをこのまま印刷すると、A4の紙いっぱいに印字されてしまい、とてもみっともないので「ヘッダやフッタはブラウザで調整することにする」ということにして、この例では25mm空けることにしています。
ところが、こうしてやってもさっきの現象は起こります。
印刷をする場合、印字する領域は1つのboxとして作られ、そこにいろいろ指定してやることで、余白や改ページを制御できます。今使っているのだと、
@media print{ @page { size: A4 portrait; margin: 25mm; } .content-wrapper { -webkit-print-color-adjust: exact; background: #FFFFFF !important; } .print { width: 210mm; height: 297mm; box-sizing: border-box; padding: 0mm; page-break-after: always; } p, h3, .print:last-child { page-break-after: auto; } }
のようになっています。この例のうちクラス名そのものには意味がありませんが、「.print」と名前をつけた印字エリアを指定したボックスパラメータに従って印刷してくれるというだけです。イメージ的には@pageの内側に.printが出来るという感じです。
この辺まではいろんな解説を読めば、もっと詳しく正しい解説があると思いますので、それ自体はそちらでどうぞ。
世間ではこれで上手く行くはずだということになっていて、それ以上の言及がありません。でも、うちでは最初の現象が出たままです。
印字するべきものを動的(AJAX的)に作っているのがいけないのかと思ってstaticに生成する機能もつけてみたのですが、やっぱり同じ。
いろいろテストしていたのですが、実際に「印刷」してみると綺麗に出ます。ますます意味がわからない。
解決
ふと思い立って、「MS PDFで印刷したらどうなるんだろう」と試してみたら、なんと思った通りのPDFになってました。
つまり何であったかと言うと、それまでは印刷する時に「PDFに保存」を選んでいたんですね。
「PDFに保存」というのは、どうも動作を見ていると
指定されたウェブページを要求されたページサイズ(たとえばA4)にして、そのまま保存する
もののようです。つまり、長いままで保存して意図的なページブレークはしてくれない、ただページサイズが「紙」の大きさになっているので、それに従ってページ分割しているだけなのです。
他方「なんとかPDFドライバを使って印刷する」というのは、
指定されたウェブページを要求れたページサイズ(たとえばA4)に分割してPDFのページにし、紙束のようなPDFにする
ことです。
何が違うかと言えば、「ページ分割の処理をするかどうか」です。「余白」のための処理はこの「ページ分割の処理」の時のものなので、いくら紙の大きさを指定していても、「PDFに保存」ではページ分割の処理をするわけありません。そのため、2ページ目以降の余白がおかしなことになってしまうわけです。
わかってしまえばどうってことない話なんですが、今まで特に意識することもなく「PDFに残すなら保存でしょ」くらいの認識でいたのですが、それは大きな間違いであったということでした。だいじなことなのでもう一度書いておきますね。
「PDFに保存」と「PDFに印刷」は違うこと
なのです。