typstを使って卒論の環境構築をする

# はじめに

tosaken1116 Advent Calendar 2024 2日目担当の土佐犬です


絶賛大学四年生でめちゃくちゃ忙しい日々を過ごしてる中記事を書く暇がありません

今回は卒論の環境構築をします

卒論の環境構築ってなんやって話ですが、大学生は4年生になると卒業するために卒業研究をし、その論文を書かなくてはなりません　理系は特に、

でその卒論というのは基本的に`Microsoft Word` or `Latex`で書きます　

今回の話は前者`Microsoft Word`で書く人には当てはまりません

私が所属する研究室ももれなくLatexで書くようですが、去年の今頃typstなるものをチラリと見かけていて使ってみたいなぁと思い、教授に直談判したところ「いいんじゃない？」ということで今回はtypstを使った卒論環境構築を作ろうと思います

ちなみに私はLatexも書いたことがありません

のでtypstでもLatexでもハードルは同じくらいです

今回作成したディレクトリはこちら

https://github.com/tosaken1116/typst-template

## この記事で扱う話 扱わない話
扱う話
環境構築したよって話

扱わない話
typstの文法の詳細


# typstとは
Latexより早くて簡単なドキュメントカキコツールだぜ！！って書いてありました


https://github.com/typst/typst


https://typst.app/



# 要件定義

要件としては以下のようなものを考えています
- 手元でコンパイルできる
- コンパイル済みファイルを外部に共有できる
- バージョン管理ができる


バージョン管理に関してはgitでどうにでもなります

手元でコンパイルに関してもtypst cliでできそうです

コンパイル済みファイルを外部に共有することが今回の環境構築の主な内容になりそうです

# 環境構築

## typstファイルを書く

typstはbrewで入れられます

```
brew install typst
```

```
typst --version
```

```
typst 0.12.0
```


まだベータ版なのかメジャーバージョンは0です


typstはinitコマンドを使ってテンプレートを呼び出して作成することができますが今回は1から構築していきます

ディレクトリ構成ですが以下のようになっています

```terminal
.
├── .cz-config.js // コミットフォーマット
├── .czrc         // コミットフォーマット
├── .github         
│   └── workflows
│       └── build.yaml
├── .gitignore
├── README.md
├── Taskfile.yml
├── docs                       // ユーザーが書き込む部分
│   ├── abstract.typ
│   └── chapters
│       └── chapter1.typ
├── env.typ　　　　　　　　　　　　// 環境変数
├── init.sh                    // 環境構築スクリプト
├── lefthook.yml               // lefthook用(pre commit)
├── libs                       // 文書のフォーマット定義
│   ├── abstract.typ
│   ├── config.typ
│   ├── cover.typ
│   ├── index.typ
│   └── template.typ
└── main.typ　　　　　　　　　　　// main
```


まずは`main.typ`を作ります



```typst
#import "libs/cover.typ": cover
#import "libs/abstract.typ": abstract
#import "libs/config.typ": view
#import "libs/index.typ": index
#show: view.with()

// 表紙
#cover()
#counter(page).update(1)
#set page(numbering: "i")

// アブストラクト
#abstract()

// 目次
#index()

#set page(numbering: "1")
#counter(page).update(1)


#let chapters = ("docs/chapters/chapter1.typ",)

#for chapter in (chapters) {
  pagebreak()
  include (chapter)
}

```

typstは`#import`を使って他のtypstファイルを参照できます

ここではlibsディレクトリの`cover`, `abstract` `config` `index`を呼び出しています

それぞれのファイルには以下が定義されています

- cover: 表紙テンプレート
- abstract: アブストラクトテンプレート
- config: 文書全体に適用する設定
- index: 目次テンプレート

`#show`文は関数を受け取りその関数を用いて文書を表示します

ここではconfigを用いて続く文書を表示するようにしています


`config.typ`は次のように書いています

```typst
#let view(doc) = {
  set text(font: "IPAMincho", 12pt) //　テキストの設定
  set par(                          // 文章コンテンツの設定
    first-line-indent: 1em,
    spacing: 0.65em,
    justify: true,
  )
  set heading(numbering: (..nums) => { // 章のフォーマット
    let numbers = nums.pos()
    if numbers.len() == 1 {
      numbering("第1章　", ..numbers)
    } else {
      numbering("1.1　", ..numbers)
    }
  })

  show heading.where(level: 1): set block(above: 40pt, below: 50pt) //　章のマージン
  show heading.where(level: 2): set block(above: 30pt, below: 15pt)
  show heading.where(level: 3): set block(above: 30pt, below: 15pt)
  show heading.where(level: 1): set text(size: 24pt) // 章のテキストサイズ
  show heading.where(level: 2): set text(size: 16pt)
  show heading.where(level: 3): set text(size: 12pt)
 
  set page(  // 全ページ共通の設定
    paper: "a4",
    margin: (
      top: 40mm,
      bottom: 40mm,
      left: 35mm,
      right: 20mm,
    ),
  )

  doc
}

```


まだ中身は最低限のものしか書いていませんが今後必要になったら足していこうと思います


続いてcover関数の呼び出しを行っています

cover.typは次のように記述しています

```typst

#import "../env.typ": env

#let cover() = {
  set align(center)
  set text(18pt)

  v(20mm)
  [#text(size: 20pt)[卒~~業~~論~~文]]

  v(15mm)
  box(width: 145mm)[
    #align(center)[#env.thesis_title]
  ]
  v(40mm)
  stack(
    dir: ttb,
    spacing: 3mm,
    text(size: 18pt)[#env.college_name #env.faculty_name],
    text(size: 18pt)[#env.department_name],
  )
  v(15mm)
  [#env.student_name]
  v(25mm)
  [#env.year_of_grad 年度]
  v(10mm)
  [指導教員：#env.supervisor_name]
  pagebreak()
}

```

env.typから特定の値を呼び出し表紙として表示する関数です


その次のページ数の表示をしています

表紙にはページ番号をつけたくないため表紙を表示した後に関数を実行し表示するようにしています

```
#counter(page).update(1)
#set page(numbering: "i")
```


またこの時のページ番号はローマ数字表記にしています


次にabstractとindexです

abstractとはその論文の概要を示すセクションです

```typst
// アブストラクト
#abstract()

// 目次
#index()
```

abstract.typではdocsディレクトリの中のabstract.typファイルから文章を読み出し、それを特定のフォーマットに基づいて表示するようにしています


とはいってもそこまで大きな設定はないので冗長といえば冗長です

`include`を使ってtypstファイルから文書全体を取り出して表示しています

```typst:libs/abstract.typ
#let abstract() = {
  align( //文書を真ん中寄せで表示
    center,
    text(size: 12pt)[概~要],
  )
  v(4mm)
  [#include "../docs/abstract.typ"] // docs/abstract.typをinclude
  pagebreak() // 次の文書が次のページに来るようにする
}
```


index.typではそれ以降に表示する文書の目次を表示する関数を定義しています

```typst:libs/index.typ
#let index() = {
  show outline.entry.where(level: 1): it => { // 最も大きい章の表示設定
    v(12pt)
    text(size: 12pt, it)
  }

  show outline.entry.where(level: 2): it => { // 2番目に大きい章の表示設定
    text(size: 12pt, it)
  }

  v(10mm)
  outline( // 目次の表示
    indent: 2em,
    title: box([
      #text(size: 24pt)[目~~次]
      #v(10mm)
    ]),
  )
}
```


目次を表示したらもう一度ページ番号をリセットします

```typst
#set page(numbering: "1")
#counter(page).update(1)
```

これは目次の次のページからページ番号を始めるためです

なおこの時の数字表記はアラビア数字を用いています


そして最後にchapterを表示しています

chapters配列にインポートしたいファイルを定義してそれをfor文を用いて表示しています

```typst

#let chapters = ("docs/chapters/chapter1.typ",)

#for chapter in (chapters) {
  pagebreak()
  include (chapter)
}

```

最後にユーザーが文書を書く部分ですが`env.typ`ではユーザーに関するデータなどを定義します


```typst:env.typ
#let student_name = "著者の名前"
#let student_id = "00000000"
#let supervisor_name = "指導教員の名前"
#let thesis_title = "卒論のタイトル"
#let year_of_grad = "2023"
#let college_name = "大学名"
#let faculty_name = "学部名"
#let department_name = "学科名"

#let env = (
  student_name: student_name,
  student_id: student_id,
  supervisor_name: supervisor_name,
  thesis_title: thesis_title,
  year_of_grad: year_of_grad,
  college_name: college_name,
  faculty_name: faculty_name,
  department_name: department_name,
)
```


またdocsディレクトリ内のchapterやabstract.typでは次のように書くだけで文書が表示されます


```typst:abstract.typ
アブストラクト書くとこ
```


```typst:chapters/chapter1.typ
= はじめに // typstでは = を用いて章を書きます

これはテスト用のドキュメントです
```

これで一通りのtypstファイルの定義を作ったのでコンパイルします

```terminal
typst compile main.typ
```

また文書が変わったら動的にコンパイルする`watch`コマンドもあります


```terminal
typst watch main.typ
```



そしてコンパイルされた文書が以下です


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3230849/3fbe21ca-cc63-e662-257a-cc99878f9999.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3230849/e6ef1314-c736-06d1-67ad-ce103c0bdc0b.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3230849/19cdf02b-bbae-1bbe-206e-5f91568a7665.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3230849/c4cc4105-c78f-9fec-9489-1337872984b9.png)


いい感じにできました

とはいっても私はまだ卒論を書いたことがないのでこれはサークルの先輩に見せていただいた論文を模したものです



## 周りのツールなど
typstコマンドを毎回全部打つのは面倒くさいのでtaskfileを作ります


taskfileはgo製のツール`Task`のコマンド定義ファイルです

といってもここでは詳しくは触れません

公式サイト
https://taskfile.dev/


```yml:taskfile.yml
version: "3"

tasks:
  default:
    desc: Run the default task
    cmds:
      - task -l

  compile:
    desc: Builds the typst document
    deps:
      - fmt
    cmds:
      - mkdir -p exports && typst compile main.typ exports/main.pdf
  compile:watch:
    desc: Builds the typst document on change
    cmds:
      - mkdir -p exports && typst watch main.typ exports/main.pdf
  commit:
    cmds:
      - cz
  fmt:
    desc: Format all typ files
    cmds:
      - typstyle format-all libs
      - typstyle format-all main.typ
      - typstyle format-all env.typ
      - typstyle format-all docs
    silent: true

```

今回は`compile` `compile:watch` `commit` `fmt`の四つのコマンドを定義しました

commitには`commitizen`という特定のフォーマットに基づいたコミットができるツールを使っています


またtypstにはデフォルトではフォーマットコマンドがないので有志のパッケージ`typstyle`を用いています

typstyle
https://github.com/Enter-tainer/typstyle

## github actionsの設定
最後にgithub actionsの設定です


一度最初に作った時に出てきたpdfが以下のようなものです

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3230849/c2e71b1b-845d-9823-d771-348f58639556.png)



豆腐がたくさんありますね

github actionsで使用されるubuntuにはデフォルトでは日本語フォントが入っていないためこのような表記になります


ということでそれを加味したactionが以下です

```yml:.github/workflows/build.yaml
name: Build Typst document
on:
  push:
    branches:
      - main
    paths:
      - docs/**/*
      - main.typ
      - env.typ

env:
  GOOGLE_DRIVE_UPLOAD_DIRECTORY: /research/report
  TZ: Asia/Tokyo

jobs:
  build_typst_documents:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Cache Typst binary
        id: cache
        uses: actions/cache@v3
        with:
          path: ~/.cargo/bin/typst
          key: ${{ runner.os }}-typst-bin-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-typst-bin-

      - name: Set up Typst
        if: steps.cache.outputs.cache-hit != 'true'
        run: |
          sudo apt update -y
          sudo apt install -y fonts-ipafont
          cargo install --git https://github.com/typst/typst --locked typst-cli

      - name: Verify Typst Installation
        run: typst --version

      - name: Build Typst Document
        run: typst compile main.typ

      - name: Get current date in JST
        id: date
        run: |
          export TZ=$TZ
          echo "DATE=$(date +%Y-%m-%d-%H:%M)" >> $GITHUB_ENV

      - name: Rename file
        run: mkdir exports && mv main.pdf exports/${{ env.DATE }}.pdf

      - name: Upload to Google Drive
        uses: adityak74/google-drive-upload-git-action@main
        with:
          credentials: ${{ secrets.GOOGLE_DRIVE_SECRET }}
          filename: "exports/*.pdf"
          folderId: ${{ secrets.GOOGLE_DRIVE_FOLDER_ID }}

```

ubuntuからtypstを入れるにはRust経由が良さそうだったのでRustをインストールしそこからtypstをインストールしています


またtypstでコンパイル後google driveにアップロードして他のユーザーからも参照できるようにします


なおgoogle driveにアップロードするためには`Google Cloud`でプロジェクトを作りgoogle drive apiにアクセス可能なサービスアカウントを作り、自分のフォルダにそのサービスアカウントを招待することが必要です

ちょっとめんどくさいです


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3230849/38e42da2-3089-1c96-d994-4be78499efc5.png)


ということでできました

リポジトリにpushするだけでpdfができるactionsの完成です



# 終わりに
ということで今回はtypstでテンプレートを作った話でした

色々忙しくて卒論を書くのはちょっと大変ですが、卒業できるように頑張ります...