マニュアル一覧PDFで読む

Reports.web Rust WASM言語ラッパー プログラマーズガイド/APIリファレンス

Reports.webのWASM言語ラッパーは、Rustの業務データから、共通の印刷データPREPEJを組み立てるための小さなライブラリです。帳票の座標計算やPDF描画を各言語へ重複実装せず、ブラウザまたはサーバーの共通WASM帳票エンジンへ任せます。

この一冊では、初めて利用する方が次のプログラムを作れるようになることを目標にしています。

WASM言語ラッパーの全体構成業務アプリが言語別ラッパーでPREPEJを作り、ブラウザまたはサーバーの共通WASM帳票エンジンへ渡す流れいつもの言語でデータを作り、描画は共通WASMへ。薄いラッパーは描画処理を持たず、持ち運べる印刷データ PREPEJ を組み立てます。YOUR APPLICATION業務アプリDB・API・業務ロジックWASM言語ラッパーPrintData API座標・PDF描画は持たないPREPEJ印刷データJSON・保存・HTTP・メール添付ONE ENGINE, TWO ROUTES共通WASM帳票エンジンブラウザ内で生成SVGプレビュー・PDF・印刷サーバーで生成PDF・定期処理・配信組み立てる渡すPHP · Java · TypeScript / Node.js · C# / .NET · Rust · Go · Ruby ─ 同じPREPEJで同じ結果へ

1. 最初に知っておくことRM001

1.1 「WASM言語ラッパー」とはRM002

本書では、各言語でPREPEJを組み立てる補助ライブラリをWASM言語ラッパーと呼びます。実装の中心はPrintDataです。ラッパー自身はWASMを直接ロードせず、SVGやPDFも描きません。出力形式を共通JSONに揃えることで、業務処理と描画エンジンを疎結合にします。

これはPure Java/Pure Python/WPF・.NETの言語別エンジンとは異なる方式です。Pure系は各言語の帳票エンジンを直接呼びます。WASM方式は7言語が同じWASMエンジンとブラウザUIを共有します。

1.2 帳票ができるまでRM003

  1. デザイナーで帳票定義 .prepdj を作ります。
  2. RustのPrintDataへPREPDJを設定します。
  3. DBやAPIから取得した値を、デザイン上のオブジェクト名へ設定します。
  4. 1ページずつ確定し、.prepejを作ります。
  5. PREPEJをブラウザのプレビュアー、またはサーバーWASMへ渡します。
  6. 共通エンジンがSVGプレビュー、PDF、印刷を担当します。

1.3 二つのファイルRM004

拡張子 呼び名 役割
.prepdj 帳票定義 用紙、文字、罫線、画像、バーコード、繰返しなどのデザイン
.prepej 印刷データ(Print Document) 帳票定義とページごとの値・属性をまとめた、再表示・再印刷可能な文書

PREPEJは単なる一時レスポンスではありません。ファイル保存、再読込、メール添付、REST API、サーバー間転送、後日の再印刷に同じ文書を使えます。

2. サンプルを動かすRM005

基準環境は Rust stable です。同梱のDocker構成では必要な言語環境とDBをコンテナ内に用意するため、まずPC側で準備するのはDockerです。Windows/MacではDocker Desktopを起動し、LinuxではDocker EngineとComposeプラグインが使える状態にしてください。

ZIPを全体ごと展開してください。共通RuntimeとDB初期化データも使うため、番号フォルダーだけを取り出さないでください。初回のイメージ取得とビルドにはインターネット接続が必要です。

短いコードから学ぶ場合は WebAssembly/Samples/rust/01.あっという間に帳票出力/README.html から始めます。ここでは全帳票を確認できる02の起動を説明します。

  1. VS CodeやCursor、または使い慣れたエディターで WebAssembly/Samples/rust/02.全帳票をWebでプレビュー を開きます。
  2. そのフォルダーでターミナルを開き、次を実行します。開発用のReports Webリポジトリをビルドする必要はありません。
docker compose up -d --build --wait
docker compose ps
  1. 起動が成功してから、ブラウザで http://127.0.0.1:19241/ を開きます。URLを開くだけではアプリは起動しません。
  2. 終了するときは、同じフォルダーで docker compose down を実行します。このサンプルのコンテナとネットワークが削除されます。DBデータの初期化やイメージの片付けは、そのフォルダーのREADMEに手順があります。

02では、単純帳票、10の倍数、郵便番号一覧、見積書、請求書、商品一覧、名刺、デザイン機能見本を確認できます。請求書などを選び、ソースと出力結果を見比べてください。別のアプリから帳票APIを呼ぶ使い方は、続く03のREADMEに説明しています。

3. 最初の一枚を作るRM006

01のsrc/main.rsにあるcreate_print_data()を、次の関数へ置き換えてください。先頭のuseとWebサーバーの処理は残します。PrintDataはreport-data/src/lib.rsに同梱され、Cargo.tomlのパス依存で参照済みです。

fn create_print_data() -> anyhow::Result<String> {
    let definition: serde_json::Value = serde_json::from_str(
        &std::fs::read_to_string("definition/quick-report.prepdj")?)?;
    let mut print = PrintData::new();
    print.set_definition(&definition)?
        .page_start()?
        .set_value("Title", "はじめての帳票")?
        .set_value("CustomerName", "パオ商事株式会社")?
        .page_end()?;
    print.to_json(true)
}

01のターミナルでdocker compose up -d --build --wait --wait-timeout 180を実行し、成功後にhttp://127.0.0.1:19240/を再読み込みします。見出しと会社名が変わったらPDFも保存します。01は起動時に印刷データを作るため、ソース変更後は再ビルド・再起動が必要です。

TitleとCustomerNameは01の帳票定義にある動的文字の名前です。Rustの変数名ではありません。自分の帳票では、デザイナーで付けた名前を指定します。?はエラーを呼び出し元へ返すRustの記法で、成功を無条件に仮定するものではありません。

呼出順は 定義を設定 → ページを開始 → 値を設定 → ページを確定 → JSONを取得 です。未完了のページがある状態ではPREPEJを取得できません。この制約により、途中までの帳票を誤って配信することを防ぎます。

4. PrintData APIリファレンスRM007

配布ZIP内の実装ファイルは WebAssembly/Samples/rust/02.全帳票をWebでプレビュー/src/lib.rs です。各番号のサンプルにも必要なソースを置いています。次のAPI名は実装に合わせています。

API 用途
set_definition 帳票定義(PREPDJ)を設定します。設定値は複製され、呼び出し元の変更から守られます。
page_start / page_start_with 新しい印刷ページを開始します。別の定義を渡すと、表紙から明細などへ様式を切り替えられます。
set_value / set_value_at 名前で指定した動的オブジェクトへ値を設定します。indexは0から始まる繰返し位置です。
set_repeated_value 縦横二方向に繰り返すオブジェクトへ、X・Y位置を指定して値を設定します。
set_typed_value DynamicText、DynamicImage、DynamicBarcodeなど、値の種類を明示して設定します。
set_image 画像URLまたはdata URIを動的画像へ設定します。
set_barcode バーコード/二次元コードへ渡す文字列を設定します。
draw_object 値を変更せず、指定オブジェクトを描画対象へ加えます。
change_attributes 位置、幅、色、フォント、配置などをページ単位で動的に変更します。
change_repeated_attributes 繰返し位置を含めて動的属性を変更します。
page_end 現在のページを確定し、印刷データへ追加します。
to_value / to_json 完成したPREPEJをオブジェクトまたはJSON文字列として取得します。
save 完成したPREPEJを一時ファイル経由で安全に保存します。

4.1 値とインデックスRM008

以下は引数の順序です。Valueはserde_json::Value、Pathはstd::path::Pathです。&mut selfは省略しています。

PrintData::new() -> PrintData
set_definition(definition: &Value)
page_start()
page_start_with(definition: Option<&Value>)
set_value(name: &str, value: impl Into<Value>)
set_value_at(name: &str, value: impl Into<Value>, index: usize, drawing: bool)
set_repeated_value(name: &str, value: impl Into<Value>,
                   x: usize, y: usize, drawing: bool)
set_typed_value(name: &str, kind: &str, value: impl Into<Value>,
                index: usize, drawing: bool)
set_image(name: &str, source: impl Into<Value>)
set_barcode(name: &str, code: impl Into<Value>)
draw_object(name: &str, index: usize, kind: &str)
change_attributes(name: &str, values: Value, index: usize)
change_repeated_attributes(name: &str, values: Value, index: usize,
                           repeat: Option<(usize, usize)>, kind: &str)
page_end()
to_value() -> anyhow::Result<Value>
to_json(pretty: bool) -> anyhow::Result<String>
save(path: &Path) -> anyhow::Result<()>

set_definitionからpage_endまではanyhow::Result<&mut Self>を返します。?でエラーを扱いながら続けて呼び出せます。set_value、set_image、set_barcodeはindex=0・drawing=trueです。繰返し位置や描画可否を指定するときはset_value_atやset_typed_valueを使います。page_start_with(Some(&definition))はそのページだけの定義指定です。

to_valueは完成データのコピーを返し、to_json(false)は整形用の空白を省きます。save(Path::new("output.prepej"))はサーバー上のファイル保存で、ブラウザーのダウンロードとは別です。名前・値・ページ開始終了の順序が不正ならErrになり、保存時はI/Oエラーも返ります。HTTP要求ごとに新しいPrintDataを作り、作成途中のインスタンスを共有しないでください。

値には文字列、数値、真偽値、nullの単純値を渡します。配列や任意オブジェクトを値として渡さず、帳票オブジェクトごとに明示的に対応付けてください。繰返しのindex、indexX、indexYは0から始まり、負数はエラーです。

drawing=falseは、その繰返し位置を描画しない場合に使います。設定は値の一覧へ追加され、以前の設定を削除する操作ではありません。一か所への値は一度に決めて渡します。動的属性はJSONオブジェクトで渡し、その値にはnullを使えません。

4.2 ページごとに定義を切り替えるRM009

表紙と本文、途中から異なる一覧形式などでは、ページ開始時に別のPREPDJを渡します。座標をプログラムへ直接書くのではなく、デザイナーで完成させた定義をページへ結び付けます。定義を変更する前に現在のページを必ず確定してください。

5. 明細と改ページRM010

以下は自作帳票への組込み例です。definitionには「ページ」「品番」「品名」「金額」を持つ定義を、detailsにはcode・name・amountを持つ明細列を用意します。01の定義へそのまま貼り付ける抜粋ではありません。

let mut print = PrintData::new();
print.set_definition(&definition)?;
for (page, rows) in details.chunks(20).enumerate() {
    print.page_start()?.set_value("ページ", page + 1)?;
    for (row, d) in rows.iter().enumerate() {
        print.set_value_at("品番", d.code.clone(), row, true)?
             .set_value_at("品名", d.name.clone(), row, true)?
             .set_value_at("金額", d.amount, row, true)?;
    }
    print.page_end()?;
}

1ページの行数は帳票定義と業務仕様から決めます。最終ページの空行を描画するか、明細ごとに罫線を出すかもプログラム側で明示します。ページ番号はPREPEJのページ順と、帳票へ表示する業務上のページ番号を分けて考えます。

5.1 空文字で罫線・図形を消すRM011

罫線・四角・円(Line、DynamicLine、Square/Rectangle、Circle/Ellipse)は、値で表示を切り替えられます。

書く値 罫線・図形
""(空文字) 描かない(消える)
Value::Null、または何も書かない 定義どおり描く
""以外の値(例 "1") 描く。値は表示には使わない

繰返しの行でも、1つだけの図形でも同じです。最初に書いたときも、別の値のあとに書いたときも同じです。表の行を途中で終える、合計行の上だけ線を引かない、条件で枠を消す、などに使います。次の明細線は、定義にある罫線のオブジェクト名の例です。

print.set_value_at("明細線", "", row, true)?; // この行の罫線を消す

drawing=falseも描きません。PREPEJでは、図形の値が""ならDrawingがtrueでも描きません。

文字の欄(Text)は別の規則です。値が""またはValue::Nullなら、文字も枠線も背景色も描きません。SVG・印刷・PDFで同じです。

5.2 何も書かない繰返しは1行目だけ描くRM012

繰返し(RepeatCountが2以上)のある物体に、そのページで何も書かないと、定義どおり1つだけ、1行目の位置に描きます。行の数だけ並べては描きません。

一部の行にだけ書くと、書いた行だけ描きます。書かなかった行は描きません。1行目に書かなければ、1行目も描きません。

表の罫線や枠を全部の行に描くには、描きたい行それぞれにValue::Null(値なし)を書きます。罫線・図形はValue::Nullなら描き、""なら消えます(5.1)。次の1行を上の明細のループに足すと、明細の行数ぶん明細線にValue::Nullを書きます。

print.set_value_at("明細線", Value::Null, row, true)?; // この行の罫線を描く

文字の欄は、Value::Nullを書くとその行の文字が空になります。定義の文字を出したい行には、その文字を書きます。PREPEJへ保存した印刷データでも同じです。

6. 画像・バーコード・動的属性RM013

値を書いていない文字・四角には、同じページで前に値を書いた物体の最後の行と同じ行の属性だけが当たります。値なしの罫線・楕円・画像・バーコードの旧形式の属性は当たりません。

ページ開始後のprint、画像のdata URIを持つstamp_data_uri、対応する「角印」「商品QR」「合計金額」を定義に用意した場合の抜粋です。

use serde_json::json;
print.set_image("角印", stamp_data_uri.clone())?
    .set_barcode("商品QR", "https://example.jp/items/123")?
    .change_attributes("合計金額", json!({
        "bold": true, "fontSize": 16,
        "horizontalAlignment": "Right"
    }), 0)?;

画像は閲覧先でも取得できる管理下のURL、または許容サイズのdata URIにします。サーバーPDF生成では、任意の外部URLをそのまま取得するとSSRFにつながります。固定画像は許可リストまたはアプリ管理下のファイルから解決してください。

動的属性では、名前・型・値を無検証のリクエストから直接渡さないでください。変更できる属性をアプリ側で限定し、数値範囲、色、配置、フォント名を検証します。

7. ブラウザへ渡して表示するRM014

Rust APIはPREPEJをapplication/json; charset=utf-8で返します。ブラウザ側は共通のReports Web APIへ渡します。

<iframe id="reportsViewer" title="帳票プレビュー"
        style="width:100%;height:75vh;border:0"></iframe>
<script>
const viewer = document.querySelector('#reportsViewer');
viewer.addEventListener('load', async () => {
  const response = await fetch('/print-data');
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  await viewer.contentWindow.ReportsWeb.setPrintData(await response.json());
});
viewer.src = '/reports.web/preview/index.html?empty=1';
</script>

上は01のURLを使った抜粋です。実際の受信・エラー表示・PDF保存は01のindex.htmlで確認できます。自作アプリでは配信先に合わせてURLを変更します。

親画面とプレビュアーは同一オリジンに置くのが最も簡単です。別オリジンでpostMessageを使う場合は、送信元と送信先のoriginを固定し、*を本番環境で使わないでください。

プレビュアー側ではsetZoom("auto")、ページ移動、検索、印刷、exportPdf()、savePrintData()を利用できます。独自画面へ表示面だけを組み込む場合はhideAll()でツール群を隠し、同じ機能を外側のボタンから呼び出します。

8. PDFを作る二つの場所RM015

8.1 ブラウザで作るRM016

PREPEJを利用者のブラウザへ渡し、ブラウザ内のWASMがPDFを生成します。サーバーのPDF描画負荷を抑え、プレビューからそのまま保存・印刷する操作に向きます。機密データをブラウザへ渡してよいかは、業務の認可設計で判断します。

8.2 サーバーで作るRM017

Axumなどから共通Node/WASMサービスへHTTPでPREPEJを送ります。 定期処理、メール添付、保存、監査、画面を開かない帳票生成に向きます。

POST /render/pdf
Content-Type: application/json

<PREPEJ JSON>

成功時はapplication/pdfを返します。失敗時は次の状態コードと、理由を書いたJSON({"error":"…"})を返します。

状態 意味 対処
400 入力の誤り。JSONが壊れている、数値が有限の数でない、幅・高さなどが負、定義の数値を文字列で書いた、ルートのVersionが無い・"1.0"のような文字列でない、など errorの理由を見て、PREPEJを直します
413 大きすぎる要求。64MiBを超える、または64MiB以内でもサーバーのメモリーで処理できる量を超える ページを分けて送るか、Node.jsのメモリー上限(--max-old-space-size)を増やします
408 要求の本文が30秒以内に届かなかった。届かない本文で、後ろに並んだ要求を止めないためです 送信側の接続を確かめて再実行します。時間は環境変数REPORTS_ENGINE_BODY_TIMEOUT_MS(ミリ秒)で変えられます
503 混雑。要求は本文の受け取りから PDF の生成まで1件ずつ順番に処理し、処理の開始まで10秒を超えて待った要求を断ります。処理が5分を超えた要求も断り、次の要求へ進みます しばらく待って再実行します。待ち時間は環境変数REPORTS_ENGINE_QUEUE_WAIT_MS、処理の時間の上限はREPORTS_ENGINE_OPERATION_TIMEOUT_MS(どちらもミリ秒)で変えられます

408・413・503のときもサーバーは止まらず、次の要求を処理します。業務アプリ側でも処理時間を制限し、失敗時の途中PDFを保存・配信しないでください。ブラウザ生成とサーバー生成は同じPREPEJを入力にするため、業務データ組立てを二重実装する必要はありません。

9. PREPEJを保存・再利用するRM018

saveは完成したPREPEJをUTF-8 JSONとして保存します。保存前にページをすべて確定してください。ファイル名には.prepejを使い、アップロード時には拡張子だけで信用せず、Format、Version、Definition、Pages、サイズ、ページ数を検証します。

保存した文書は、Webプレビュアーの「開く」またはドラッグ&ドロップで再表示できます。HTTPで受け渡す場合もファイルと同じJSONです。再印刷の証跡として保管する場合は、業務データの保持期間、暗号化、アクセス権、削除規則を定めます。

10. Web APIとデータベースRM019

DB列と帳票オブジェクト名はコードで明示的に対応付けます。SQLはプレースホルダーを使い、利用者が参照できる伝票かを取得前に確認します。日付、通貨、端数処理は帳票へ渡す前に業務規則で確定します。

機密帳票のレスポンスにはCache-Control: no-storeを設定します。内部例外、接続文字列、ファイルパスを利用者へ返さず、要求IDを安全なサーバーログへ残します。帳票定義や画像は可能なら公開ルート外へ置きます。

11. テストとリリース確認RM020

Dockerのみを準備した方は、同梱READMEに沿って表示・編集・PDF保存・PREPEJ再読込を確認します。PCにもRustとCargoを用意した方は、02のフォルダーでcargo test --libを実行すると同梱ラッパーの単体試験を実行できます。開発リポジトリ固有のパスを指定する必要はありません。

最低限、次を確認します。

ラッパー変更時はWASM描画エンジン全体を毎回再試験するのではなく、生成PREPEJが共通仕様と基準サンプルに一致することを中心に確認します。WASM、プレビュアー、フォント、画像処理を変更した場合は、別途描画回帰試験が必要です。

12. よくある問題RM021

症状 確認すること
帳票が空になる PREPDJ上のオブジェクト名とsetValueの名前、indexを確認します。
2ページ目がない ページごとにpage start/page endが対になっているか確認します。
画像が×になる URLの到達性、data URI、CORS、サーバー側許可リストを確認します。
日本語が欠ける 配置したフォント、フォントマップ、WASMとWeb資源の版を確認します。
WASMが読み込めない .wasmのURL、application/wasm、キャッシュ、同一リリースのJSを確認します。
PDFだけ失敗する バーコードの値、入力サイズ、画像サイズ、タイムアウト、サーバーログの要求IDを確認します。表せない値のバーコードがあると、PDFは対象のバーコードと値を示すエラーで止まります(プレビューでは枠に赤で「バーコードエラー」と表示します)。
サーバーPDFが400・413・503になる 応答JSONのerrorを見ます。400は入力の誤り、413は大きすぎる要求、503は混雑です。第8.2節の表を参照してください。

13. 配置・更新・ライセンスRM022

ラッパー、プレビュアー、JavaScript/TypeScript、CSS、WASMは同じReports.web配布版から取り出してください。HTMLだけ、またはラッパーだけを別バージョンへ差し替えると、PREPEJや公開APIの互換性を判断できなくなります。

現行の製品版への切替は、購入者のメールアドレスを含む電子署名付きの reports-web.license を、実際に帳票を出力する側へ配置して行います。購入者ごとにWASMを差し替える手順ではありません。ブラウザーとサーバーでは配置先が異なるため、製品版を使い始めるに従ってください。ライセンスファイルを公開サンプルやソース管理へ混入させないでください。正式な動作環境、使用許諾、更新履歴、問い合わせ先は共通製品マニュアルを正本とします。開発ライセンスは開発用PC 1台につき1ライセンスです。