Typstで遊ぼう

この記事は Typst で書いていたものの写しであり書きかけであり更新中です。
Typstで作成したPDF: https://discord.ubanis.com/typst-manual.pdf
kawaiiロゴ: https://gist.github.com/fenjalien/1463a19ba2b91d061ed35e295494e0b3
Typst で作る表紙の例
色々できないことも多い(縦書きなど)
以下はTypstで作成したドキュメントをMarkdownにしたものです。
はじめに
Typst とは
Typstは、科学技術文書の作成に特化した、現代的な組版システムです。Markdownのようにシンプルな構文と、LaTeXのようにパワフルで柔軟な表現力を兼ね備えています。リアルタイムでの高速なコンパイルが特徴です。
- githubページ : https://github.com/typst/typst
- ドキュメント : https://typst.app/docs/
Typst の利点
- 学習の容易さ: 直感的で覚えやすい構文を採用しており、初心者でもすぐに使い始めることができます。
- 高速なコンパイル: 変更を即座にプレビューに反映するため、トライ&エラーが容易です。
- 強力なスクリプティング: 変数、関数、ループ、条件分岐といったプログラミング機能が組み込まれており、動的で再利用性の高いコンテンツを作成できます。
- 統一されたエコシステム: パッケージ管理機能が内蔵されており、外部のライブラリやテンプレートを簡単に導入できます。
インストール
Typstは、Webアプリケーションとしてブラウザ上で利用する方法と、ローカル環境にインストールして利用する方法があります。詳細は公式サイトを参照してください。
- Web アプリケーション: https://typst.app/
- ローカルへのインストール: https://github.com/typst/typst?tab=readme-ov-file#installation
以下は代表的なパッケージ管理ソフトでのインストール方法です。
winget(Windows)
winget install --id Typst.Typst
scoop(Windows)
scoop install typst
Cargo
cargo install --locked typst-cli
Snap(Linux)
sudo snap install typst
mise
mise use -g typst
はじめての Typst
まずシンプルな以下のような内容のファイルをexample.typ
という名前で保存します。
= 見出しです
段落です。
- リスト1- リスト2- リスト3
以下のコマンドで対象のTypstファイルからpdfを出力します。
typst compile example.typ
出力されたpdfファイルを開いてみると以下のようになっているはずです。
以下のコマンドは対象のTypstファイルを監視して変更があるたびにコンパイルをしてpdfを更新し続けます。
typst watch example.typ
この状態でexample.typ
を編集するたびにpdfファイルが更新されることになります。
vscode 拡張 Tinymist Typst の導入
vscode上でTypstを快適に編集するための拡張機能Tinymist Typstを導入すると、構文ハイライトや補完機能が利用可能になります。 リアルタイムプレビュー機能もあり、Typstのドキュメントを編集しながら即座に結果を確認できます。
このようにとても編集しやすいのでぜひ導入しましょう
プレビュー画面の表示
vscode上の右上にある虫眼鏡のついたプレビューアイコンをクリックすると現在のファイルのリアルタイムプレビュー画面が右側に表示されます。 プレビュー画面の文などの要素をクリックすると該当箇所のファイルの行へ移動でき、 ファイル内の要素をクリックするとプレビュー画面がその位置へスクロールします。
プレビュー画面の右上にある四角に右上矢印のアイコンをクリックするとプレビュー画面は外部ブラウザに表示されるようになります。 クリックでジャンプする機能はブラウザ上でもそのまま利用できます。
基本なマークアップ
見出し
= の数で見出しのレベルを表現します。
= 章 (レベル1)== 節 (レベル2)=== 項 (レベル3)
テキストの装飾
Typstでは、簡単な記号でテキストを装飾できます。
強調
*強調*
斜体
_イタリック_
下線
#underline[下線]
打ち消し線
#strike[打ち消し線]
URL
URL https://example.com
改行・エスケープ文字
\
を使います。
このように改行できます。\ 文中では\ スペースを一つ空けます。\\$←のようなTypstで利用されている文字を書きたい場合のエスケープ文字としても利用されます。
コードブロック
`(バッククォート1つ)で囲んだ文字はインラインコードになります。
`これはインライン`です。
```(バッククォート3つ)で囲んだ行はコードブロックを形成します。言語を指定するとシンタックスハイライトが適用されます。
//これはブロックです。//シンタックスハイライトが適用されています。#include <stdio.h>
void main(int argc, char *argv[]){ char *str = "hello world!"; puts(str);}
リスト
-
(ハイフン)や+
(プラス)でリストを作成します。インデントすることでネストも可能です。
- 順序なしリスト - ネストされたリスト- 順序付きリスト + 番号は自動で振られます + 2番目の項目
用語リスト
/ Typst : 今注目されている新しい組版システム。2019年に開発が開始され、2023年3月にベータ版が一般公開された。Rustで書かれており、シンプルな文法やコンパイルの速さが利点。
/ Rust : メモリ安全性を保証しつつ、低レベルのシステムプログラミングが可能な言語。所有権システムを採用しており、ガベージコレクタ#footnote("コンピュータプログラムが動的に確保したメモリ領域のうち、不要になった領域を自動的に解放する機能です。")がなくてもメモリリークやデータ競合を防ぐ。
/ ねこ : @nyan-image
数式
$
で数式を囲みます。1行でインライン数式、複数行でブロック数式になります。
/ インライン数式 : $E = m c^2$ はインライン数式です。
/ ブロック数式 : これはブロック数式です。 $ sum_(i=1)^n i = (n(n+1)) / 2 $
参照
<ラベル名>
で要素にラベルを付け、@ラベル名
で参照します。数式や図、セクションなど、あらゆる要素にラベルを付けられます。
#figure( image("../img/nyan4.jpg", width: 3cm), caption: [参照されるねこ],) <nyan-image>#align(center)[@nyan-image をこのように参照できます。]
関数
Typst における関数
#
で始まる文は関数を表します。
内蔵の関数や以下のように#let
を使って自作の関数を定義し利用することができます。
#let test(body) = { [渡された文字列は#body] }- #test("テスト")- #test[#text(fill: red, weight: "bold", [test])]
引数の渡し方は複数あります。以下はbody
に引数が割り当てられます。
#test("テスト")
このようにマークアップ文全体をを渡すこともできます。これも自動的にbody
に割り当てられます。
#test[#text(fill: red, weight: "bold", [test])]
以下のように複数の引数がある場合も自動的にbody
に割り当てられます。引数の初期値も設定でき省略時にこれが参照されます。
#let test2( var: false, body) = { if var { [varはTrueです。] } else { [varはFalseです。] } body}
- #test2[引数]- #test2(var: true)[引数]
{ }
で区切られたブロック内はコードブロックなので関数に#
をつけずに呼び出します。マークアップ文を使う場合は[ ]
で囲みます。
- コンテントブロック(Content block)
[...]
: 関数は#
をつけて使う。#text(fill: red)[*Hey* there!]
- コードブロック(Code block)
{...}
: 関数は#
をつけずに使う。text(fill: red)[*Hey* there!]
マークアップ関数の一覧
目次
// target で作りたい目次の要素を指定する。`depth`は目次を作るレベルの深さを数値で指定する。#outline(target: heading, depth: 2)
水平方向のスペース
#h(1fr)
垂直方向のスペース
#v(0.2em)
中央揃え
#align[center](テキスト)
取り消し線
#strike[文字列]
下線
#underline[文字列]
大文字化
#upper[apple]
小文字化
#lower[APPLE]
スモールキャピタル
#smallcaps[Smallcaps]
上付き文字
2#super[2]
下付き文字
2#sub[2]
ハイライト
#highlight[文字列]
引用
#quote[block: true, attribution: [名前]](引用文字列)
囲み
#box[stroke: 0.5pt + black, outset: 0.3em](文字列)
ダミーテキストを 10 単語生成
#lorem(10)
今日の日付を取得
#datetime.today()
水平線
#line(stroke: 1pt + black, start: (5%,0%), end: (95%,0%))
改ページ
#pagebreak()
この関数から下は次のページになります。
スクリプト
Typstはスクリプト言語としての機能も持っています。変数、ループ、条件分岐などを利用して、コンテンツを動的に生成できます。
for 関数
#let users = ("Alice", "Bob", "Charlie")#for user in users { [こんにちは、#text(fill: red)[#user] さん!]}
if 関数
#let a=32#let b=64#align(center)[#if a < b { [aはbより小さいです。] }]
単純な画像表示関数を定義
#let nyan(size: 5cm) = {image("../img/nyan4.jpg", width:size)}#align(center)[#nyan(size: 2.5cm)]
image 関数
#image()
関数で画像を挿入します。#figure()
と組み合わせることで、キャプションや図番号を付与できます。
#figure( image("../img/nyan4.jpg", width: 3cm), caption: [ねこです])
図形関数
#rect()
や #circle()
などの関数で、基本的な図形を簡単に描画できます。
#align(center)[#grid(columns: 3, [#square(size: 1cm, fill: blue)], [#circle(radius: 0.5cm, fill: red)], [#polygon( (0cm, 1cm), (0.5cm, 0cm), (1cm, 1cm), fill: green, )])]
色の指定方法
関数の引数にcolor
がある場合、色を指定できます。fill
は塗りつぶしです。stroke
は枠線です。3pt + black
のように+
で属性を追加できます。
#rect(fill: red, stroke: 3pt + black, radius: 1em)#line(stroke: 3pt + green, start: (0%,0%), end: (50%, -4em))
rgb("#000000")
のようにRGB値で指定することもできます。透明度を含めたrgb("#000000aa")
のような指定方法もあります。
#grid(columns: 2,polygon( (0cm, 1cm), (0.5cm, 0cm), (1cm, 1cm), fill: rgb("#0000cc"), ),circle(radius: 0.5cm , fill: rgb("#0000cc55")))
グラデーションを指定するにはgradient
を利用します。グラデーションの種類をgradient.linear
のように指定します。
グラデーションの方向はangle: 90deg
のように角度で指定します。
#rect(fill: gradient.linear(red, blue,angle: 90deg), radius: 1em,)
table 関数
#table()
関数で柔軟な表を作成できます。
#table( columns: (auto, 1fr, 1fr), stroke: 0.5pt, align: (left), [*No.*],[*項目*],[*説明*], [1], [Typst], [組版システム], [2], [Markdown], [軽量マークアップ言語], [3], [LaTeX], [組版システム],)
段組
#columns()
関数で多段組を実現できます。#colbreak()
で段を区切ります。
#import "@preview/roremu:0.1.0": roremu#columns(2)[ #roremu(100) #colbreak() #roremu(100)]
このように長い文章は折り返され、指定した段数で区切られます。
#lorem()
(ここで使っているのは外部パッケージの#roremu
)は指定された文字数の例文を表示する関数です。
grid 関数
#columns()
関数よりも柔軟なレイアウトをしたい場合利用できます。
#set rect( inset: 8pt, fill: rgb("e4e5ea"), width: 100%, )
#grid( columns: (60pt, 1fr, 2fr), rows: (auto, 60pt), gutter: 3pt, rect[Fixed width, auto height], rect[1/3 of the remains], rect[2/3 of the remains], rect(height: 100%)[Fixed height], grid.cell( colspan: 2, image("../img/nyan4.jpg", width: 100%), ), )
footnote 関数
脚注をつけることができます。
Astro#footnote("SSGだけでなくSSRもできる静的サイトジェネレーター")もありますがTypstは使えません。
要素の移動や回転
ある要素を移動させたい場合以下のような関数が利用できます。他の要素の位置に注意して使用してください。
特定位置への要素の表示
#place
関数を使用することでページ上の特定位置に要素を表示することができます。要素の中ならばその要素の相対位置になります。
#block
関数でブロック領域を作りその中で#place
関数を使ってみます。灰色の部分がブロック領域です。
#let mybox(body, color: white)={ box(stroke: 1pt, fill: color, inset: 0.5em, body)}#block(height: 3cm, width: 100%, fill: gray)[#place(top+right)[#mybox([右上です], color: aqua)]#place(top+center, dy: 2em)[#mybox([中央上から2文字下です], color: yellow)]#place(bottom+right, dx: -0.5cm, dy: -0.5cm)[#mybox([右下から左に0.5cm上に0.5cmの位置です], color: green)]]
現在位置から指定位置への要素の移動
#move
関数を使用すると要素の現在位置から指定された位置に要素を移動できます。
#let mybox(body, color: white)={ box(stroke: 1pt, fill: color, inset: 0.5em, body)}#move(dx: 3cm)[#mybox([移動した要素], color: teal)]#rect(inset: 0pt, move( dx: 6pt, dy: 6pt, rect( inset: 8pt, fill: white, stroke: black, [`#rect`の中で入れ子になって移動しています] )))
要素を回転させる
#rotate
関数を利用して要素を指定した角度に回転させることができます。
#set text(fill: white)#align(center,block(fill: black, width: 100%, [ #place(left + horizon, dx: 6em)[ #rotate(-70deg)[*ローテート!*]] #rotate(-10deg)[ #rotate(45deg)[#block(radius: 100%, clip: true)[ #image("../img/nyan4.jpg", width: 3cm) ] ] このように要素が #rotate(30deg)[すべて] 指定された角度`deg`に回転します。 #rotate(25deg)[ #move(dx: 7em, dy: -8em)[入れ子になっていても大丈夫です。] ] ] ]))
ファイルの分割
#for
関数と #include
関数を使って分割されたファイルを読み込みします。
複数のファイルに分割することで項目ごとに編集管理しやすくできます。
// ファイルのパスを配列にする#let chapters = ( "content/text-format.typ", "content/heading.typ", "content/list.typ", "content/codeblock.typ", "content/formula.typ",)
= ここからchaptersが配列順にそれぞれ並ぶ
#for chapter in (chapters) { include(chapter)}
リストのループ
別ファイルからリストを読み込み#for
ループを利用して用語リストとテーブルに表示してみます。#table
関数では..#for
と書くことで配列を渡します。
import される Typst ファイル
#let locales=( ( name:"日本",str:"首都は東京です。",), ( name:"アメリカ",str:"首都はワシントンD.C.です。",), ( name:"フランス",str:"首都はパリです。",), ( name:"イギリス",str:"首都はロンドンです。",), ( name:"ドイツ",str:"首都はベルリンです。",), ( name:"中国",str:"首都は北京です。",), ( name:"韓国",str:"首都はソウルです。",),)
コード
#import "../import/locales.typ": locales
#for c in locales { terms.item(text(c.name),text(c.str))}
#table( columns: (auto, 1fr), stroke: 1pt+gray, align: (left), [*国名*],[*首都*], ..for (name,str) in locales { (name,str) })
外部データの読み込み
Typstは以下のデータを読み込むことができます。
- cbor
- csv
- json
- read
- toml
- xml
- yaml
読み込むデータ
[ { "name": "ねこ", "path": "../img/e670ceab9f15d95ce92d718ba046aaac3142aac3.jpg", "description": "クリスマスにゃんこだにゃん" }, { "name": "きつね", "path": "../img/2022-0106-01.jpg", "description": "見せつけてくるきつね" }, { "name": "ティファ", "path": "../img/c2b7d960c6205cb3d973fe0b3c51b477ecf75202.jpg", "description": "FF担当" }, { "name": "ブリジット", "path": "../img/1e4697102be08c9174a60f109b4e6558e838bf81.jpg", "description": "格ゲー" }]
コード
#let columnimage(data) = columns( 2, for img in data{ block( width: 100%, clip: true, square( radius: 1em, width: 100%, height: auto, inset: 0.5cm, fill: if img.name == "ねこ" { yellow } else { aqua }, highlight(fill:rgb("#77ffff"), text(strong(underline(img.name)))) +"\n"+ text(img.description) +align(center, rotate(8deg, image(img.path, fit:"cover", width: 80%))), ) ) })
#columnimage(json("../json/animals.json"))
スタイリング
#set を利用したスタイリング
#set
を利用して要素のスタイルを設定できます。
par
は段落を設定します。text
はテキストを設定します。
#import "@preview/roremu:0.1.0": roremu#set par(spacing: 1em, leading: 1em)#set text(weight: "bold", size: 0.8em)#columns(2)[#roremu(400)]
#set
の適用範囲はファイルの終端かブロックの終わりまでです。
#[#set list(marker: [★], indent: 2em)- ここは適用される]- こちらは適用されない
ページのスタイリング
#set page()
ルールで、用紙サイズ、余白、ヘッダー、フッターなどを設定できます。
#set page( paper: "a4", margin: (x: 2.5cm, y: 3cm), header: align(right)[Typst Manual], footer: align(center)[#counter(page).display("1")])
#show を利用したスタイリング
#show
を使用すると、要素のスタイルを細かくカスタマイズできます。
#[...]
は無名のコンテントブロックです。
#[ #show heading: set text(fill: red) = レベル1 == レベル2 === レベル3]ブロックの外なので通常です= レベル1== レベル2=== レベル3
コロン:
の後に関数をつけてさらに細かくカスタマイズすることもできます。
it
は要素の実体を表します。
#[ #show heading: it => [ #set text(fill: green) #align(center)[#it] ]= レベル1== レベル2=== レベル3]
要素の中の特定要素をwhere
で指定することもできます。
#[ #show heading.where(level: 2): it => [ #set text(fill: fuchsia) #align(center)[#it] ] = レベル1 == レベル2 === レベル3]
単純な文字列の置き換えは以下の方法が利用できます。
#block(stroke: 1pt + gray, inset: 5mm)[ #show "日本語": "nihongo" 日本語という文字列がこのように変化しています。]
#show
にregex
を利用して特定文字列を対象にすることもできます。
#block(stroke: 1pt + gray, inset: 5mm)[ #show regex("apple"): [ジョブズ] apple computer]
#block(stroke: 1pt + gray, inset: 5mm)[ #show regex("ジョブズ"): it => [ #rotate(30deg)[#strong(it)] ] apple ジョブズ]
#show:と with を利用して外部のテンプレートを利用する
#set
や#show
で設定したスタイリングはそのファイル内かブロック内で有効になりますが、
これを外部ファイルに記述してそれを#import
して利用することができます。
layout.typ
というファイルを作成し、#let
を使って名前はなんでもよいのですがlayout
を定義します。
そして=
に続くコードブロック内でスタイリングをします。
引数は自由に定義して問題ありません。コロン:
の後に続く要素はデフォルト値になります。
#let layout( body, title: "Typstの使い方", author: "ubanis", pagesize: "a5", column: 1, fontname: "Noto Sans CJK JP", fontsize: 8pt, language: "ja",) = { // ドキュメントの設定 set document( title: _title, author: author, ) // ページの設定 set page( fill: none, paper: pagesize, flipped: false, binding: left, margin: ( top: 25mm, bottom: 15mm, inside: 15mm, outside: 15mm, ), columns: column, numbering: "1", ) // フォントの設定 set text( font: fontname, lang: language, size: fontsize, ) // 本文 {//このコードブロックはなくてもよい body }
}// layoutの終わり
必ず関数の最後の方にbody
を入れてください。
そしてこれを本体の文書から#import
を使って読み込みます。
#import "layout.typ"
の後に続く:
はそのファイルから読み込む関数を指定します。*
は全てを読み込むことを表しています。
特定の関数を読み込むならば#import "layout.typ": layout
のように指定します。
そしてその下で#show
に:
を付けてlayout.with
関数を呼び出します。
#import "layout.typ": *
#show: layout.with( title: "いろいろできそうなTypstで遊ぼう", author: "ubanis", pagesize: "a5", column: 1, fontsize: 9pt, fontname: "BIZ UDPGothic",)
= ここから本文です
`layout`で設定した要素がここに適用されます。この部分全てがlayoutの引数`body`に入っています
- リスト1- リスト2- リスト3
コードにあるように#show: layout.with(...)
以降のファイル内はlayout.typ
の#layout
内で設定したスタイルが適用されます。
他に#include
関数もあり、別ファイルの内容がそのまま同じファイル内に取り込まれるため以下のようにした場合も別ファイルにスタイルが適用されます。
#show: layout.with(...) // 省略
= ここから本文です
`layout`で設定した要素がここに適用されます。この部分全てがlayoutの引数`body`に入っています
- リスト1- リスト2- リスト3
#include "next.typ" //このファイルの中身もlayoutのスタイルが適用される。
layout.typ
のbody
の前にコンテンツを書けばそれはこの本文の前に置かれます。#outline
を使って目次を載せたり引数を活用して表紙を作成したりするのもよいでしょう。
// ここまで省略 // タイトルページ { v(3cm) set text(size: 1.5em) align(center, title) align(bottom+center, author) pagebreak()//改ページ } // 目次 { set text(size:0.9em) outline( title: [目次], depth: 2, indent: 2em, ) pagebreak()//改ページ } // 本文 {//このコードブロックはなくてもよい body }}// layoutの終わり
外部パッケージ
codly コードブロック装飾

以下のようにインポートと初期化を行うことでコードブロックのスタイルが変化します。
#import "@preview/codly:1.3.0": *#import "@preview/codly-languages:0.1.1": *#show: codly-init.with()
Rustの例
一部分のハイライト
#codly(highlights: ( (line: 4, start: 2, end: none, fill: red), (line: 5, start: 13, end: 19, fill: green, tag: "(a)"), (line: 5, start: 26, fill: blue, tag: "(b)"),))
Rubby ルビをふる

初期化
#import "@preview/rubby:0.10.2": get-ruby
#let ruby = get-ruby( size: 0.5em, // Ruby font size dy: 0em, // Vertical offset of the ruby pos: top, // Ruby position (top or bottom) alignment: "center", // Ruby alignment ("center", "start", "between", "around") delimiter: "|", // The delimiter between words auto-spacing: true, // Automatically add necessary space around words)
ルビのふりかた
ルビはRubbyを#ruby[り|よう][利|用]する。
wrap-it 回り込み

初期化
#import "@preview/wrap-it:0.1.1": wrap-content
使用例
#let wrap_fig = [ #figure( image("../../img/nyan4.jpg",width:5cm), caption: [画像],)<fig:nyan4>]
#let wrap_body = [#lorem(100)]#wrap-content(wrap_fig, wrap_body,align: bottom + left, column-gutter: 1em)* @fig:nyan4 *
showybox 様々なボックス描画

様々な枠線を作ることができるパッケージです。
初期化
#import "@preview/showybox:2.0.4": showybox
使用例
#showybox( [Hello world!])
#showybox( frame: ( border-color: red.darken(50%), title-color: red.lighten(60%), body-color: red.lighten(80%) ), title-style: ( color: black, weight: "regular", align: center ), shadow: ( offset: 3pt, ), title: "Red-ish showybox with separated sections!", lorem(20), lorem(12))
#showybox( frame: ( dash: "dashed", border-color: red.darken(40%) ), body-style: ( align: center ), sep: ( dash: "dashed" ), shadow: ( offset: (x: 2pt, y: 3pt), color: yellow.lighten(70%) ), [This is an important message!], [Be careful outside. There are dangerous bananas!])
付録
地震情報を掲載する
気象庁の地震情報を取得できるアドレス https://www.jma.go.jp/bosai/quake/data/list.jsonからjsonファイルを取得してテーブル表示します。
json ファイルの取得
wget https://www.jma.go.jp/bosai/quake/data/list.json
リストは1000件と長すぎるため表示する範囲はslice
で限定しました。
#let earthquake-list(num) = { set text( size: 0.8em, ) let earthquakes = json("json/list.json") [== 最近の地震 #num 件] table( stroke: 0.5pt, align: left, columns: (auto,auto,auto,auto), [*地震発生時刻*], [*震源地*], [*マグニチュード*], [*最大震度*], ..for (at,anm,mag,maxi) in earthquakes.slice(0,num){ (at,anm,mag,maxi) } )}
#earthquake-list(10)