🌻

Typstで共通テスト数学のテンプレートを作成してみた

に公開

はじめに

共通テスト数学IIBCの問題形式を再現するためのTypstテンプレートを作成しました。Typstは新しい組版システムで、LaTeXよりもシンプルな記法で美しい文書を作成できます。

この記事では、Claude Codeを使ってテンプレートを作成した経緯と、完成したテンプレートの機能を紹介します。

作成の背景

共通テストの問題冊子には、独特のレイアウトやフォーマットがあります:

  • 解答欄のボックス(ア、イ、ウなど)
  • 丸囲み数字(①、②など)
  • 選択肢番号(⓪、①、②など)
  • 会話形式のボックス
  • 注意事項のボックス
  • 問題番号と配点表示

これらの要素をTypstで再現することで、問題作成の効率化を図ることができます。

完成したテンプレートの出力例

実際にテンプレートを使って作成したPDFを見てみましょう。

表紙・注意事項ページ

表紙ページ
共通テストの表紙を再現。注意事項や出題科目、選択方法などを記載

選択方法の説明ページ

選択方法ページ
第1問~第7問の選択方法を表で示す

第1問(三角関数)

第1問 - 前半
解答欄ボックス、丸囲み数字、会話ボックス、選択肢などの要素が含まれる

第1問 - 後半
増減表や解答群の表示例

第2問(図形と計量)

第2問
余弦定理や三角形の面積を扱う問題

第3問(複素数平面)

第3問
複素数平面上の三角形に関する問題

テンプレートの主な機能

1. ページ設定

A4サイズ、適切な余白設定、ページ番号表示を自動で行います。

#set page(
  paper: "a4",
  margin: (top: 25mm, bottom: 25mm, left: 20mm, right: 20mm),
  numbering: "— 1 —",
  number-align: center,
  footer: context [
    #align(right)[
      #text(size: 9pt)[2604#counter(page).display()]
    ]
  ]
)

2. フォント設定

日本語に適したヒラギノフォントを使用し、読みやすい文字サイズを設定しています。

#set text(
  font: ("Hiragino Mincho ProN", "Hiragino Sans"),
  size: 10.5pt,
  lang: "ja"
)

3. 便利な関数群

解答欄ボックス(answer-box

解答を記入するためのボックスを作成します。

#let answer-box(content) = {
  h(0.15em, weak: true)
  box(
    stroke: 1pt + black,
    inset: 3pt,
    baseline: 20%,
    width: 3em,
    height: 1.4em,
    align(center + horizon, $display(#content)$)
  )
  h(0.15em, weak: true)
}

使用例:

#answer-box[] $pi$

選択肢番号(choice

選択肢の丸囲み番号を作成します。

#let choice(body) = {
  box(
    stroke: 0.8pt + black,
    inset: (x: 2pt, y: 4pt),
    radius: 100%,
    baseline: 25%,
    text(size: 9pt, body)
  )
}

使用例:

#choice[0]P と点 Q の $x$ 座標が等しい
#choice[1]P と点 Q の $y$ 座標が等しい

丸囲み数字(circle

問題文中の丸囲み数字(①、②など)を表示します。

#let circle(num) = {
  let circled = (
    "①", "②", "③", "④", "⑤",
    "⑥", "⑦", "⑧", "⑨", "⑩",
    "⑪", "⑫", "⑬", "⑭", "⑮",
    "⑯", "⑰", "⑱", "⑲", "⑳"
  )
  if type(num) == int and num >= 1 and num <= 20 {
    circled.at(num - 1)
  } else {
    num
  }
}

使用例:

$ sin(theta + pi/6) = sin 2theta #h(2em) dots.h.c #circle(1) $

問題番号(question

問題番号、必答/選択の区別、配点を表示します。

#let question(num, points: none, required: false) = {
  let req-text = if required {
    text(fill: black, font: "Hiragino Sans", size: 11pt)[(必答問題)]
  } else {
    text(fill: black, font: "Hiragino Sans", size: 11pt)[(選択問題)]
  }

  let point-text = if points != none {
    text(fill: black, font: "Hiragino Sans", size: 11pt)[(配点 #points)]
  } else { "" }

  [
    = 第 #num 問 #req-text #point-text
  ]
}

使用例:

#question(1, points: 15, required: true)
#question(4, points: 20, required: false)

注意事項ボックス(notice-box

表紙の注意事項を囲むボックスです。

#let notice-box(content) = {
  rect(
    width: 100%,
    inset: 10pt,
    stroke: 1.5pt + black,
    [#content]
  )
}

使用例:

#notice-box[
  試験開始の指示があるまで、この問題冊子の中を見てはいけません。
]

会話ボックス(dialogue-box

問題内の会話形式の部分を表現します。

#let dialogue-box(content) = {
  rect(
    width: 100%,
    inset: 10pt,
    stroke: (
      top: none,
      bottom: none,
      left: none,
      right: none,
      rest: 1pt + black
    ),
    [#content]
  )
}

使用例:

#dialogue-box[
  太郎:角が等しくなくても、サインの値が等しくなることがあるね。

  花子:サインの値が等しくなるのはどんなときか、単位円を用いて考えてみようか。
]

分数の解答欄(frac-answer

分数形式の解答欄を作成します。

#let frac-answer(num, den) = {
  $frac(#answer-box(num), #answer-box(den))$
}

使用例:

$ theta = frac(#answer-box[], #answer-box[]) pi $

実際の使用例

三角関数の問題(第1問)

#question(1, points: 15, required: true)

(1) $0 <= theta < pi$ のとき、方程式

$ sin(theta + pi/6) = sin 2theta #h(2em) dots.h.c #circle(1) $

の解を求めよう。以下では、$alpha = theta + pi/6$,$beta = 2theta$ とおく。

(i) $alpha = beta$ を満たす $theta$ は #answer-box[] $pi$ であり、
これは #circle(1) の解の一つである。

図形と計量の問題(第2問)

#question(2, points: 15, required: true)

三角形 $upright(A) upright(B) upright(C)$ において、
$upright(A) upright(B) = 5$,$upright(B) upright(C) = 7$,
$angle upright(A) upright(B) upright(C) = 60 degree$ とする。

(1) 余弦定理により、$upright(A) upright(C) = $ #answer-box[]
$sqrt(#answer-box[])$ である。

複素数平面の問題(第3問)

#question(3, points: 20, required: true)

複素数平面上の3$upright(A)(alpha)$,$upright(B)(beta)$,
$upright(C)(gamma)$ を頂点とする三角形について考える。

(1) $alpha = 2 + i$,$beta = -1 + 2i$,$gamma = 1 - i$ のとき、
三角形はどのような三角形か。#answer-box[] が成り立つ。

#answer-box[] の解答群

#choice[0] 正三角形である
#choice[1] 直角二等辺三角形である
#choice[2] 直角三角形であるが二等辺三角形ではない

Typstのメリット

1. シンプルな記法

LaTeXと比較して、より直感的で読みやすい記法を採用しています。

  • 関数定義が簡潔(#letで定義)
  • 条件分岐が自然な書き方
  • 組版コマンドの記法が分かりやすい

2. 高速なコンパイル

LaTeXよりも圧倒的に高速にPDFを生成できます。リアルタイムプレビューも快適です。

3. モダンな機能

  • 関数定義が柔軟
  • 変数スコープが明確
  • エラーメッセージが分かりやすい

Claude Codeとの協働作成プロセス

このテンプレート作成では、Claude Codeと以下のようなプロセスで進めました:

  1. 要件定義: 共通テストの問題形式を分析し、必要な要素をリストアップ
  2. 関数設計: 各要素に対応する関数を設計
  3. 実装とテスト: 実際にTypstコードを書き、PDFで出力を確認
  4. 微調整: レイアウトやスタイルを細かく調整

特に有用だったのは、Claude Codeが以下の点でサポートしてくれたことです:

  • Typstの記法についての即座のフィードバック
  • より簡潔な実装方法の提案
  • エラー修正の迅速な対応
  • 実際の共通テストの形式に近づけるための細かな調整

テンプレートの改善ポイント

実際に作成してみて、以下の改善点が見つかりました:

実装済みの機能

  • ✅ 解答欄ボックス(answer-box
  • ✅ 選択肢番号(choice
  • ✅ 丸囲み数字(circle
  • ✅ 問題番号と配点表示(question
  • ✅ 注意事項ボックス(notice-box
  • ✅ 会話ボックス(dialogue-box
  • ✅ 解答表組み(answer-table
  • ✅ 分数の解答欄(frac-answer

今後の改善予定

  • 図形描画機能: TypstのCeTZ(図形描画パッケージ)を使った図形の追加
  • より精密なレイアウト: 実際の共通テストとの差分を詰める
  • 数式表現の拡充: より複雑な数式への対応
  • カスタマイズ性の向上: 科目や年度に応じた柔軟な設定
  • 解答用紙テンプレート: マークシート形式の解答用紙の作成

まとめ

Claude Codeを活用して、Typstで共通テスト数学のテンプレートを作成しました。これらの関数を組み合わせることで、共通テストの問題形式を効率的に作成できます。

Typstは新しい組版システムですが、教育現場での資料作成にも十分活用できると感じました。特に数学の問題作成においては、LaTeXの代替として有力な選択肢になると思います。

AI(Claude Code)との協働により、短時間で実用的なテンプレートを作成できたことも大きな成果でした。実際の問題形式を見ながら、必要な機能を一つずつ実装していくプロセスは、とても効率的でした。

参考

付録:完全なTypstコード

実際に使用した.typファイルの全コードを以下に掲載します。

#set page(
  paper: "a4",
  margin: (top: 25mm, bottom: 25mm, left: 20mm, right: 20mm),
  numbering: "— 1 —",
  number-align: center,
  footer: context [
    #align(right)[
      #text(size: 9pt)[2604#counter(page).display()]
    ]
  ]
)

#set text(
  font: ("Hiragino Mincho ProN", "Hiragino Sans"),
  size: 10.5pt,
  lang: "ja"
)

#set heading(numbering: "1.")

#set par(
  leading: 0.65em,
  justify: true,
  first-line-indent: 1em
)

#show heading: it => {
  set text(font: "Hiragino Sans")
  it
}

#let answer-box(content) = {
  h(0.15em, weak: true)
  box(
    stroke: 1pt + black,
    inset: 3pt,
    baseline: 20%,
    width: 3em,
    height: 1.4em,
    align(center + horizon, $display(#content)$)
  )
  h(0.15em, weak: true)
}

#let choice(body) = {
  box(
    stroke: 0.8pt + black,
    inset: (x: 2pt, y: 4pt),
    radius: 100%,
    baseline: 25%,
    text(size: 9pt, body)
  )
}

#let circle(num) = {
  let circled = (
    "①", "②", "③", "④", "⑤",
    "⑥", "⑦", "⑧", "⑨", "⑩",
    "⑪", "⑫", "⑬", "⑭", "⑮",
    "⑯", "⑰", "⑱", "⑲", "⑳"
  )
  if type(num) == int and num >= 1 and num <= 20 {
    circled.at(num - 1)
  } else {
    num
  }
}

#let question(num, points: none, required: false) = {
  let req-text = if required {
    text(fill: black, font: "Hiragino Sans", size: 11pt)[(必答問題)]
  } else {
    text(fill: black, font: "Hiragino Sans", size: 11pt)[(選択問題)]
  }

  let point-text = if points != none {
    text(fill: black, font: "Hiragino Sans", size: 11pt)[(配点 #points)]
  } else { "" }

  [
    = 第 #num 問 #req-text #point-text
  ]
}

#let notice-box(content) = {
  rect(
    width: 100%,
    inset: 10pt,
    stroke: 1.5pt + black,
    [#content]
  )
}

#let dialogue-box(content) = {
  rect(
    width: 100%,
    inset: 10pt,
    stroke: (
      top: none,
      bottom: none,
      left: none,
      right: none,
      rest: 1pt + black
    ),
    [#content]
  )
}

#let answer-table(..args) = {
  table(
    columns: args.pos().len(),
    stroke: 1pt + black,
    ..args.pos()
  )
}

#let frac-answer(num, den) = {
  $frac(#answer-box(num), #answer-box(den))$
}

#align(center)[
  #v(5em)

  #text(size: 12pt, font: "Hiragino Sans")[
    *令和7年度大学入学共通テスト*
  ]

  #v(2em)

  #text(size: 16pt, weight: "bold", font: "Hiragino Sans")[
    *数学IIBC*
  ]

  #v(3em)

  #notice-box[
    #set par(first-line-indent: 0em)
    #align(left)[
      #v(0.5em)
      #text(size: 11pt)[
        *注意事項*
      ]

      #v(1em)

      1. 試験開始の指示があるまで、この問題冊子の中を見てはいけません。

      2. この問題冊子は42ページあります。

      3. 解答用紙は別に配付されます。

      4. 問題は、第1問から第7問まであります。このうち、第1問、第2問、第3問は必答問題で、第4問から第7問までは選択問題です。選択問題については、次ページの指示に従って解答してください。

      5. 問題冊子の余白等は適宜利用してもかまいませんが、どのページも切り離してはいけません。

      #v(0.5em)
    ]
  ]

  #v(3em)

  #text(size: 11pt, font: "Hiragino Sans")[
    (下書用紙は、ページの後にあります。)
  ]
]

#pagebreak()

#align(center)[
  #v(3em)

  #text(size: 13pt, font: "Hiragino Sans")[
    *選択方法*
  ]

  #v(2em)
]

#set par(first-line-indent: 0em)4問から第7問までのうちから3問を選択し、解答してください。

4問以上解答した場合は、はじめの3問を採点します。

#v(2em)

#align(center)[
  #table(
    columns: (auto, auto, auto, auto),
    stroke: 1pt + black,
    align: center + horizon,
    inset: 12pt,

    table.header(
      [*問題番号*],
      [*出題分野*],
      [*配点*],
      [*備考*]
    ),

    [1], [三角関数], [15], [必答],
    [2], [図形と計量], [15], [必答],
    [3], [複素数平面], [20], [必答],
    [4], [確率分布], [20], [選択],
    [5], [数列], [20], [選択],
    [6], [ベクトル], [20], [選択],
    [7], [微分・積分], [20], [選択]
  )
]

#pagebreak()

#question(1, points: 15, required: true)

(1) $0 <= theta < pi$ のとき、方程式

$ sin(theta + pi/6) = sin 2theta #h(2em) dots.h.c #circle(1) $

の解を求めよう。以下では、$alpha = theta + pi/6$,$beta = 2theta$ とおく。

(i) $alpha = beta$ を満たす $theta$ は #answer-box[] $pi$ であり、これは #circle(1) の解の一つである。

(ii) $alpha + beta = pi$ を満たす $theta$ は #answer-box[] $pi$ であり、これは #circle(1) の解の一つである。

(i)(ii)より、#circle(1) の解は #answer-box[] $pi$,#answer-box[] $pi$ である。

#v(1em)

#dialogue-box[
  太郎:角が等しくなくても、サインの値が等しくなることがあるね。

  花子:サインの値が等しくなるのはどんなときか、単位円を用いて考えてみようか。

  太郎:$alpha = beta$ となるときと、$alpha + beta = pi$ となるときがあるね。

  花子:なるほど。#circle(1) の両辺の角がどのような関係にあるか、一般的に考えてみよう。
]

#v(1em)

(2) $0 <= theta < 2pi$ のとき、不等式

$ sin(theta + pi/6) > sin 2theta #h(2em) dots.h.c #circle(2) $

を解こう。

$f(theta) = sin(theta + pi/6) - sin 2theta$ とおく。$0 <= theta < 2pi$ における $f(theta)$ の増減を調べると、次の表のようになる。

#v(1em)

#align(center)[
  #table(
    columns: (1fr, 0.4fr, 0.4fr, 0.4fr, 0.4fr, 0.4fr, 0.4fr, 0.4fr, 0.4fr),
    stroke: 1pt + black,
    align: center + horizon,
    inset: 8pt,

    [$theta$], [0], [], [$#answer-box[] pi$], [], [$#answer-box[] pi$], [], [$#answer-box[] pi$], [$2pi$],

    [$f'(theta)$], [], [+], [0], [-], [0], [+], [0], [-],

    [$f(theta)$], [0], [], [], [], [0], [], [], [], [0]
  )
]

#v(1em)

したがって、不等式 #circle(2) の解は

#h(2em) $#answer-box[] < theta < #answer-box[] pi, #h(1em) #answer-box[] pi < theta < #answer-box[] pi$

である。ただし、#answer-box[] には 0 を入れよ。

#v(2em)

(3) $0 <= theta < 2pi$ において、$sin theta = cos 2theta$ を満たす $theta$ は全部で #answer-box[] 個ある。

#v(1em)

#answer-box[] の解答群

#choice[0] 2 #h(2em) #choice[1] 3 #h(2em) #choice[2] 4 #h(2em) #choice[3] 5 #h(2em) #choice[4] 6

#pagebreak()

#question(2, points: 15, required: true)

三角形 $upright(A) upright(B) upright(C)$ において、$upright(A) upright(B) = 5$,$upright(B) upright(C) = 7$,$angle upright(A) upright(B) upright(C) = 60 degree$ とする。

(1) 余弦定理により、$upright(A) upright(C) = $ #answer-box[] $sqrt(#answer-box[])$ である。

また、正弦定理により、三角形 $upright(A) upright(B) upright(C)$ の外接円の半径は $#answer-box[] sqrt(#answer-box[]) / #answer-box[]$ である。

#v(1em)

(2) 三角形 $upright(A) upright(B) upright(C)$ の面積は $#answer-box[] sqrt(#answer-box[]) / #answer-box[]$ である。

#v(1em)

(3)$upright(B) upright(C)$ を $2:1$ に内分する点を $upright(D)$ とする。このとき、$upright(A) upright(D) = $ #answer-box[] $sqrt(#answer-box[])$ である。

また、$cos angle upright(A) upright(D) upright(B) = #answer-box[] / #answer-box[] sqrt(#answer-box[])$ である。

#v(1em)

(4)$upright(A) upright(B)$ 上に点 $upright(P)$ をとり、$upright(C) upright(P) perp upright(A) upright(D)$ となるようにする。このとき、$upright(A) upright(P)$ の長さは #answer-box[] が成り立つ。

#v(1em)

#answer-box[] の解答群

#choice[0] $upright(A) upright(P) < 2$

#choice[1] $upright(A) upright(P) = 2$

#choice[2] $2 < upright(A) upright(P) < 3$

#choice[3] $upright(A) upright(P) = 3$

#choice[4] $3 < upright(A) upright(P) < 4$

#choice[5] $upright(A) upright(P) = 4$

#choice[6] $4 < upright(A) upright(P)$

#pagebreak()

#question(3, points: 20, required: true)

複素数平面上の3$upright(A)(alpha)$,$upright(B)(beta)$,$upright(C)(gamma)$ を頂点とする三角形について考える。

(1) $alpha = 2 + i$,$beta = -1 + 2i$,$gamma = 1 - i$ のとき、三角形 $upright(A) upright(B) upright(C)$ はどのような三角形か。#answer-box[] が成り立つ。

#v(1em)

#answer-box[] の解答群

#choice[0]$upright(A)$ と点 $upright(B)$ の $x$ 座標が等しい

#choice[1]$upright(A)$ と点 $upright(B)$ の $y$ 座標が等しい

#choice[2]$upright(B)$ と点 $upright(C)$ の $x$ 座標が等しい

#choice[3]$upright(B)$ と点 $upright(C)$ の $y$ 座標が等しい

#choice[4]$upright(C)$ と点 $upright(A)$ の $x$ 座標が等しい

#choice[5]$upright(C)$ と点 $upright(A)$ の $y$ 座標が等しい

#v(1em)

(2) 一般に、三角形 $upright(A) upright(B) upright(C)$ が正三角形であるための条件は、

#h(2em) $(gamma - alpha)^2 + (alpha - beta)^2 + (beta - gamma)^2 = #answer-box[]$

である。

#v(1em)

#answer-box[] の解答群

#choice[0] $0$ #h(2em) #choice[1] $1$ #h(2em) #choice[2] $-1$ #h(2em) #choice[3] $i$ #h(2em) #choice[4] $-i$

#v(1em)

(3) 複素数 $omega = (-1 + sqrt(3)i) / 2$ について考える。

(i) $omega^3 = $ #answer-box[] であり、$1 + omega + omega^2 = $ #answer-box[] である。

#v(1em)

#answer-box[]、#answer-box[] の解答群(同じものを繰り返し選んでもよい。)

#choice[0] $-1$ #h(2em) #choice[1] $0$ #h(2em) #choice[2] $1$ #h(2em) #choice[3] $i$ #h(2em) #choice[4] $omega$

#v(1em)

(ii) $alpha$,$beta$,$gamma$ が

#h(2em) $gamma = omega alpha + (1 - omega)beta$

を満たすとき、三角形 $upright(A) upright(B) upright(C)$ は #answer-box[] である。

#v(1em)

#answer-box[] の解答群

#choice[0] 正三角形である

#choice[1] 直角二等辺三角形である

#choice[2] 直角三角形であるが二等辺三角形ではない

#choice[3] $angle upright(B) upright(A) upright(C) = 120 degree$ の二等辺三角形である

#choice[4] $angle upright(A) upright(B) upright(C) = 120 degree$ の二等辺三角形である

#choice[5] $angle upright(B) upright(C) upright(A) = 120 degree$ の二等辺三角形である

#choice[6] 上記のいずれでもない
GitHubで編集を提案

Discussion