Skip to content

チュートリアル — 最初の1枚から本番リリースまで

このページは、PDFを1枚出すところから、テンプレートをDockerイメージに梱包して本番に載せるところまでを一直線に進みます。コマンドは全部、CIで実際に実行されているものの転記です。

1. まず1枚出す(Docker)

イメージにはCLI・MCPサーバ・フォント/ロケールパック・全同梱例が入っています。

bash
docker run --rm ghcr.io/kengos/shojiku:edge > receipt.pdf

テンプレートを手元に取り出して編集し、プレビューを見る手順はクイックスタートにあります。編集→検証→プレビューのループはそちらが本編です。

2. アプリに組み込む(公開レジストリのSDK)

5言語のパッケージが公開済みです。どのSDKも同じ形をしています:クライアントを作り、テンプレート名とパラメータを渡し、返ってきたバイト列を書き出す。エラーは例外ではなく、failure の種別とメッセージで返ります。

bash
pip install shojiku
bash
gem install shojiku
bash
npm install shojiku
bash
dotnet add package Shojiku
xml
<!-- 本体 + 実行プラットフォームのclassifier(Netty/LWJGLと同じ流儀) -->
<dependency>
  <groupId>jp.kengos</groupId>
  <artifactId>shojiku</artifactId>
  <version>0.1.0</version>
</dependency>
<dependency>
  <groupId>jp.kengos</groupId>
  <artifactId>shojiku</artifactId>
  <version>0.1.0</version>
  <classifier>linux-x64</classifier>
</dependency>

レンダリングは全言語で同じ3行です。Pythonなら:

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)

フォントとロケールのパックはリポジトリの packs/ をアプリに同梱します。使うロケールのパックだけに絞って構いません。

3. ロゴ画像と自社フォントを足す

実務のテンプレートで最初に必要になるのは、この二つです。

画像はテンプレートの隣に置いて src で参照します。基準ディレクトリはテンプレートファイルのある場所(CLIなら --assets-dir で変更可)で、data: URIやインラインSVG、paramsから差し込む動的画像も使えます。正確な仕様はimage.mdへ。

yaml
- type: image
  box: { w: 120, h: 40 }
  src: assets/logo.svg

フォントはパックです。フォントファイルと manifest.yml(ライセンス1つ + 顔ごとのsha256)を packs/fonts/<id>/ に置き、ロケールのオーバーレイで uses に足します。読み込み時にsha256と埋め込み権利(fsType)が検証されます。正確な仕様はfonts.mdへ。

yaml
# packs/fonts/my-corporate/manifest.yml
version: 1
license: Proprietary
redistributable: false
faces:
  - id: my-corporate
    file: MyCorporate-Regular.ttf
    sha256: <sha256sum の出力>
yaml
# packs/locale/ja-jp.yml(ビルトインja-JPへのオーバーレイ)
fonts:
  uses: [biz-ud, ipamj-mincho, noto-sans-mono, my-corporate]

これで fontFamily: my-corporate が使えます。uses は全体を書き直す点(追記ではない)と、ロケールが uses していないパックの fontFamily は黙ってフォールバックする点に注意。下のDockerfileレシピは packs/ を丸ごとCOPYするので、自作パックも同じ行に乗ります。

4. 本番に載せる(Dockerfileレシピ)

テンプレートができたら、アプリ・テンプレート・パックを1つのイメージに焼き込みます。以下は5言語ぶんの実レシピで、make proof-deploy が公開レジストリに対して実際にビルド&レンダリングして検証しているファイルそのものです。

docker
# 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"]
docker
# 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"]
docker
# 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"]
docker
# 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"]
docker
# 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から合成する形です:

python
"""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)

5. 署名して、検証する

配る前に署名し、受け取った側が検証します。どちらもネットワークを使いません。パスフレーズを引数で渡すフラグは意図的にありません(argv は他プロセスから読めるため)。

bash
shojiku sign --input out.pdf --key signer.pem --cert signer.crt --output signed.pdf
shojiku verify --input signed.pdf --anchor signer.crt

verify は署名が実際に覆うバイト範囲を含むJSONレポートを出力し、文書が検証できなければ非ゼロで終了します。信頼するcertは --anchor で毎回明示します。マシンの証明書ストアは参照しません。

次へ