Skip to content

Instantly share code, notes, and snippets.

@pickoba
Last active October 22, 2023 18:26
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pickoba/2c7c8e1391711adc519976f716a4cc91 to your computer and use it in GitHub Desktop.
Save pickoba/2c7c8e1391711adc519976f716a4cc91 to your computer and use it in GitHub Desktop.
SATySFi のページ分割一般化と多段組

SATySFi のページ分割一般化と多段組

この記事は SATySFi Advent Calendar 21 日目の記事です。

もともと個人的なメモとしてまとめていた、SATySFi のページ分割に関するアイデアを公開します。あくまでも一ユーザの意見なのでツメが甘いところが多々あるかと思いますが、その際はご指摘いただけると幸いです。また、具象構文は SATySFi 0.1.0 のものを採用しています。

想定読者

SATySFi を使ったことがあり、block-boxes 等の概念を理解している人

はじめに

現在 SATySFi でページ分割をする際には、page-breakpage-break-multi-column といったプリミティブを利用します。これらのプリミティブは大まかに言うと、block-boxes を入力として、それを適切な長さに切り分けページに配置した document を生成するというものです。この際ページ上の配置位置やヘッダ・フッタはこれらのプリミティブの引数として設定します。

基本的な組版はこの仕組みで十分なのですが、少し特殊なことをしようとすると不足を感じることがあります。例えば、

  • ページ全体を利用した画像配置(LaTeX の画像配置の p オプション)
  • 多段組で、段の先頭に画像を配置する
  • 文書中の任意の位置から多段組を開始する(LaTeX の multicol のような機能)

といったことは現状の API では実現が難しいと思われます。そこで本稿では、ページ分割を一般化することでこうした組版を実現する手法を模索します。

新プリミティブの導入

基本的なアイデアは、page-break の機能を以下の 2 つのプリミティブに分離するというものです(名前は適当です)。

  • cut-block : block-boxes を指定した長さで切り出す
  • generate-document : 切り出した block-boxes をページに配置する(document を生成する)

cut-block は以下のようなシグネチャを持ちます。

% length -> block-boxes -> (block-boxes, block-boxes)
cut-block l bb

第一引数 l は切り出す長さ、第二引数 bb は切り出し対象の block-boxes です。返り値として、切り出された block-boxes と残りの block-boxes がタプルとして返却されます。

当然指定された長さ丁度で切り分けられるとは限らないので、より正確には「 l を超えない範囲で最大の長さを切り出す」ことになります。また、ここでの分割は現在の page-break 同様 add-footnote によって追加した block-boxes が含まれ得るとします。

一方 generate-document は以下のようなシグネチャを持ちます。

% page-func -> list block-boxes -> document
generate-document pagef bblst

第一引数の pagef は各ページでコンテンツをどの座標に配置するかを決定し、第二引数の block-boxes のリストが実際に配置されるコンテンツです。リストの一要素が 1 ページに対応します。返り値として作成した document が返却されます。また、この呼出しの中で hook-page-break 等で指定したフックが実行されます。

page-break の実装

通常のページ分割は上記の 2 つのプリミティブの組み合わせで実現できます。

val page-break pagef bb =
  let rec inner bb =
    if is-block-nil bb then
      []
    else
      let (head, rest) = cut-block text-height bb in
      head :: (inner rest)
  in
  let bblst = inner bb in
  generate-document pagef bblst

ヘッダやフッタを挿入する場合には以下のように実装できます。

val page-break ctx pagef bb =
  let rec inner n bb =
    if is-block-nil bb then
      []
    else
      % ヘッダの生成
      let header-bb = gen-header ctx n in
      let header-length = get-natural-length header-bb in
      % フッタの生成
      let footer-bb = gen-footer ctx n in
      let footer-length = get-natural-length footer-bb in
      % 主コンテンツの切り出し
      let effective-height = text-height -' (header-length +' footer-length) in
      let (main-bb, rest) = cut-block effective-height bb in
      % ヘッダ・フッタとの結合
      let page = header-bb +++ main-bb +++ footer-bb in
      % 結合
      page :: (inner (n + 1) rest)
  in
  let bblst = inner 1 bb in
  generate-document pagef bblst

フッタが必ずページ最下部に配置されるようにするために、main-bbfooter-bb の間に block-skip を入れても良いでしょう。重要なのは、ヘッダ・フッタの概念が SATySFi 処理系から離れクラスファイルで導入できるものとしているおかげで、こうした細かな調整が記述できるようになっているということです。

また実装は省略しますが、相互参照と組み合わせることでページ全体に渡る画像表示(LaTeX の figure の p オプション)も実現できるはずです。

多段組の実装

現状の page-break-two-column 相当の関数は以下のように実装できます。

val page-break-two-column' ctx pagef bb =
  let rec inner n bb =
    if is-block-nil bb then
      []
    else
      % ヘッダの生成
      let header-bb = gen-header ctx n in
      let header-length = get-natural-length header-bb in
      % フッタの生成
      let footer-bb = gen-footer ctx n in
      let footer-length = get-natural-length footer-bb in
      % 主コンテンツの切り出し
      let effective-height = text-height -' (header-length +' footer-length) in
      let (main-left, rest) = cut-block effective-height bb in
      let (main-right, rest) = cut-block effective-height rest in
      let main-bb = side-by-side main-left main-right in
      % ヘッダ・フッタとの結合
      let page = header-bb +++ main-bb +++ footer-bb in
      % 結合
      page :: (inner (n + 1) rest)
  in
  let bblst = inner 1 bb in
  generate-document pagef bblst

引数の bb には予めテキスト幅半分で組まれたものが渡されると仮定しています。また page-break-multi-column も同様の方法で実装できるでしょう。

ここで side-by-sideblock-boxes を水平方向に連結するもので、現状の SATySFi であれば embed-block-top などを用いて実装できるかと思います(ベースラインを合わせるのはここではあまり望ましくないですが)。

さらに、ヘッダ・フッタの生成に相当することを各カラム単位で行えば、段の先頭に画像を配置することもできると思われます。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment