Typstにはいわゆる糖衣構文が多々存在し,これによって文書作成に最適化された文法が提供されている.しかし,それ故にTypstの文法や構文の背景を正しく理解しないと,いざ何か込み入った事をしようという時に分からなくなってしまう.私もその一人.
ここでは,Typstの文法や構文,特に#show
句について,糖衣構文の観点から解説をおこない,コードの等価変換を用いて分かりやすい形での理解を試みる.
Syntax:糖衣構文だらけのTypst
様々なSyntaxは,関数呼び出しの糖衣構文であると捉えられる.
Typst文書の本質は,文字,見出し,コードなどのcontent
を作成する関数呼び出しの集合体である.
それが強力な糖衣構文を通じて,Markdownのような軽いマークアップ言語のように見えているだけである.
例えば,テキストを表示するには
#text("あいうえお")
という関数呼び出しが必要だが,これは
あいうえお
と等価である.
見出しの等価構文:
#heading("小見出し", level: 2)
== 小見出し
モード
@zr_tex8r 様からのコメントを基に内容を変更しました.
Typstのコーディングは大きく分けて「Code」,「Math」,「Markup」の3つのモードに分けられる.特に「Code」と「Markup」がよく混同してしまうので,ここで区別を明確にする.
-
Markupモード
- Markup(文字,見出し等)を書くのに特化している
- Markupは無印で記述できる.
- Codeは冒頭に#を付ける
-
#{}
でCodeモードの領域を誕生させる
-
Codeモード
- Code(変数定義や関数呼び出し)を書くのに特化している
- Markupは
[]
で囲む - Codeは無印で記述できる
-
[]
でMarkupモードの領域を誕生させる
次の例はMarkupモード内での書き方である:
// Markupモード
まーくあっぷ // Markupは無印
#let x = [あいうえお] // Codeは#付き,この行はCodeモードなのでMarkupは[]で囲む
#square() // Codeは#付き
#x // "Markupの展開"というCodeなので#付き
#{}
を書いてMarkupモードの中にCodeモードの領域を誕生させる.その領域中に上記を移すには,次のように書く:
// Markupモード
#{
// Codeモード
[まーくあっぷ] // Markupは[]で囲む
let x = [あいうえお] // Codeは#無印
square() // Codeは無印
x // "Markupの展開"というCodeなので無印
}
変な例:MarkupモードとCodeモードのマトリョシカ
// Markupモード
#{
// Codeモード
[
// Markupモード
#{
// Codeモード
{
// もう一回Codeモードだにょーん🪆
}
}
]
}
content
タイプの位置パラメータの指定方法
次のような,任意の色で自己紹介を表示する関数を考える:
#let selt-introduction = (name, livesin, fill: orange) => {
let body = [私は #name といいます。#livesin に住んでいます。]
text(body, fill: fill)
}
#selt-introduction([おみや], [関東], fill:blue) // 青文字で "私はおみやといいます。関東に住んでいます。"
ここで,name
とlivesin
はいずれもcontentタイプの位置パラメータである.contentタイプの位置パラメータは,次のように,()の外に追いやることができる:
- #selt-introduction([おみや], [関東], fill:blue)
+ #selt-introduction(fill:blue)[おみや][関東]
この等価変換によって,コードがちょっとスッキリする.例えば次の2行は等価である:
- #rect([こんにちは])
+ #rect[こんにちは]
show 文法:文書全体にメスを入れる
元々あった文章に対して,「私は昨日...[元々あった文章を黄色の四角で囲む]...という夢を見たんだ」という表示にする加工を行うことを考える:
// 元々あった文書...をまるっと`motomoto`の中にぶち込む
#let motomoto = [
横浜駅周辺は大変混み合っていた。いくつか店を回ったのだが、予約をしていない我々がすぐ着席できるところは無かった。
仕方がなかったので横浜中華街へ行き、話題沸騰中のあのお店に行ってみた。
]
// メスを入れる
私は昨日...
#rect(motomoto, fill: yellow.lighten(50%), width: 100%)
...という夢を見たんだ
上記は#show
句を使った次のコードと等価である:
// メスを入れる
#show: motomoto => {
[私は昨日...]
rect(motomoto, fill: yellow.lighten(50%), width: 100%)
[...という夢を見たんだ]
}
// 元々あった文書
横浜駅周辺は大変混み合っていた。いくつか店を回ったのだが、予約をしていない我々がすぐ着席できるところは無かった。
仕方がなかったので横浜中華街へ行き、話題沸騰中のあのお店に行ってみた。
motomoto
に元々あった文書全体が代入され,実行される.#show
句によって,元々あった文章全体へ介入する処理を行える.#show
句を使った方では,元々あった文書に対して何も触りも移動もせずに,加工できている.
#show
句は,元々あった文書に対し,そのスタイルや内容へ介入・改変する能力を持つ.この機能は,テンプレートを作ったり(後述),複雑なスタイルの設定などに活用されている.
show 文法:条件に合う要素だけに対しメスを入れる
先ほどの例では文書全体へ介入していた.ここでは,条件に合う要素だけを取り出して,それらに介入することを考える.#show
の直後に取り出したい条件を書く.
ここでは,「ポラーノの広場」の一節に対し,カタカナ単語だけをオレンジにする処理を考える.
安直な人は次のように書くだろう:
あの#text(fill: orange)[イーハトーヴォ]のすきとおった風、夏でも底に冷たさをもつ青いそら、うつくしい森で飾られた#text(fill: orange)[モリーオ]市、郊外のぎらぎらひかる草の波。
またそのなかでいっしょになったたくさんのひとたち、#text(fill: orange)[ファゼーロ]と#text(fill: orange)[ロザーロ]、羊飼の#text(fill: orange)[ミーロ]や、顔の赤いこどもたち、地主の#text(fill: orange)[テーモ]、山猫博士の#text(fill: orange)[ボーガント]・#text(fill: orange)[デストゥパーゴ]など、いまこの暗い巨きな石の建物のなかで考えていると、みんなむかし風のなつかしい青い幻燈のように思われます。では、わたくしはいつかの小さなみだしをつけながら、しずかにあの年の#text(fill: orange)[イーハトーヴォ]の五月から十月までを書きつけましょう。
ただこれだと,カタカナの単語の数だけテキストをオレンジにする処理を入れないといけないので面倒臭い.
そこで,「カタカナ」という条件に合う要素だけを取り出し,それぞれの文字色をオレンジにする処理を実装する:
#show regex("[ァ-ヶー]+"): katakana => text(katakana, fill: orange)
あのイーハトーヴォのすきとおった風、夏でも底に冷たさをもつ青いそら、うつくしい森で飾られたモリーオ市、郊外のぎらぎらひかる草の波。
またそのなかでいっしょになったたくさんのひとたち、ファゼーロとロザーロ、羊飼のミーロや、顔の赤いこどもたち、地主のテーモ、山猫博士のボーガント・デストゥパーゴなど、いまこの暗い巨きな石の建物のなかで考えていると、みんなむかし風のなつかしい青い幻燈のように思われます。では、わたくしはいつかの小さなみだしをつけながら、しずかにあの年のイーハトーヴォの五月から十月までを書きつけましょう。
regex("[ァ-ヶー]+")
という条件を入れた.これによって,katakana
にはカタカナのcontent
が入ってくきて,文字色をオレンジにする処理が行われる.
#show
の直後に条件を入れる.この「条件」のことはselector
と呼ばれ,次のようなものがサポートされている:
- 対象の
content
を作った関数.例えば「見出し」はheading
. - 正規表現
- ラベル
等.
show-with構文を解析する
#show
句に関しては,これが一番意味不明だった.
先ほどの,カタカナをオレンジにする処理は次のようにも書き表すことができる:
#show regex("[ァ-ヶー]+"): text.with(fill: orange) //カタカナに対し,その文字色をオレンジにする
#show
と.with
が出てくるので,これを"show-with"構文と呼ぶことにしよう.show-with構文は,主にテンプレートの初期化処理などでよく使われている(例:kunskap).
まず,関数.with
はデフォルト引数を変更した関数を返すものである.例えば,デフォルトでオレンジのテキストを表示するような関数orange-text
は次のように作られる:
#let orange-text = text.with(fill: orange)
オレンジのテキストを表示する処理として,次に示すものは等価である:
- #text(fill:orange)[やっほ]
+ #let orange-text = text.with(fill: orange)
+ #orange-text[やっほ]
よって,例のカタカナの色をオレンジにするshow-with構文は,次のように,#show
句にて関数を置いている書き方だと言える:
#let orange-text = text.with(fill: orange)
#show regex("[ァ-ヶー]+"): orange-text
そして,Typstにおける関数はJavaScriptの関数オブジェクトのように機能しており,#where
句においては次の3つは等価である:
#show regex("[ァ-ヶー]+"): orange-text //カタカナに対し,`orange-text`関数で表示する
#show regex("[ァ-ヶー]+"): katakana => orange-text(katakana) //得られたカタカナを`orange-text`関数の引数に入れる
#show regex("[ァ-ヶー]+"): katakana => text(katakana, fill:orange) //得られたカタカナを`text`関数の引数に入れ,加えて文字色をオレンジにしt
show-with構文の例:kunskapテンプレート
kunskapとは,文書の冒頭や各ページのヘッダをいい感じに整備してくれるライブラリである.
ドキュメントによると,次のようにshow-with構文を用いて使うことが推奨されている:
#import "@preview/kunskap:0.1.0": *
#show: kunskap.with(
title: "糖衣構文だらけのTypst文法",
author: "Mya-Mya",
date: "今日",
header: "Qiita記事"
)
ここから本文だよ〜〜
もちろん,show-with構文を使わなくても使える.例えば次のように#show
だけでもいける:
#import "@preview/kunskap:0.1.0": *
#show: body => kunskap(
body,
title: "糖衣構文だらけのTypst文法",
author: "Mya-Mya",
date: "今日",
header: "Qiita記事"
)
ここから本文だよ〜〜
さらに,#show
を使わないような書き方もいける:
#import "@preview/kunskap:0.1.0": *
#kunskap(
title: "糖衣構文だらけのTypst文法",
author: "Mya-Mya",
date: "今日",
header: "Qiita記事",
)[
ここから本文だよ〜〜
]
ただ,コードの煩わしさからいうと,show-with構文が最も短くて楽だ.
おわりに
Typstは文書作成用に最適化された言語であるが故,その仕組みが見えづらい.特に自分で凝ったデザインの文書を作ろうなどとなると,Typstの文法と構文をよく理解していなければならない.特に#show
句,その中でも特にshow-where構文はユニークな背景に由来する構造を持っているため,よく解釈する必要がある.
その他の詳しいことはdocsを参照されたい.