この記事は Qiita Advent Calendar 2025 Typst シリーズ Day 11 の記事です。
はじめに:私がTypstに出会うまで
私はかつて、スライドや書類の作成にPowerPointやAdobe Illustratorを使っていました。
LLMの登場とともに、AIにも内容が解釈可能な形式として reveal.js でHTMLスライドを作成するようになりました。テキストベースでGit管理ができ、AIとの協業も容易になりましたが、次第に課題を感じるようになりました:
- HTMLはファイルサイズが大きい
- 細かいデザインのコントロールに限界がある
そこで Typst に出会いました。
今年7月から本格的に使い始めて、半年で152ファイル を作成しました。講座で使うスライド資料、見積書、提案書、納品書、マニュアル...あらゆる書類をTypstで作るようになりました。
こんな見積書もTypstでつくるようになりました (後ほど具体的に紹介します)
Typstの魅力
テキストベースでGit管理
Typstファイルはプレーンテキストなので、Gitでバージョン管理できます。
AIとの協業
マークアップ言語なので、LLMが内容を理解しやすく、生成や修正を依頼できます。まずLLMに大まかに作ってもらい、細かいところを自分で修正していくというのが基本的なワークフローです。
細かいデザイン制御
私はCSSやTailwindcssの経験がありますが、typstの記法を抵抗なく受け入れることができ、細かいレイアウト調整もできるようになりました。
外部データの読み込み
Typstには yaml() や json() 関数が組み込まれており、外部ファイルからデータを読み込めます。
#let data = yaml("data.yaml")
#data.title // YAMLのデータにアクセス
これにより、データとレイアウトを分離 できます。かつてWordで「データの流し込み」をしていましたが、ずっと柔軟性を高くしたようなイメージです。
実例:日本の商習慣に合った見積書
Typstの実用例として、冒頭の見積書を紹介します。
見積書のファイルフォーマットに満足していますか
もしかして Excel方眼紙 に疲弊していませんか?
YAML + Typst なら、データはYAMLで管理し、typst compile 一発でPDFが生成されます!
日本の見積書に必要な要素
日本のビジネス文書には様々な留意するポイントがあります。Typstでこれらにどう対応するか見ていきましょう。
- 宛先表記: 宛名は自社より少し大きく、御中 をつけること
- T番号: 取引先によっては請求書だけではなく見積書にもT番号を求められます
- 消費税の外税表示: 小計・消費税・合計を明示
- 印鑑欄: 社名に少し重ねて角印を配置
- 備考欄: 有効期限、支払条件などの定型文
実装してみる
YAMLでデータ定義
まず見積データをYAMLで定義します。
# estimate-data.yaml
customer: "株式会社○○○○"
project_name: "社内ポータルサイト リニューアル開発"
issue_date: "2025年12月11日"
valid_until: "2025年12月31日"
estimate_number: "EST-2025-1211"
company:
name: "合同会社△△△△"
postal_code: "100-0001"
address1: "東京都千代田区千代田1-1-1"
address2: "サンプルビル 5階"
tel: "03-1234-5678"
registration_number: "T1234567890123" # インボイス登録番号
person_in_charge: "山田 太郎"
items:
- category: "要件定義・設計"
items:
- name: "要件定義"
unit_price: 800000
quantity: 1
unit: "式"
# ...
- category: "フロントエンド開発"
items:
- name: "トップページ"
unit_price: 180000
quantity: 1
unit: "式"
# ...
- category: "バックエンド開発"
items:
- name: "認証・認可基盤"
unit_price: 350000
quantity: 1
unit: "式"
# ...
- category: "プロジェクト管理"
items:
- name: "進行管理・定例会議運営"
unit_price: 400000
quantity: 1
unit: "式"
tax_rate: 0.10
remarks:
- "本見積書の有効期限は発行日より20日間です。"
- "お支払い条件: 着手時50%、検収完了後50%"
- "AWS等クラウド利用料は貴社アカウントでの直接お支払いとなります。"
- "対応ブラウザはGoogle Chrome / Microsoft Edge最新版(PCのみ)です。"
ポイント:
-
階層構造:
itemsの中にcategoryがあり、その中にさらにitemsがある入れ子構造 -
計算用データ:
unit_price、quantity、tax_rateは数値として定義 - 備考は配列: 複数の備考項目を管理しやすい
Typstでテンプレート作成
次にTypstのテンプレートを作成します。
// estimate.typ
// YAMLデータの読み込み
#let data = yaml("estimate-data.yaml")
// ユーティリティ関数: 数値をカンマ区切りでフォーマット
#let add_comma(num) = {
let s = str(int(num))
let len = s.len()
let result = ""
for i in range(len) {
if i > 0 and calc.rem(len - i, 3) == 0 {
result += ","
}
result += s.at(i)
}
result
}
// 合計金額計算関数
#let calculate_totals(items) = {
let subtotal = 0
for category in items {
for item in category.items {
subtotal += item.unit_price * item.quantity
}
}
let tax = int(subtotal * data.tax_rate)
let total = subtotal + tax
(subtotal: subtotal, tax: tax, total: total)
}
金額のフォーマット
add_comma() 関数で数値を見やすくフォーマットします。
#add_comma(1234567) // => "1,234,567"
Excelの書式設定のような機能をTypstで実装しています。
合計金額の計算
calculate_totals() 関数で、全カテゴリの全アイテムを走査して合計を計算します。
#let totals = calculate_totals(data.items)
// totals.subtotal => 小計
// totals.tax => 消費税
// totals.total => 合計
明細テーブルの動的生成
Typstの強力な機能として、ループでテーブル行を動的に生成できます。
#table(
columns: (3fr, 1fr, 1fr, 0.5fr, 1fr),
align: (left, right, right, center, right),
stroke: 0.5pt + rgb("666666"),
inset: 3pt,
// ヘッダー行
table.header(
table.cell(fill: rgb("333333"))[#align(center)[#text(fill: white, weight: "bold")[品目]]],
table.cell(fill: rgb("333333"))[#align(center)[#text(fill: white, weight: "bold")[単価]]],
table.cell(fill: rgb("333333"))[#align(center)[#text(fill: white, weight: "bold")[数量]]],
table.cell(fill: rgb("333333"))[#align(center)[#text(fill: white, weight: "bold")[単位]]],
table.cell(fill: rgb("333333"))[#align(center)[#text(fill: white, weight: "bold")[価格]]],
),
// データ行を動的に生成
..{
let rows = ()
for category in data.items {
// カテゴリ行(背景色付き)
rows.push(table.cell(colspan: 5, fill: rgb("e8e8e8"))[
#text(weight: "bold")[#category.category]
])
// アイテム行
for item in category.items {
let amount = item.unit_price * item.quantity
rows.push([#item.name])
rows.push([¥#add_comma(item.unit_price)])
rows.push([#str(item.quantity)])
rows.push([#item.unit])
rows.push([¥#add_comma(amount)])
}
}
rows
}
)
ポイント:
-
..{ }でスプレッド演算子を使い、配列を展開してテーブルに渡す -
table.cell(colspan: 5)でカテゴリ行を全列結合 -
fill: rgb("e8e8e8")で背景色を設定 - ヘッダーは濃い背景色
rgb("333333")に白文字
備考欄の生成
YAMLの配列をそのままループで展開します。
#text(size: 8pt, weight: "bold")[備考]
#box(
width: 100%,
stroke: 0.5pt + rgb("666666"),
inset: 5pt,
)[
#text(size: 8pt)[
#for remark in data.remarks [
・#remark \
]
]
]
印鑑の配置(社名に重ねる)
日本の商習慣では、角印(社印)を社名に少し重ねて押すのが一般的です。Typstの place と image を使って実現できます。
// 社名と印鑑画像を重ねて配置
#box[
#text(size: 1.2em, weight: "bold")[#data.company.name]
#place(dx: 8em, dy: -5em)[
#image("seal.png", height: 7em)
]
]
ポイント:
-
boxで社名を囲み、その中でplaceを使う -
dx: 8emで右方向、dy: -5emで上方向にオフセット - 印鑑画像のサイズは
height: 7emで調整 - 位置の微調整で社名に少し重なるように配置
印鑑画像がない場合は、circle で簡易的な印鑑を描画することもできます:
#place(dx: 8em, dy: -3em)[
#circle(radius: 1.5em, stroke: 1pt + rgb("cc0000"))[
#set align(center + horizon)
#text(fill: rgb("cc0000"), size: 6pt, weight: "bold")[社印]
]
]
コンパイルと出力
typst compile estimate.typ estimate.pdf
これだけでPDFが生成されます。
ファイル監視モード
開発中は --watch オプションが便利です。
typst watch estimate.typ
YAMLやTypstファイルを変更すると、自動的にPDFが再生成されます。
発展: 複数見積の管理
実際の運用では、顧客ごと・案件ごとに見積を管理することになります。
estimates/
├── template/
│ └── estimate.typ # 共通テンプレート
├── 2025-12-client-a/
│ └── estimate-data.yaml
├── 2025-12-client-b/
│ └── estimate-data.yaml
└── 2025-12-client-c/
└── estimate-data.yaml
テンプレートを共通化し、各案件はYAMLデータだけを持つ構成です。
一括コンパイル
シェルスクリプトで一括処理も可能です。
#!/bin/bash
for dir in estimates/2025-*/; do
cp estimates/template/estimate.typ "$dir"
typst compile "$dir/estimate.typ" "$dir/estimate.pdf"
done
おまけ: ウォーターマークの追加
サンプルや下書き版であることを示すため、「SAMPLE」などのウォーターマークを追加したい場合があります。
Typstの page 設定には background と foreground という2つのパラメータがあります。
background vs foreground
// ❌ background: コンテンツの後ろに配置される
#set page(
background: place(center + horizon)[
#rotate(-45deg)[
#text(size: 80pt, fill: rgb("00000015"))[SAMPLE]
]
],
)
// ✅ foreground: コンテンツの上に配置される
#set page(
foreground: place(center + horizon)[
#rotate(-45deg)[
#text(size: 80pt, fill: rgb("00000015"))[SAMPLE]
]
],
)
-
background: ページコンテンツの後ろに配置。テーブルや文字に隠れてしまう -
foreground: ページコンテンツの上に配置。半透明で重ねて表示される
ウォーターマークには foreground を使います。
透明度の調整
色指定の末尾2桁が透明度です(16進数、00=透明〜FF=不透明)。
rgb("00000015") // 黒、透明度約8%(ほぼ見えない程度)
rgb("00000030") // 黒、透明度約19%(やや見える)
rgb("ff000050") // 赤、透明度約31%
15(10進数で21)は256分の21、約8%の不透明度です。内容を邪魔せず、かつ存在が分かる程度の薄さになります。
まとめ
Typstの魅力は:
- テキストベース: Git管理、AI協業が容易
- 軽量: HTMLより小さく、コンパイルも高速
- 細かい制御: 印鑑を社名に重ねるような細かい配置も可能
- データ分離: YAML/JSONから読み込み、テンプレートと分離
日本の商習慣(御中、消費税、印鑑など)にも対応できます!
一度つくったテンプレートはYAMLを変更するだけで使いまわすことができます。
参考
明日のTypst Advent Calendarは @rice8y さんの Typst × Lindera で自動ルビ振り です!

