チュートリアル — 最初の1枚から本番リリースまで
このページでは、YAMLのテンプレートからPDFを1枚出すところから、テンプレートをDockerイメージに梱包して本番に載せるところまでを、順に進みます。コマンドは全部、CIで実際に実行されているものの転記です。
1. まず1枚出す(Docker)
イメージにはCLI、MCPサーバ、フォントとロケールのパック、そしてすべての例が入っています。
docker run --rm ghcr.io/kengos/shojiku:edge > receipt.pdfテンプレートをローカルに取り出して編集し、プレビューを見る手順はクイックスタートにあります。編集→検証→プレビューのループはそちらが本編です。
2. アプリに組み込む(公開レジストリのSDK)
5言語のパッケージが公開済みです。どのSDKも形は同じで、クライアントを作り、テンプレート名とパラメータを渡し、返ってきたバイト列を書き出します。エラーは例外ではなく、failure の種別とメッセージで返ります。
pip install shojikugem install shojikunpm install shojikudotnet add package Shojiku<!-- 本体 + 実行プラットフォームのclassifier(Netty/LWJGLと同じ流儀) -->
<dependency>
<groupId>jp.kengos</groupId>
<artifactId>shojiku</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>jp.kengos</groupId>
<artifactId>shojiku</artifactId>
<version>0.2.0</version>
<classifier>linux-x64</classifier>
</dependency>レンダリングは全言語で同じ3行です。Pythonではこう書きます。
import json, shojiku
client = shojiku.Client(
templates="templates/", font_dirs=["packs/fonts"], locale_dirs=["packs/locale"]
)
result = client.generate("receipt-ja", json.load(open("params.json")))
if not result.success:
raise SystemExit(f"{result.failure.kind} | {result.failure.message}")
open("out.pdf", "wb").write(result.artifact.bytes)フォントとロケールのパックは、テンプレートと同じくアプリに同梱してください。一式はリリースの tarball から取るのが手軽で、使わないロケールのパックは消して構いません。
wget https://github.com/kengos/shojiku/releases/download/v0.2.0/shojiku-0.2.0-packs.tar.gz
tar xzf shojiku-0.2.0-packs.tar.gz # packs/fonts と packs/locale が出てくる3. 自前の画像を使う
ロゴなど、自前の画像をテンプレートに入れたい場合です。画像ファイルは、テンプレートファイルの隣の assets/ に置きます。
templates/
receipt-ja/
templates.yml
assets/
logo.pngこのように配置すると、テンプレートから相対パスで参照するだけで描画されます。画像のバイト列を渡す手続きはありません。
- type: image
box: { w: 120, h: 40 }
src: assets/logo.pngパスの基準はテンプレートファイルのあるディレクトリで、CLIでは --assets-dir で変えられます。data: URIやインラインSVG、paramsから差し込む動的画像も使えます。正確な仕様はimage.mdにあります。
4. 同梱パック以外のフォントを使う
Designerのフォントピッカーで選んだ書体や、自社のコーポレートフォントを使いたい場合です。テンプレート側の指定は style の fontFamily です。
# templates.yml — コンテナに書けば、下の要素に継承されます
style: { fontFamily: my-corporate }このmy-corporateをエンジンに解決させるには、フォントパックを置き、それをロケールに登録します。
Designerで選んだ場合は、エクスポートキット(zip)にパック一式がライセンスファイルごと入っています。packs/fonts/ に展開すれば、パックの用意は終わりです。
手元にTTFがある場合は、コマンド1つでパックができます。
shojiku font add MyCorporate-Regular.ttf \
--family my-corporate --license Proprietary
# 同じファミリーにボールドを足す
shojiku font add MyCorporate-Bold.ttf \
--family my-corporate --license Proprietary --weight boldpacks/fonts/my-corporate/ を作り、フォントファイルをコピーし、その sha256 を書き込んだ manifest.yml を生成します。
# packs/fonts/my-corporate/manifest.yml
version: 1
license: Proprietary
faces:
- id: my-corporate
file: MyCorporate-Regular.ttf
sha256: <自動で計算されます>読み込み時には sha256 と埋め込み権利(fsType)が検証されますが、font add も同じ2つを先に確認します。埋め込みが許可されていないフォントは、最初のレンダリング時ではなくこの時点で弾かれます。別途埋め込みライセンスを持っている場合は --embedding-attested で明示します。パックは既定では再配布不可で、--redistributable を渡したときだけ再配布可になります。
どちらの場合も、最後にどのパックをロードするかをレンダリングに伝えます。1回のレンダリングならフラグで済みます。
shojiku render --templates templates.yml --params params.json \
--output out.pdf --font-pack my-corporate毎回のレンダリングで使うデプロイなら、ロケールの uses に入れます。オーバーレイファイルを1枚置くだけです。
# packs/locale/ja-jp.yml(ビルトインja-JPへのオーバーレイ)
fonts:
uses: [biz-ud, ipamj-mincho, noto-sans-mono, my-corporate]uses は追記ではなく全体の書き直しなので、同梱パックを並べたまま自分のパックを足します。1回だけなら --font-pack のほうが短いのは、こちらが置き換えではなく追加だからです。ロケールが uses しておらず、実行時にも指定していないパックのフォントを fontFamily に指定すると、unknown_font_family の警告が出て、ロケール既定のフォントにフォールバックします。
パックの探し方はCLIもSDKも同じで、明示指定、環境変数、カレントの ./packs/fonts と ./packs/locale のすべてから探します。同じidがぶつかったときは、明示指定が優先されます。SDKでの明示指定はクライアントのオプションです(セクション2のPythonの例で渡していた font_dirs / locale_dirs がそれです)。環境変数は SHOJIKU_FONT_DIR / SHOJIKU_LOCALE_DIR(PATH区切り)、CLIのフラグは --font-dir / --locale-dir です。次のセクションのDockerfileは packs/ を丸ごとCOPYするので、自作パックも一緒に含まれます。正確な仕様(url: による自動フェッチ、フォールバックチェーンなど)はfonts.mdにあります。
5. 本番に載せる(Dockerfileレシピ)
テンプレートができたら、アプリとテンプレートとパックを1つのイメージに焼き込みます。以下は5言語ぶんの実レシピで、make proof-deploy が公開レジストリに対して実際にビルド&レンダリングして検証しているファイルそのものです。
# Production shape: your app + its template + the packs, rendered by the
# shojiku wheel from PyPI. The params come out of a SQLite database built
# into the image (seed.py) — swap that for your real database connection.
#
# docker build -t receipt-renderer .
# docker run --rm receipt-renderer > receipt.pdf
FROM python:3.12-slim-bookworm
RUN pip install --no-cache-dir shojiku
WORKDIR /app
# Vendor the template set and the font/locale packs your documents use
# (packs/ comes from the Shojiku repository; trim it to your locale's packs).
COPY templates/ templates/
COPY packs/ packs/
COPY params-base.json seed.py render.py ./
RUN python seed.py
CMD ["python", "render.py"]# Production shape: your app + its template + the packs, rendered by the
# platform gem from RubyGems.
#
# docker build -t receipt-renderer .
# docker run --rm receipt-renderer > receipt.pdf
FROM ruby:3.3-slim-bookworm
RUN gem install -N shojiku
WORKDIR /app
COPY templates/ templates/
COPY packs/ packs/
COPY render.rb ./
CMD ["ruby", "render.rb"]# Production shape: your app + its template + the packs, rendered by the
# shojiku npm package (napi addon resolved per platform).
#
# docker build -t receipt-renderer .
# docker run --rm receipt-renderer > receipt.pdf
FROM node:22-bookworm-slim
WORKDIR /app
RUN npm init -y >/dev/null && npm install --no-audit --no-fund shojiku
COPY templates/ templates/
COPY packs/ packs/
COPY render.mjs ./
CMD ["node", "render.mjs"]# Production shape: publish a trimmed console app with the Shojiku NuGet
# package (native engine per RID under runtimes/), then run it on the
# runtime-only image.
#
# docker build -t receipt-renderer .
# docker run --rm receipt-renderer > receipt.pdf
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY Renderer.csproj Program.cs ./
RUN dotnet publish -c Release -o /out
FROM mcr.microsoft.com/dotnet/runtime:10.0
WORKDIR /app
COPY --from=build /out ./
COPY templates/ templates/
COPY packs/ packs/
CMD ["dotnet", "Renderer.dll"]# Production shape: a Maven build with the Shojiku jar + the PLATFORM
# CLASSIFIER jar (the one deliberate line — Maven never resolves a
# classifier on its own; pick the classifier of the image you run on).
#
# docker build -t receipt-renderer .
# docker run --rm receipt-renderer > receipt.pdf
FROM maven:3-eclipse-temurin-21 AS build
WORKDIR /src
COPY pom.xml .
COPY src/ src/
RUN mvn -q package dependency:copy-dependencies -DoutputDirectory=/deps
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /src/target/renderer-1.jar app.jar
COPY --from=build /deps/ deps/
COPY templates/ templates/
COPY packs/ packs/
CMD ["java", "-cp", "app.jar:deps/*", "Render"]Pythonのレシピは一歩進めて、paramsをイメージ内のSQLiteから引いています。静的な事実(発行者ブロックやQR)はテンプレート側のparamsに置き、取引の行だけをDBから合成する形です。
"""The production shape: static document facts (issuer block, QR) stay in
the vendored base params; the transactional half (header + lines + totals)
comes out of SQLite. Writes the PDF to stdout."""
import json
import sqlite3
import sys
import shojiku
con = sqlite3.connect("params.db")
head = con.execute("select number, issued_at, recipient, purpose from receipt").fetchone()
lines = con.execute("select name, quantity, unit_price from line").fetchall()
params = json.load(open("params-base.json"))
items = [
{"name": n, "quantity": q, "unit_price": p, "amount": q * p} for (n, q, p) in lines
]
total_ex = sum(i["amount"] for i in items)
tax = total_ex // 10
params.update(
{
"receipt": {"number": head[0], "issued_at": head[1]},
"recipient": {"name": head[2]},
"purpose": head[3],
"items": items,
"amount": {"total_in_tax": total_ex + tax, "total_ex_tax": total_ex, "tax": tax},
}
)
client = shojiku.Client(
templates="templates/", font_dirs=["packs/fonts"], locale_dirs=["packs/locale"]
)
result = client.generate("receipt-ja", params)
if not result.success:
raise SystemExit(f"render failed: {result.failure.kind} | {result.failure.message}")
sys.stdout.buffer.write(result.artifact.bytes)6. 署名して、検証する
配る前に署名し、受け取った側が検証します。どちらもネットワークを使いません。パスフレーズを引数で渡すフラグは意図的にありません(argv は他プロセスから読めるため)。
shojiku sign --input out.pdf --key signer.pem --cert signer.crt --output signed.pdf
shojiku verify --input signed.pdf --anchor signer.crtverify は署名が実際に覆うバイト範囲を含むJSONレポートを出力し、文書が検証できなければ非ゼロで終了します。信頼するcertは --anchor で毎回明示します。マシンの証明書ストアは参照しません。