帳票、契約書などのビジネス文書をページ化メディアで印刷するまでのロードマップ。
Paged Media
Paged MediaとはCSS3のページ化メディア= 版物の用紙サイズ、ふち、改ページなどのスタイルを指定するためのど真ん中の仕様セット。
ページ化メディア
とかページ組版
とかPaged Media
とか、いかにも専門チックな用語が登場するが、ページ概念のあるスタイルセットという理解でOK。
しかしながら、いま現在もメジャーブラウザの対応状況はまちまちだそうで、ブラウザ互換性という意味で安定していないようだ。
ライブプレビュー
それでもPagedMedia
で印刷をスタイリング出来るのは結構だが、(印刷ダイアログではなく)スクリーンでも精度の高いライブプレビューを再現したいというもの。
残念ながらPagedMedia
はスクリーンのそれとは異なるため、スクリーンのスタイルは別途作り込む必要がある。
単ページの帳票なら印刷ダイアログっぽく用紙サイズのページ要素のコンテナを作ってCSSオンリーでスタイリング出来るが、
可変ページ概念が出てくるとページ要素の増減、ページを横断する段落
/テーブル
要素の分割&リビルドとか、画像
は改ページして、、気の遠くなる調整実装が待っている。
paged.js
そこで、上記の気の遠くなるページ化メディア調整をしてくれるpaged.jsというオープンソースのポリフィルがある。
スクリーンでPagedMediaオリエントにDOMを再構築しれるので、スクリーンを印刷に合わせるのではなくて、印刷にスクリーンを再現させることができる。
使用するPaged Media仕様は@page
ディレクティブの用紙サイズsize
/マージンmargin
/ふち印字@bottom-center
/改ページ制御break-inside
あたり
実装
スクリプト
次のCDNスクリプトを読み込むと自動的にpaged.jsのポリフィルが走る。
Getting Started with Paged.js —
<script src="https://unpkg.com/pagedjs/dist/paged.polyfill.js"></script>
2024.3.18現在 CDNで公開中のステーブルバージョンv0.4.3
/**
* @license Paged.js v0.4.3 | MIT | https://gitlab.coko.foundation/pagedjs/pagedjs
*/
文書を組む
paged.jsは@media print
クエリを展開する
paged.jsはPagedMediaを完全擬態しようとするので@media print
クエリを展開する。
逆に@media screen
クエリはデフォルトで無視するため、スクリーンにのみ適用したいスタイルはlink
タグやstyle
タグにmedia=“screen”
指定することで適用してくれる。
<!-- linkはmedia=“screen”を入れる -->
<link rel="stylesheet" type="text/css" href="screen.css" media="screen">
<!-- styleタグにmedia="screen" -->
<style media="screen">
p {
color: red;
}
</style>
じつは上記仕様に至るに破壊的な変更があり、v0.4.0以前はスクリーン向けには本来の@media screen
クエリを展開していたが、スクリーンも完全なPagedMediaオリエントへと変遷したようだ。
以前v0.2を利用していて、今回現最新版のv0.4.3を試したところ、プレビュー専用のスタイルが言うことを聞かないので頭を悩ませた。
バージョン 0.4.1 以降、Pages.js は
@media screen
のメディア クエリ内にある CSS の解析を停止します。
Paged Break, the long overdue update —
キャンバス
以下のスタイルを当てると用紙サイズのキャンバスができるので、あとは画面ノリで組むだけ。
<!-- スクリーンにのみあてるスタイル -->
<style>
@page{
/* A4・横向きでサイズ指定 */
size: A4 landscape;
/* ふちを20mmとる */
margin: 20mm;
/* 各ページのフッター中央に { 現在ページ } / { 総ページ } を印字 */
@bottom-center{
content: counter(page) '/' counter(pages);
}
}
</style>
<!-- スクリーンにのみあてるスタイル media="screen" -->
<style media="screen">
body{
/* 背景色を指定してプレビューぽくする */
background: #666;
}
/* ポリフィルがあてるシートとそのコンテナブラッシュアップ */
/* コンテナクラス は縦並びでセンタリングする*/
.pagedjs_pages {
display: flex;
justify-content: center;
flex-wrap: wrap;
width: calc(var(--pagedjs-width));
margin: 0 auto;
}
/* ポリフィルがあてるページシートクラス */
.pagedjs_page {
margin: 5mm;
border: 1px solid #333;
background: #fff;
}
</style>
スクリーンに帳票を組んだ結果
印刷プレビュー
スクリーンをそのまま再現できている
シートから溢れた要素を適宜改ページしてくれる
表の改ページ精度問題
シートから溢れた文字列とかブロック要素は問題なく改ページしてくれるが、Table
、Grid
は下図のようにちょん切れたり(widthもありえない数字)、不可視領域に吹っ飛んだり動作不安定でまともに使えず、公式フォーラムでもこの手のイシューがちらほらあるが猛者たちが調整で頑張っている。
Paged.JS and CSS Flex / Grid
Paged.js TABLE breaks · Issue #149 · pagedjs/pagedjs
Chrome: Missing row content on page break within table (v0.4.3 vs v0.5.0-beta.0) (#431) · イシュー · pagedjs / pagedjs · GitLab
表に関しては下記のようなFlexテーブルで代替する。それでもちょん切れる場合は行要素に改ページ禁止break-inside: avoid;
を当てるとうまくいく。
<style>
.flex-column{ display: flex; flex-flow: column;}
.flex-row{ display: flex; flex-flow: row;}
.flex-cell {
flex-grow: 1; padding: 2pt 6pt 2pt 6pt;
border: 1px solid #000;
}
/* 隣接するセル間のボーダーを重複させない */
.flex-row .flex-cell:not(:first-child) {
border-left: none;
}
.flex-row:not(:first-child) .flex-cell {
border-top: none;
}
.flex-row.head .flex-cell {
text-align: center;
}
.flex-cell:nth-of-type(1){flex-basis: 30%;}
.flex-cell:nth-of-type(2){flex-basis: 20%;}
.flex-cell:nth-of-type(3){flex-basis: 10%;}
.flex-cell:nth-of-type(4){flex-basis: 10%;}
.flex-cell:nth-of-type(5){flex-basis: 30%;}
/* 項目と表途中の改ページ禁止 */
.no-break{
break-inside: avoid;
}
</style>
<div class="flex-column">
<div class="flex-row head">
<div class="flex-cell">品名</div>
<div class="flex-cell">規格</div>
<div class="flex-cell">単価</div>
<div class="flex-cell">金額</div>
<div class="flex-cell">摘要</div>
</div>
<div class="flex-row">
<div class="flex-cell">1</div><div class="flex-cell">2</div><div class="flex-cell">3</div><div class="flex-cell">4</div><div class="flex-cell">5</div>
</div>
<div class="flex-row">
<div class="flex-cell">2</div><div class="flex-cell">2</div><div class="flex-cell">3</div><div class="flex-cell">4</div><div class="flex-cell">5</div>
</div>
<div class="flex-row">
<div class="flex-cell">3</div><div class="flex-cell">2</div><div class="flex-cell">3</div><div class="flex-cell">4</div><div class="flex-cell">5</div>
</div>
<div class="flex-row">
<div class="flex-cell">4</div><div class="flex-cell">2</div><div class="flex-cell">3</div><div class="flex-cell">4</div><div class="flex-cell">5</div>
</div>
<div class="flex-row">
<div class="flex-cell">5</div><div class="flex-cell">2</div><div class="flex-cell">3</div><div class="flex-cell">4</div><div class="flex-cell">5</div>
</div>
<div class="flex-row no-break">
<div class="flex-cell" style="flex-basis: 100%; height: 36mm;">
【備考欄】
</div>
</div>
</div>