🙌

pdfmeの動的テーブルの実装アイデア

2024/01/02に公開

pdfmeで動的テーブルが実装できなかった理由

pdfmeはこれまでテンプレートのbasePdfというプロパティにPDFのデータを埋め込み、そのPDFの上にスキーマを重ねる方法で簡易的なバリブル印刷を実現していた。

この方法は非常にシンプルで、簡単にカスタマイズしたPDFを生成することができた。

しかし、PDFを作成したいユースケースとして無視できないニーズがある。

それはテーブルのレンダリングだ。もっと具体的にいうと請求書など、下記の画像の赤枠部分のように行数が増えるケースに対応したいユーザーは多いだろう。

上記で説明したbasePdfにスキーマを重ねる方法だと、ページブレイクがうまく実装できない。

なので現状のpdfmeは動的に行が増えるようなテーブルに対応できなかった。

私はこのニーズを理解しているが、具体的にどのように実装すればいいか悩んでいた。

とにかくこれがTable実装のブロッカーだったんだ。

ページブレイクを実装する方法

かなり前からユーザーと議論をしていたがようやくページブレイクが実装できる可能性が出てきた。キーとなる2つの要素が揃った。

  • V3.2.0 で追加された ReadOnly Schemas
    • Line, Rectangle, Ellipse, SVG, Text, Image
  • V4 向けに実装された BlankPDFというbasePdfの新しい指定方法とパディング

ReadOnly SchemasによってbasePdfにデザインされたPDFを読み込まなくても、pdfme内でテンプレートの静的な部分をデザインできる。それはテーブルなどページに高さが追加された場合に、高さが追加されたスキーマよりも下にある全てのスキーマのy座標を高さが増加した分、追加することが可能にする。またパディングによってページブレイクした際にどこから続きをレンダリングすればいいかなどもコントロールすることができる。

下記の図ではテーブルに19行のrowデータを与えて、ページブレイクし、noteというスキーマが押し下げられている例。

自分がイメージしていたロジックは、

  • テーブルをヘッダー、Row0→Row19まで一行ずつレンダリングしていく。
  • 高さが増加してRow12行目で1ページ目をはみ出るため、ページブレイクし2ページ目を作成する。
  • Row19行目までレンダリングしたらgenerator関数に最終的なテーブルの高さを返却し、generator関数はの増加分をyAdjustのような変数に保持しておく。(上記の図では190mm)
  • 高さが増加したスキーマのy座標+heightよりもy座標が下にあるスキーマ(この例ではnote)にも高さの増加分190mmをy座標に追加する。1ページ目をはみ出て2ページ目にいき、y座標140mmにレンダリングされる。

問題点としてはヘッダーやフッターなどの固定したいレイアウトに対応できない。その場合はbasePdfをBlankPDFのwidth, heightのサイズ指定ではなく、ヘッダーやフッターの固定デザインを含むPDFを指定できるようにすればいいだろう。これは追加で修正する予定だ。

テーブルの設定、レンダリングの複雑性にどうやって立ち向かうか

テーブルの設定、レンダリングは複雑になりかねない。どのようなアプローチで立ち向かっていくか。

pdfmeの良さであるシンプルに、簡単にPDFを作成できるというコンセプトを生かしてテーブル機能を実現したいと思っている。

例えば下記のテーブルを例にどのように設定していくかを考えてみよう。

テーブルというのは基本的にヘッダー(th)とテーブルデータ(td)で構成されている。

テーブルデータはテキスト以外でレンダリングされる可能性があり、この例ではNameとCategoryはTextレンダラー、ImageはImageレンダラーでレンダリングされている。

幸いにもpdfmeにはTextだけでなく、Image, SVG, Barcodesなどさまざまなレンダラーをすでに実装している。つまり、各セルのレンダリング処理を新たに作成する必要はなく、セルのサイズと位置が決まればすでにあるPDFのレンダリング処理を呼び出すことでレンダリングできる。

これはpdfmeがプラグインアーキテクチャでレンダリング処理を書いている特徴をうまく活かしており、複雑なことをシンプルに解決できる可能性がある。

このアイデアは動的テーブルのIssue上でMP70おもしろいコメントを残してくれて思いついた。

上記を考えると、テーブルは下記の3つを行えば良いと考えている。

1. テーブルのスタイリング

下記の画像はQRコードの設定パネルだが、赤枠部分に下記のスタイルを設定をできるようにすればいい。

stripe,grid,plainなど基本的なスタイルを実現できるようにする。

  • ボーダー
    • color, width
  • セルのパディング(コンテンツマージン)
    • horizontal, vertical
  • セルの背景色
    • ABデザインのように奇数と偶数でstripeを実現できるようにする予定
  • ヘッダーの表示 / 非表示

2. ヘッダーとカラムの設定

ヘッダーにカラムを追加して、どのような設定でレンダリングさせるかを設定する。

  • 各カラムはReadOnlyTextでレンダリングされる
    • 既存のTextレンダラーで設定可能な文字色・背景色などのオプションが利用可能
  • Typeを指定してテーブルデータのレンダリングの設定
    • ImageやBarcodesなどpdfmeで使えるレンダラーをtypeとして設定して利用可能
  • 上記の画像の歯車ボタンをクリックするとモーダルが開き、カラムとTypeの設定を行える

3. テーブルのレイアウトの計算

スタイルとカラムのレンダリングの設定が決まればあとはレイアウトの計算を行い、セルをレンダリングする。

  • セルの位置の計算とレンダリング
    • 基本的に各カラムと同じx座標でレンダリングするが、y座標は一つ上のRowのy座標と高さを利用する。位置が決まればカラムの設定を使ってセルのレンダリングを行う。
  • Rowとセルの高さの計算
    • ページブレイクで説明したが、増加した高さはgenerator関数に伝える必要がある

    • 問題点として現状のTextレンダラーはDynamicフォントサイズ機能を利用しない場合、下記の画像のようにレンダリングした後に設定した高さをはみ出る可能性がある。

      • 本当は下記の画像のようにRowの高さを広げる必要がある。これを実現するためにTextレンダラーはレンダリングした後にレンダリングした後の高さを返却する必要がある。

      • セル単位のレンダリング結果の高さからRowの高さが決定し、各Rowの高さを合計し、テーブルの高さが決まる。

フィードバックを貰えると嬉しい

まだテーブルの実装は始まっていない。今はまだこのドキュメントに書かれているアイデアだけだ。

このアイデアに考慮漏れや、問題はたくさんあると思う。

OSSである以上、多くの人の目に触れて、フィードバックを欲しいし、たくさんのコラボレーションが起きることを期待している。もし問題がありそうならぜひ教えて欲しい。

動的テーブルのIssueは下記だ。気軽にコメントして欲しい。

https://github.com/pdfme/pdfme/issues/332

そして、最後までみてくれてありがとう。

もしこのプロジェクトを応援してくれるなら、この記事をシェアしていただくのが一番簡単な貢献です。

これからもpdfmeをよろしくお願いします。

Discussion