6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

SATySFiで素敵な文字を出力する

Last updated at Posted at 2018-12-08

これは「SATySFi Advent Caleandar 2018」の8日目の記事です。
(7日目は hanachinさん でした。9日目は bd_gfngfnさん です。)

SATySFiの重要な目的の一つは「LaTeXのよりよい代替となること」です。そして、皆さんご存知の通り1、LaTeXといえば**[ゆきだるま][snowman-1]**:snowman:です。となると、SATySFiでも:snowman:ができることが求められるのは当然の帰結でしょう。

というわけで、早速SATySFiで素敵な:snowman:の絵を描く話……といきたいところですが、なんと、[どっかの誰か][ZR]かSATySFiの正式リリースの当日に早々と[そのネタを済ませて][snowman-2]しまっています。困った事態です。

そこで、チョット目先を変えて、素敵な:snowman:の「絵」でなく「字」を書くことにしましょう。すなわち、SATySFiで“☃”(U+2603)の文字を書くことを目指します。

※本記事に登場するソースコードの完全版をGistに置きました:
SATySFiで素敵な文字を出力する(Gist:zr-tex8r)

☃を書けば☃が……出ない

とはいっても、SATySFiはUnicodeに十分に対応していてソースファイルをUTF-8で記述するようになっているはずです。それゆえ、Unicode文字である☃(U+2603)を出力するのは自明な気もするでしょう。とにかく試してみましょう。

sample0.saty
@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を開くと……。

sample-1.png

☃できません! 何てことでしょう!

☃のために必要なもの

気を取り直して出力をよく見てみましょう。グリフ欠落を示す“?”は欧文フォント(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 → context
    set-font scr (fname, r, rb) で、文字体系 scr に対するフォント5を (fname, r, rb) に更新する6

これを利用して、フォントを(アドホックに)変更する命令\my-fontを実装してみます7

sample1.saty
@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のコンパイル結果(本文部分)は以下のようになりました。

sample-2.png

今度はちゃんと「IPAex明朝」の☃が出力されています。

番外編:好きなフォントを使いたい

本題からは少し外れますが、せっかく「明示的にフォント名を指定してフォントを変更する」ような命令を実装したので、「自分がもっている好きなフォントで文字を出力する」ための方法を解説しておきます。例として、「源真ゴシック Regular」(ファイル名はGenShinGothic-Regular.ttf)で素敵な☃を出力してみます。

※フォントの登録関連の話は『The SATySFibook』の7章「システムと設定ファイル」で解説されています。

※ここでは実効の(実際に中に dist/ が存在する)ライブラリルートのディレクトリが ~/.satysfi/ であると仮定します。SATySFiをopamを通じて自分でコンパイルしてインストールした場合はそれで合っているはずです8

まずは、「源真ゴシック Regular」のファイルをSATySFiで使えるように“登録”します。

  1. フォントファイル GenShinGothic-Regular.ttf を ~/.satysfi/dist/fonts/ の直下に配置します。

  2. フォントハッシュファイル(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を与えるだけです。

sample2.saty
% \my-test の定義を以下に変更する.
let-inline \my-test = {少年老い易く\my-font(`GenShinGothic`){☃}成り難し}

sample-3.png

「源真ゴシック」の☃が出力されました!

“和文の”フォントで出力したい

さて本題に戻りましょう。\my-fontではユーザが明示的にフォント名を与える方式でしたが、最終的に作りたい\my-jaでは“和文の”フォント、すなわち「文字体系Kanaに対するフォント」を強制的に適用する必要があります。既定の設定ではKanaのフォントは「IPAexフォント」ですが、この値はユーザの環境設定や文書クラスの指定によって当然変わってきます。従って、「現在のフォント設定内容」を読み出す操作が必要になります。これを行うのがget-fontプリミティヴです。

  • get-font: script → context → string ∗ float ∗ float
    get-font scr ctx は文字体系 scr に対する現在のフォント。

ではこれを利用して、「指定の文字体系用と同じフォントで出力する」ための命令\my-font-scriptを実装してみます。

sample3.saty
@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

sample-4.png

これでほぼ目的のものができているようにも見えますが、実はまだ問題があります。sample3.satyの出力では☃の周りに四分空きが入っています11。しかし本当に“和文として”出力したのであれば和文同士ということで空きは入らないはずです。

真の“和文扱い”を求めて

つまり、ここで必要なのは、引数のテキストを「その周りから見て“和文”(Kana)であるように見せかける」という処置です。そして、SATySFiにはこれを行うためのプリミティヴも存在します。

  • script-guard: script → inline-boxes → inline-boxes
    script-guard scr ib は、インラインボックス列 ib を前後の空き量に関して「文字体系 scr のテキストである」ように見せかけたもの。

これを利用して、sample3の\my-font-scriptを改良してみましょう。命令名はmy-force-scriptとします。

sample4.saty
@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も定義してみました。

sample4.saty(続き)

%% \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と同じ

sample-5.png

和文扱いの素敵な☃が出力されました! 完璧ですね!

まとめ

:snowman:もいいけど、文字☃もいいよ♪

  1. えっ、ご存知でしょ?☃
    [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

  2. 別に「和文の☃を出力する命令」を定義しても構わないのですが、汎用的に使える\my-jaの方がより妥当でしょう。

  3. つまり、これまでの話では便宜的に「欧文」「和文」と呼んでいましたが、実際のSATySFiでは分類は4つあるということです。どの文字がどの文字体系に属するかはUnicodeにおける文字体系の規定(Script属性値)により決められます。なお、☃(U+2603)および他の多くの記号はUnicodeでは「どの文字体系にも属さない」(Script値がCommon)となっていますが、SATySFiでは「どの文字体系にも属さない」文字はSATySFiにおける寡占的文字体系(dominant scripts)の設定に基づいて特定の文字体系に“吸収”されて扱われます。結局、既定の状態では、☃はLatinに“吸収”されるため、ラテン文字と同じくJunicodeフォントで出力する(ことを試みる)ことになります。

  4. 「和欧文間空白」の有無や空き量についても、実際には「script同士の間の設定」という扱いです。

  5. SATySFiにおいて「フォント」は「(SATySFiが管理する)フォント名(string)」「スケール値(float)」「ベースライン補正値(float)」の3つ組で表されます。

  6. ある context → context 型の函数について「その函数が引数のテキスト処理文脈を受け取って、……に更新した新たなテキスト処理文脈を返す」という言明を、この記事では単に「その函数で……に更新する」と表現することにします。またこのような函数を便宜的に「文脈更新函数」と呼ぶことにします。

  7. コメント中に函数の型を記していますが、型註釈を入れているわけではないため、実際に定められる型はより広いものになっている場合があることに注意してください。

  8. ライブラリルート(library root)とは何かについては『The SATySFibook』を参照してください。「SATySFi for Windows」ではライブラリルートは環境変数SATySFi_RUNTIMEの値となります。UNIX的OSで何らかのパッケージマネージャでSATySFiのバイナリをインストールした場合は恐らく /usr/local/share/satysfi/ になるのだと思われます。なお、実際にはライブラリルートは“複数同時に”配置できるのですが、ここでは元々存在するもののみを使うこととします。

  9. なお、TrueType(またはOpenType)Collection形式のフォントファイルを登録する場合は書き方が少し誓います。

  10. 実はsample1.satyとsample3.satyの出力は微妙に異なります。stdjaの設定では和文(KanaHanIdeographic)のフォントに対してスケール値0.88が指定されているため、sample3の☃はsample1のものに比べて若干小さく(周りの和文文字と同じサイズに)なっています。

  11. SATySFiの既定では、“和文”(KanaHanIdeographic)とLatinの間に四分空きが入ります。既に脚注で述べた通り、SATySFiでは☃(U+2603)はLatinとして扱われるのでした。

6
2
0

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
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?