0
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 0.13.0 の段落の話を理解しようとした

Last updated at Posted at 2025-03-03

これまでLaTeXを利用していましたが、数週間前にTypstというツールの存在を知りました。実際、LaTeXについてはあまり理解していないまま使用しており、TypstはLaTeXよりも表記が簡潔で、コンパイルなしで利用できるとのことで、Typstでの文書作成を始めることにしました。

しかし、LaTeXでも必要な部分だけを取り入れて文書を作成しており、Typstでも適当に文章を作成していたため、当然のことながら多くの不明点が生じました。その後、さらにアップデートが行われ、段落に関する新しい設定が追加されたとのことです。

そこで、公式ドキュメントをしっかりと読み込み、挙動を確認し、その結果を記録することで理解を深めたいと考え、この記事を作成しました。

公式ドキュメントの個人的な理解

0.13.0の公式ドキュメントを雑に翻訳してみました。

段落

段落はインラインレベルの要素を集めたものであり、インラインレベルの要素に含まれるものは

  • 文章
  • 水平方向の空き : h(長さ)
  • box関数 : box()
  • インライン数式 : $数式$

などがある。
段落を分ける方法としては空白行を入れるかparbreak()を入れる。

段落の途中で block() などのブロックレベルのコンテンツを入れると段落は中断される。ブロックレベルのコンテンツは

  • 2つ以上の段落parbreak() 要素を含む文章(3/9編集、ご指摘がありました。)
  • block関数 : block()
  • place関数 : place()
  • 独立した数式 : $ 数式 $

などがある。

3/9追加
空白行や parbreak() などが含まれない文章は段落とみなされない。
空白行や parbreak() などが含まれる文章になると文章全体が段落とみなされブロックレベルのコンテンツになる。

par()は主にset par()の形で用いられるが par()[] などといった形で [] の中に段落を入れることができるが ブロックレベルのコンテンツを入れることができない。

par()[]par(引数, []) par(引数, "") といった形でも表せるますがそれぞれの記法のメリットは未だにわかりません...

box()block()

box() の中に block() などのブロックレベルのコンテンツを入れるすることで文章の途中にブロックレベルのコンテンツを入れることができる。
逆に block() の中に box() などのインラインレベルの要素を入れることで段落の外に記載できる。次のセクションの「正式な段落」で普通に段落を入れた時との違いが説明されている。

「正式な段落」という表記は「Typst 0.13.0 の内容を早めに深堀り」を参考にしました。

段落の条件

段落はインラインレベルの内容を集めたものであるが、見出しやキャプションといった段落に含めるべきではないものもある。
ルールとして

  • 何にも属していないテキストは全て段落に含まれる
  • ブロックなどに含まれているテキストは、その中にブロックレベルのコンテンツが含まれている場合にのみ段落になる
  • インラインレベルのコンテンツだけだと段落は作られない

打ち込んでいる最中、それが段落になるのかはすぐにはわからない。

  • set par() の内容( first-line-indent など)は正式な段落にのみ適用
  • ...

このルールを元に段落をカスタマイズすることができる。

スクリーンリーダーやHTMLの話が出てきましたが、現状使う予定がないため割愛しました。

試してみた

実際に書いてみて挙動を確認しました。
普段はVS Codeを使用していますが、試験的にWebアプリ版で作成してみました。

段落を変えようとしてみた

空白行や parbreak() を入れると段落が変わったと認識されているみたいです。 \ を入れると改行はするが段落は変わりませんでした。
box()インラインレベルの要素のため文章中に入れても段落は変更されません。その一方で block()ブロックレベルのコンテンツであるため段落が中断され、 再開後は段落が変わりました。(正直 box()block() もよくわからないまま適当に使っていました...)
par()[] の形で1つの段落は書けましたが、2段落目を書き始めるとエラーを吐いたので段落は2つ以上あるとブロックレベルのコンテンツとみなされてるのでしょう。これは個人の推測なのでテキトー言っていたらすみません。

改行などの挙動
test.typ
#set text(lang: "ja", font: "Harano Aji Mincho", 10pt)
// 段落が変わったときに一文字分インデントされるようにする
#set par(first-line-indent: (amount:1em, all: true))

吾輩は猫である。名前はまだ無い。

どこで生れたかとんと見当がつかぬ。 #parbreak() 何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。 \ 吾輩はここで始めて人間というものを見た。

test.png

box・blockの挙動
test2.typ
#set text(lang: "ja", font: "Harano Aji Mincho", 10pt)
// 段落が変わったときに一文字分インデントされるようにする
#set par(first-line-indent: (amount:1em, all: true))

吾輩は猫である。名前はまだ無い。

どこで生れたかとんと見当がつかぬ。#box(rect(width: 50%, height: 1em, fill:gray))何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。 #block(rect(width: 50%, height: 1em, fill:gray))吾輩はここで始めて人間というものを見た。

test2.png

parの別記法
test3.typ
#set text(lang: "ja", font: "Harano Aji Mincho", 10pt)
// 段落が変わったときに一文字分インデントされるようにする
#set par(first-line-indent: (amount:1em, all: true))

吾輩は猫である。名前はまだ無い。

どこで生れたかとんと見当がつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。吾輩はここで始めて人間というものを見た。しかもあとで聞くとそれは書生という人間中で一番獰悪な種族であったそうだ。

#par(hanging-indent: 1em)[
  吾輩は猫である。名前はまだ無い。
  どこで生れたかとんと見当がつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。吾輩はここで始めて人間というものを見た。しかもあとで聞くとそれは書生という人間中で一番獰悪な種族であったそうだ。
]

test3.png

boxの中にブロックレベルのコンテンツ、blockの中にインラインレベル要素を入れてみた

インライン数式とブロック数式を用いて試してみるとインライン数式が段落から外れたり、ブロック数式を文章の途中に入れることはできました。そのままだとレイアウトが終わっているのでなんらかの調整が必要そう。

実際の挙動
test4.typ
#set text(lang: "ja", font: "Harano Aji Mincho", 10pt)
// 段落が変わったときに一文字分インデントされるようにする
#set par(first-line-indent: (amount:1em, all: true))

sinc関数とは、正弦関数をその変数で割って得られる関数であり$sinc(x) = (sin x)/x$と表される。

sinc関数とは、正弦関数をその変数で割って得られる関数であり$ sinc(x) = (sin x)/x $と表される。

sinc関数とは、正弦関数をその変数で割って得られる関数であり#block($sinc(x) = (sin x)/x$)と表される。

sinc関数とは、正弦関数をその変数で割って得られる関数であり#box($ sinc(x) = (sin x)/x $)と表される。

test4.png

正式な段落

上記の「Typst 0.13.0 の内容を早めに深堀り」でわかりやすく解説されています。

自分がやりたかったこと

自分がTypstを使う目的は数学の教材の作成です。
下記の画像(とある県の高校入試)のようなレイアウトを作ろうとしたときに

  • 見出しを大〇問に設定したい
  • それ以降は一文字分ずつインデントしていきたい
  • 最初の段落はラベルと一文字分、改行は左端から一文字分、新しい段落は左端から二文字分開けたい

という問題が発生しました。

SnapCrab_NoName_2025-3-4_7-35-40_No-00.png

問題番号の書式は後で設定し直すとして、まずは空白の制御を試みます。
下記の適当に作った問題を元に見出し、enum() の設定を変えてみます。

SnapCrab_NoName_2025-3-4_7-46-24_No-00.png

Ana() は穴埋めのboxを高さ5mmで適当に作りました。

デフォルト

コード
test5.typ
#set text(lang: "ja", font: "Harano Aji Mincho", 10pt)
#show math.equation: set text(
  lang: "ja",
  font: (
    (name: "New Computer Modern Math", covers: "latin-in-cjk"),
    "Harano Aji Mincho"
  ),
  size: 10pt
)
// 段落が変わったときに一文字分インデントされるようにする
#set par(first-line-indent: (amount:1em, all: true))
// 正式な段落を確かめる
#show par: set text(fill: blue)
#set heading(numbering: "1")
#set enum(
  numbering: "(1)",
  spacing: 1.2em
)

test5.png

問題点として

  • 見出しの空白が目標と違いすぎる
  • 小問のベースラインがおかしい
  • ボックスレベルの数式が終わった後の段落が新しい段落とみなされ、インデントが適用されている

ということがあげられます。
知識のない自分はとりあえず見出しを #set heading()#show heading() でなんとかならないか試してみます。
また、小問はList and enum markers are not aligned with the baseline of the item's contentsに載っている解決策をそのまま流用することにします。

見出しと小問を改造

追加したコード
test6.typ
#set heading(
  hanging-indent: 1em,
  supplement: none,
  outlined: false,
)
#show heading: set heading(
  numbering: n => box(
    align(center + bottom)[第#h(1em)#n#h(1em)問],
    // stroke: 1pt,
    height: 1em,
    width: 5em,
  ),
)
#show heading: it => {
  set text(
    size: 10pt,
    weight: "regular",
  )
  context counter(heading).display(it.numbering)
  h(1em)
  it.body
}

#let counting-symbols = "1aAiI一壹あいアイא가ㄱ*"
#let consume-regex = regex("[^" + counting-symbols + "]*[" + counting-symbols + "][^" + counting-symbols + "]*")

#show enum.item: it => {
  if it.number == none {
    return it
  }
  // The generated terms is not tight
  // So setting `par.spacing` is to set the result enums' spacing
  let spacing = if enum.spacing != auto {
    enum.spacing
  } else if enum.tight {
    par.leading
  } else {
    par.spacing
  }
  set par(spacing: spacing)


  let new-numbering = if type(enum.numbering) == function or enum.full {
    numbering.with(enum.numbering, it.number)
  } else {
    enum.numbering.trim(consume-regex, at: start, repeat: false)
  }
  let current-number = numbering(enum.numbering, it.number)
  context {
    let hanging-indent = measure(current-number).width + terms
      .separator
      .amount

    set terms(hanging-indent: hanging-indent)

    terms.item(
      strong(delta: -300, numbering(enum.numbering, it.number)),
      {
        if new-numbering != "" {
          set enum(numbering: new-numbering)
          it.body
        } else {
          it.body
        }
      },
    )
  }
}

test6.png

大問のラベルと問題文に一文字分の空白を開けることに成功しましたが、heading.numberingheading.body を使って無理やり出力したため、見出し部分が段落判定されています!
この記事を作る前の自分は何が起きているかわかりませんでしたが、記事を書いている中で何となく理解できた気がします。

また小問も 1. の表記にするとベースラインが合うようになりましたが、 + 表記だと高さが変わらないです。
コードを見たところ、無理やり terms() にしているみたいで set enum の空白設定がうまくいっていないみたいです。

終わり

今回はいったんここまでにします。とりあえず段落については多少理解が深まったんじゃないかなと。
目標に向けての課題がたくさんあるため、Typstについてもう少し勉強したいと思います。

3/6追記
正直見出しを無理やり大問にするくらいなら自分で関数を作った方が楽だなと思い、その関数内でインデントを調整することにしました。
= の後に書くだけで見出しを表せるというのに憧れていましたが、そこにこだわることができるほど理解していないので...
よく考えたらLaTeXの時もセクションはカウンタだけ借りて、自作の関数で表してたことを思い出しました。

0
1
4

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
0
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?