これは「SATySFi Advent Caleandar 2018」の8日目の記事です。
(7日目は hanachinさん でした。9日目は bd_gfngfnさん です。)
SATySFiの重要な目的の一つは「LaTeXのよりよい代替となること」です。そして、皆さんご存知の通り1、LaTeXといえば**[ゆきだるま][snowman-1]**です。となると、SATySFiでもができることが求められるのは当然の帰結でしょう。
というわけで、早速SATySFiで素敵なの絵を描く話……といきたいところですが、なんと、[どっかの誰か][ZR]かSATySFiの正式リリースの当日に早々と[そのネタを済ませて][snowman-2]しまっています。困った事態です。
そこで、チョット目先を変えて、素敵なの「絵」でなく「字」を書くことにしましょう。すなわち、SATySFiで“☃”(U+2603)の文字を書くことを目指します。
※本記事に登場するソースコードの完全版をGistに置きました:
→SATySFiで素敵な文字を出力する(Gist:zr-tex8r)
☃を書けば☃が……出ない
とはいっても、SATySFiはUnicodeに十分に対応していてソースファイルをUTF-8で記述するようになっているはずです。それゆえ、Unicode文字である☃(U+2603)を出力するのは自明な気もするでしょう。とにかく試してみましょう。
@require: stdja
% '☃'(U+2603)を含むインラインテキスト
%※解説の都合上, 出力テキストをマクロに切り出した
let-inline \my-test = {少年老い易く☃成り難し}
% あとはいつものStdJa.documentの通り
in
document (|
title = {\SATySFi;でゆきだるま文字したい話};
author = {某ゆきだるま者};
show-title = false; show-toc = false;
|) '< % 先のマクロを使用
+p {\my-test;}
>
ただし、フォントのデフォルト設定はオリジナルの通りとします。つまり和文フォントは「IPAexフォント」です。これなら“☃”(U+2603)のグリフを含むので大丈夫でしょう。コンパイルしてみましょう。
---- ---- ---- ----
target file: 'sample0.pdf'
dump file: 'sample0.satysfi-aux' (will be created)
parsing 'sample0.saty' ...
parsing 'stdja.satyh' ...
……(略)
reading 'sample.saty' ...
type check passed. (document)
---- ---- ---- ----
……(略)
---- ---- ---- ----
output written on 'sample.pdf'.
FontFormat> No glyph is associated with U+2603.
アレレ、コンパイルは通りましたが、なんか「U+2603のグリフがない」と怒られてしまいました。嫌な予感とともに結果のsample0.pdfを開くと……。
☃できません! 何てことでしょう!
☃のために必要なもの
気を取り直して出力をよく見てみましょう。グリフ欠落を示す“?”は欧文フォント(Junicode)で出力されていて、さらにその周りに和欧文間空白(四分空き)があります。ここから推測すると、「“☃”の文字が欧文扱いされたため、欧文フォントで出力しようとした」と考えられます(残念ながらJunicodeにはU+2603のグリフはありません)。
理由が判ったので対策を考えましょう。☃が欧文扱いされるのが問題なので、☃の文字を**「強制的に和文として出力する」**ための仕組が必要です。そこで次のような命令を考えましょう。
-
\my-ja{...}
: [inline-text] inline-cmd
`引数のテキストを「強制的に和文として」出力する。
仮にこのような命令\my-ja
があるとすると、先のsample0.satyの中の\my-text
の定義を次のように変えれば素敵な☃が無事に出力できるはずです2。
let-inline \my-test = {少年老い易く\my-ja{☃}成り難し}
以下では、\my-ja
を実装することを目標とします。
とにかくフォントを変えてみる
SATySFiにおける文字とフォントの扱いについては『The SATySFibook』の第8章「文字組版」で解説されています。ここで必要な要点を抜粋します。
- SATySFiではUnicode文字の集合を4つの文字体系(script)に分類3していて、文字体系ごとに異なるフォントを割り当てることができる4。
- 文字体系を表す型がscript型であり、これは
HanIdeographic
(漢字)・Kana
(仮名)・Latin
(ラテン文字)・OtherScript
(その他の文字体系)の4つの定数値からなる。 -
set-font
: script → string ∗ float ∗ float → context → contextset-font
scr (fname, r, rb) で、文字体系 scr に対するフォント5を (fname, r, rb) に更新する6。
これを利用して、フォントを(アドホックに)変更する命令\my-font
を実装してみます7。
@require: stdja
%% my-set-for-each-script: (script -> context -> context) -> context -> context
% setf scr が文脈更新函数値である場合に, my-set-for-each-script setf は
% "全てのscript値 scr について setf scr による更新を行う"という更新を行う.
let my-set-for-each-script setf ctx =
ctx |> setf HanIdeographic |> setf Kana
|> setf Latin |> setf OtherScript
%% my-set-all-fonts: (script * float * float) -> context -> context
% my-set-all-fonts fspec で全ての文字体系に対するフォントを fspec に更新する.
% (fspec はフォントを表す3つ組.)
let my-set-all-fonts fspec =
my-set-for-each-script (fun scr -> set-font scr fspec)
%% \my-font: [string; inline-text] inline-cmd
% \my-font(fname){...} は引数のテキストをフォント fname で出力する.
% ※fname はフォント名で, スケールは1, ベースライン補正は0で決め打ち.
let-inline ctx \my-font fname it =
let ctx1 = my-set-all-fonts (fname, 1., 0.) ctx in
read-inline ctx1 it
% 明示的にフォント'ipaexm'を指定した
let-inline \my-test = {少年老い易く\my-font(`ipaexm`){☃}成り難し}
in
% document 以降はsample0.satyと同じ
-
\my-font
では、引数のテキストにどんな文字体系の文字が含まれていてもよいように、全ての文字体系についてフォント設定を更新しています。 - SATySFiの既定設定において、「IPAex明朝」は
ipaexm
というフォント名で登録さているため、このフォントで☃を出力するためには\my-font(`ipaexm`){☃}
と書くことになります。
このsample1.satyのコンパイル結果(本文部分)は以下のようになりました。
今度はちゃんと「IPAex明朝」の☃が出力されています。
番外編:好きなフォントを使いたい
本題からは少し外れますが、せっかく「明示的にフォント名を指定してフォントを変更する」ような命令を実装したので、「自分がもっている好きなフォントで文字を出力する」ための方法を解説しておきます。例として、「源真ゴシック Regular」(ファイル名はGenShinGothic-Regular.ttf)で素敵な☃を出力してみます。
※フォントの登録関連の話は『The SATySFibook』の7章「システムと設定ファイル」で解説されています。
※ここでは実効の(実際に中に dist/ が存在する)ライブラリルートのディレクトリが ~/.satysfi/ であると仮定します。SATySFiをopamを通じて自分でコンパイルしてインストールした場合はそれで合っているはずです8。
まずは、「源真ゴシック Regular」のファイルをSATySFiで使えるように“登録”します。
-
フォントファイル GenShinGothic-Regular.ttf を ~/.satysfi/dist/fonts/ の直下に配置します。
-
フォントハッシュファイル(font hash file) ~/.satysfi/dist/hash/fonts.satysfi-hash を編集して「源真ゴシック」のためのエントリを追加します9。ここではSATySFiにおけるフォント名を
GenShinGothic
としています。fonts.satysfi-hash{ //↓この1行を追加する "GenShinGothic": <Single: {"src-dist": "GenShinGothic-Regular.ttf"}>, "ipaexm" : <Single: {"src-dist": "ipaexm.ttf"}>, "ipaexg" : <Single: {"src-dist": "ipaexg.ttf"}>, ……(略)
これで準備は整いました。後はSATySFi文書で実際に「源真ゴシック」を指定するのですが、これは先ほど実装した\my-font
にフォント名GenShinGothic
を与えるだけです。
% \my-test の定義を以下に変更する.
let-inline \my-test = {少年老い易く\my-font(`GenShinGothic`){☃}成り難し}
「源真ゴシック」の☃が出力されました!
“和文の”フォントで出力したい
さて本題に戻りましょう。\my-font
ではユーザが明示的にフォント名を与える方式でしたが、最終的に作りたい\my-ja
では“和文の”フォント、すなわち「文字体系Kana
に対するフォント」を強制的に適用する必要があります。既定の設定ではKana
のフォントは「IPAexフォント」ですが、この値はユーザの環境設定や文書クラスの指定によって当然変わってきます。従って、「現在のフォント設定内容」を読み出す操作が必要になります。これを行うのがget-font
プリミティヴです。
-
get-font
: script → context → string ∗ float ∗ floatget-font
scr ctx は文字体系 scr に対する現在のフォント。
ではこれを利用して、「指定の文字体系用と同じフォントで出力する」ための命令\my-font-script
を実装してみます。
@require: stdja
%……(sample1.satyの my-set-for-each-script の定義を書く)
%……(sample1.satyの my-set-all-fonts の定義を書く)
%% \my-font-script: [script; inline-text] inline-cmd
% \my-font-script(scr){...} は引数のテキストを"文字体系 scr 用のフォント"で出力する.
let-inline ctx \my-font-script scr it =
let ctx1 = my-set-all-fonts (get-font scr ctx) ctx in
read-inline ctx1 it
% "和文(Kana)用と同じフォント"で出力する
let-inline \my-test = {少年老い易く\my-font-script(Kana){☃}成り難し}
in
% document 以降はsample0.satyと同じ
-
\my-font
の定義では(fname,1.,0.)
と明示的にフォント名を指定していたところを、\my-font-script
では(get-font scr ctx)
としています。この式の値は「フォントの3つ組」ですから、そのままmy-set-all-fonts
に渡せます。
出力結果は以下の通りです。和文フォントは「IPAexフォント」なので、sample1.satyと同様に☃が出ました10。
これでほぼ目的のものができているようにも見えますが、実はまだ問題があります。sample3.satyの出力では☃の周りに四分空きが入っています11。しかし本当に“和文として”出力したのであれば和文同士ということで空きは入らないはずです。
真の“和文扱い”を求めて
つまり、ここで必要なのは、引数のテキストを「その周りから見て“和文”(Kana
)であるように見せかける」という処置です。そして、SATySFiにはこれを行うためのプリミティヴも存在します。
-
script-guard
: script → inline-boxes → inline-boxesscript-guard
scr ib は、インラインボックス列 ib を前後の空き量に関して「文字体系 scr のテキストである」ように見せかけたもの。
これを利用して、sample3の\my-font-script
を改良してみましょう。命令名はmy-force-script
とします。
@require: stdja
%……(sample1.satyの my-set-for-each-script の定義を書く)
%……(sample1.satyの my-set-all-fonts の定義を書く)
%% \my-force-script: [script; inline-text] inline-cmd
% \my-force-script(scr){...} は引数のテキストを"文字体系 scr のテキストと同じ扱い"を
% 適用した上で出力する.
let-inline ctx \my-force-script scr it =
let ctx1 = my-set-all-fonts (get-font scr ctx) ctx in
script-guard scr (read-inline ctx1 it)
%(続く)
-
read-inline
が返す値がinline-boxes型であるため、これに対してscript-guard scr
を適用します。
この\my-force-script
を使えば最終目標である\my-ja
は簡単に作れます。ついでに“欧文扱い”にするための\my-al
も定義してみました。
%% \my-ja{...}: [inline-text] inline-cmd
% テキストを"和文扱い"で出力する.
let-inline \my-ja it = {\my-force-script(Kana){#it;}}
%% \my-al{...}: [inline-text] inline-cmd
% テキストを"欧文扱い"で出力する.
let-inline \my-al it = {\my-force-script(Latin){#it;}}
% "和文扱い"で出力する
let-inline \my-test = {少年老い易く\my-ja{☃}成り難し}
in
% document 以降はsample0.satyと同じ
和文扱いの素敵な☃が出力されました! 完璧ですね!
まとめ
絵もいいけど、文字☃もいいよ♪
-
えっ、ご存知でしょ?☃
[snowman-1]: http://acetaminophen.hatenablog.com/entry/2015/12/13/080226
[snowman-2]: http://d.hatena.ne.jp/zrbabbler/20180211/1518303817
[ZR]: https://qiita.com/zr_tex8r ↩ -
別に「和文の☃を出力する命令」を定義しても構わないのですが、汎用的に使える
\my-ja
の方がより妥当でしょう。 ↩ -
つまり、これまでの話では便宜的に「欧文」「和文」と呼んでいましたが、実際のSATySFiでは分類は4つあるということです。どの文字がどの文字体系に属するかはUnicodeにおける文字体系の規定(Script属性値)により決められます。なお、☃(U+2603)および他の多くの記号はUnicodeでは「どの文字体系にも属さない」(Script値が
Common
)となっていますが、SATySFiでは「どの文字体系にも属さない」文字はSATySFiにおける寡占的文字体系(dominant scripts)の設定に基づいて特定の文字体系に“吸収”されて扱われます。結局、既定の状態では、☃はLatin
に“吸収”されるため、ラテン文字と同じくJunicodeフォントで出力する(ことを試みる)ことになります。 ↩ -
「和欧文間空白」の有無や空き量についても、実際には「script同士の間の設定」という扱いです。 ↩
-
SATySFiにおいて「フォント」は「(SATySFiが管理する)フォント名(string)」「スケール値(float)」「ベースライン補正値(float)」の3つ組で表されます。 ↩
-
ある context → context 型の函数について「その函数が引数のテキスト処理文脈を受け取って、……に更新した新たなテキスト処理文脈を返す」という言明を、この記事では単に「その函数で……に更新する」と表現することにします。またこのような函数を便宜的に「文脈更新函数」と呼ぶことにします。 ↩
-
コメント中に函数の型を記していますが、型註釈を入れているわけではないため、実際に定められる型はより広いものになっている場合があることに注意してください。 ↩
-
ライブラリルート(library root)とは何かについては『The SATySFibook』を参照してください。「SATySFi for Windows」ではライブラリルートは環境変数
SATySFi_RUNTIME
の値となります。UNIX的OSで何らかのパッケージマネージャでSATySFiのバイナリをインストールした場合は恐らく /usr/local/share/satysfi/ になるのだと思われます。なお、実際にはライブラリルートは“複数同時に”配置できるのですが、ここでは元々存在するもののみを使うこととします。 ↩ -
なお、TrueType(またはOpenType)Collection形式のフォントファイルを登録する場合は書き方が少し誓います。 ↩
-
実はsample1.satyとsample3.satyの出力は微妙に異なります。stdjaの設定では和文(
Kana
とHanIdeographic
)のフォントに対してスケール値0.88が指定されているため、sample3の☃はsample1のものに比べて若干小さく(周りの和文文字と同じサイズに)なっています。 ↩ -
SATySFiの既定では、“和文”(
Kana
とHanIdeographic
)とLatin
の間に四分空きが入ります。既に脚注で述べた通り、SATySFiでは☃(U+2603)はLatin
として扱われるのでした。 ↩