paged.jsで作るPagedMediaな帳票文書




帳票、契約書などのビジネス文書をhtmlでプレビューして印刷するまでのロードマップ。

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>

スクリーンに帳票を組んだ結果


印刷プレビュー
スクリーンをそのまま再現できている




シートから溢れた要素を適宜改ページしてくれる


表の改ページ精度問題


シートから溢れた文字列とかブロック要素は問題なく改ページしてくれるが、TableGridは下図のようにちょん切れたり(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>


Share:

0 Comments:

コメントを投稿