4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Typst で日本の商習慣に合った見積書を作る

Last updated at Posted at 2025-12-11

この記事は Qiita Advent Calendar 2025 Typst シリーズ Day 11 の記事です。

はじめに:私がTypstに出会うまで

私はかつて、スライドや書類の作成にPowerPointやAdobe Illustratorを使っていました。

LLMの登場とともに、AIにも内容が解釈可能な形式として reveal.js でHTMLスライドを作成するようになりました。テキストベースでGit管理ができ、AIとの協業も容易になりましたが、次第に課題を感じるようになりました:

  • HTMLはファイルサイズが大きい
  • 細かいデザインのコントロールに限界がある

そこで Typst に出会いました。

typst-files-2025.png

今年7月から本格的に使い始めて、半年で152ファイル を作成しました。講座で使うスライド資料、見積書、提案書、納品書、マニュアル...あらゆる書類をTypstで作るようになりました。

estimate-sample.png

こんな見積書も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_pricequantitytax_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の placeimage を使って実現できます。

// 社名と印鑑画像を重ねて配置
#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 設定には backgroundforeground という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 で自動ルビ振り です!

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?