マクロツイーター

はてダから移行した記事の表示が崩れてますが、そのうちに直せればいいのに(えっ)

jfmutilで和文VFをカスタマイズする話(1)

どうやらアヒルらしいので、雑に何か書いてみようと思う。

※ただし、例の「黄色い本」の10章に書いてあるようなVFのキホン的な事項については余裕で解っていることを前提とする(うわぁ)

事例1:倍角ダーシをつなげたい話

同人誌で小説を書く人の間では有名なように、世の中にはダーシを2つ並べた場合につながってみえるフォントと隙間ができるフォントがある。TeX Live(のDVIウェアのフォントマップ設定)において既定の明朝体フォントとなっている「IPAex明朝」はダーシがつながらない。

ダーシがつながらない例
ダーシがつながらない例

そして、世の中には「ダーシがつながる」ことに非常に拘る人がいる。それだけなら「ダーシがつながるフォントを選択すればよい」だけであるが、そういう拘りをもつ人は大抵フォント自体にも拘る。なので「〇〇のフォントが大好きだからぜひ使いたいのだけど、〇〇のダーシがつながらないのだけは困る」という悩みが出てくる。

VFを活用すると、「フォント〇〇を使いつつ、全角ダーシ(U+2015)だけは“つながるダーシ”をもつ別のフォント△△を使う」というような設定が可能になる。

要件を整理してみる件

  • upTeXの標準の横組和文フォントupjisr-hをカスタマイズして新しいフォントmy1upjisr-hを作製する。
  • my1upjisr-hでは全角ダーシ(U+2015)の字形が「梅明朝1」(ume-tmo3.ttf)のものになる。
    ※「梅明朝」は“つながるダーシ”の字形をもつ。
  • my1upjisr-hのその他の性質は全て元のupjisr-hに合わせる。
    • 従って、全角ダーシ以外の文字のフォントは“通常の”設定に従う。(TeX Liveの既定の設定では「IPAex明朝」である。)

基本的な設計を考える件

upjisr-hの参照関係は以下のようになっている2

upjisr-h (VF)
   0→uprml-h (原TFM)
   2→upjisr-hq (VF)
         0→uprml-hq (原TFM)

ここで現れる2つの原TFM3は「upTeXの標準の原TFM」であり、TeX Liveの和文フォント設定ツール(kanji-config-updmapなど)の設定対象となっている。初期設定では以下のようになっていて4、「IPAex明朝」が割り当てられている。

uprml-h   UniJIS-UTF16-H  ipaexm.ttf %!PS IPAexMincho
uprml-hq  UniJIS-UCS2-H   ipaexm.ttf %!PS IPAexMincho

さて、これから作製するmy1upjisr-hでは、「梅明朝」への参照を追加する必要がある。従って、新たな原TFMである「my1uprml-hd」5を用意して、それをフォントIDの1番に追加することにしよう6

my1upjisr-h (VF)
   0→uprml-h (原TFM)
   1→my1uprml-h (原TFM) ※追加
   2→upjisr-hq (VF)
         0→uprml-hq (原TFM)

これ以上の設計の詳細については、作業しながら考えていくことにしよう。

作業を開始する件

まずはupjisr-hのVFをZVP形式のテキストに変換する。

$ jfmutil upjisr-h

upjisr-h.zvpが出力されるが、これから行うのは「my1upjisr-hというVFを作る」ことであるので、ファイル名をmy1upjisr-h.zvpに変えておこう。

参照TFMを追加する件

ZVP形式においては、VPL形式と同様のFONTMAP要素によって参照TFMを定義を行う。

[my1upjisr-h.zvp:18行目]

(MAPFONT D 0
   (FONTNAME uprml-h)
   (FONTCHECKSUM O 0)
   (FONTAT R 1.0)
   (FONTDSIZE R 10.0)
   )
(MAPFONT D 2
   (FONTNAME upjisr-hq)
   (FONTCHECKSUM O 0)
   (FONTAT R 1.0)
   (FONTDSIZE R 10.0)
   )

ID=0にuprml-h、ID=2にupjisr-hqが定義されている。ID=1にmy1uprml-hdを追加することにしよう。

(MAPFONT D 1               ;ID
   (FONTNAME my1uprml-hd)  ;TFM名
   (FONTCHECKSUM O 0)      ;チェックサム値を使わない
   ; パラメタ……

参照TFMのパラメタはどうすればいいだろうか。元のupjisr-hのグリフはダーシも含めて大部分がuprml-hを用いて出力されている。ここで追加したいmy1uprml-hdとuprml-hを比べると物理フォントが異なる(これはVFの外で指定する事項)他は同じ性質をもつ前提である。なので、uprml-hと同じにすればよい。

(MAPFONT D 1
   (FONTNAME my1uprml-hd)
   (FONTCHECKSUM O 0)
   (FONTCHECKSUM O 0)
   (FONTAT R 1.0)          ;スケール
   (FONTDSIZE R 10.0)      ;my1uprml-hdのデザインサイズ
   )

これをmy1uprml-h.zvpに追記するわけであるが、その場所については一つだけ注意がある。ZVP中で最初に書かれたMAPFONTには特別な意味がある。なので、特にその意図がない限りは「先頭のMAPFONTがどれか」を変えてはいけない。その他については制約はなく7、特にIDの番号順に並んでいる必要はない。

ダーシの定義を与える件

VPL形式では(仮想の)グリフの定義はCHARACTER要素で行うのであった。ZVP形式のmy1upjisr-h.zvpを見ると、CHARACTER要素とJPL形式のTYPE要素が複合したような形の「中にMAP要素を含むTYPE要素」がある。

[my1upjisr-h.zvp:173行目]

(TYPE D 0
   (CHARWD R 1.0)
   (CHARHT R 0.88)
   (CHARDP R 0.12)
   (MAP
      (SETCHAR)
      )
   )

このようにZVP形式においてはグリフの定義は「TYPE要素」「SUBTYPE要素」「CHARACER要素」の組み合わせで表す。しかし、今回のように「メトリックの変更を伴わない」カスタマイズの場合は、取りあえずは「変更したい文字のCHARACTER要素を追記する8」という方法だけを知っていれば対処できる。今回はU+2015の定義を修正したいので、次のようなものを書くことになる。

(CHARACTER H 2015      ;U+2015の文字の定義
   (MAP
      ;仮想グリフを出力するDVI命令…
      )
   )

ここで、U+2015が元々どのように定義されていたかを確認してみる。CHARSINTYPEの定義から判断すると、U+2015は文字クラス5に該当するので、TYPEの5の定義を見る。

[my1upjisr-h.zvp:222行目]

(TYPE D 5
   (CHARWD R 1.0)
   (CHARHT R 0.88)
   (CHARDP R 0.12)
   (MAP
      (SETCHAR)
      )
   )

「引数のないSETCHAR」という妙な要素があるが、これはZVP形式独自の拡張で、「入力の文字と同じ文字コードのグリフをsetcharする」ことを表す9。つまり、U+2015に対する定義としては、この(SETCHAR)(SETCHAR H 2015)と等価になる。要するに、元の文字を何も加工せずにそのまま出しているだけである。

※そのまま出力するといってもフォントはどれになるのかが気になるだろう。実は、SELECTFONTを実行する前の「既定のフォント」は「ZVP中で最初に出現したMAPFONT」である。つまりU+2015の定義は「uprml-hのU+2015を出力する」となる。先に「先頭のMAPFONTは特別」と書いたのはこういう意味である。

今回のカスタマイズでは、U+2015のフォントをmy1uprml-hdに変えたいのであった。従って、SETCHARの前にSELECTFONT命令を入れればよい。

(CHARACTER H 2015      ;U+2015の文字の定義
   (MAP
      (SELECTFONT D 1) ;フォントをID=1の参照TFMに変える
      (SETCHAR)        ;U+2015を出力
      )
   )

VFが完成した件

以上でZVPの改修の作業は完了である。結局、以下のコードを追加することになった。

(MAPFONT D 1
   (FONTNAME my1uprml-hd)
   (FONTCHECKSUM O 0)
   (FONTAT R 1.0)
   (FONTDSIZE R 10.0)
   )
(CHARACTER H 2015
   (MAP
      (SELECTFONT D 1)
      (SETCHAR)
      )
   )

これらをmy1upjisr-h.zvpに追記した上で、VFに変換する。

> jfmutil my1upjisr-h

無事にmy1upjisr-h.vfとmy1upjisr-h.tfmが出力されれば、VFは完成である。

(つづけ)


  1. 「梅明朝」を選択した理由は単に“つながるダーシ”をもつフォントの一つであるからであり、それ以上の理由はない。物理フォントの選択はフォントマップの部分に切り出されるので後で他の物理フォントに変更するのも容易である。

  2. こういう情報は事前に知識として知っておく必要がある。知らないのであれば、取りあえずZVP形式に変換してその結果を読んで調査することになるだろう。

  3. 「VFではないTFM」のことをこの記事では「原TFM」を呼ぶことにする。jfmutilの説明書では「原メトリックTFM」、makejvfでは「PSフォントTFM」と呼ばれている。原TFMはフォントマップによって物理フォントに割り当てられる。

  4. これは「updmap用のフォントマップ」である。updmap用の和文のフォントマップは基本的にはdvipdfmx用のフォントマップの書式に準じるが、%!PSの後にdvips用の情報を補足するという規則になっている。

  5. TFMの名前は何でもよい。標準のTFMの「uprml-hq」の「q」は「引用符(quotation)用」という意図であろうから、それにならって「ダーシ(dash)用」ということで「-hd」という語尾にした。

  6. フォントIDの番号については、0~255の範囲で空いている番号なら何でもよい。なお、TFM名の語尾の「-hd」は「ダーシ(dash)用」という

  7. VPL形式については、確か「FONTMAPは全てCHARACTERの前に書く必要がある」などの制約があったはずである。

  8. 同じ文字のCHARACTER要素が複数回現れてはいけないので、既に当該の文字のCHARACTER要素がある場合はそれを修正する。

  9. 一般にVFのほとんどの文字は「入力と同じ文字コードのグリフをsetcharする」だけという単純なMAP定義をもつ。引数なしのSETCHARを導入することで、これらが全て「全く同一のMAP定義」と見なすことができて同じサブタイプにまとめることができるので、ZVPの記述を大幅に簡潔化できるわけである。

VFがアレでもjfmutilで変換できる話

jfmutilで和文VFの中身を調べる

jfmutilコマンドは、(u)pTeX系エンジンにおける和文用のTFMやVFファイルの作成・編集を支援するための機能を集めたユーティリティプログラムである。欧文TeXにおけるpltotfやvptovfなどのコマンドに対する和文版に相当する。

TeX LiveおよびW32TeXに標準で含まれている。
※詳細については過去の記事も参照してほしい。

例えば、jfmutilの「vf2zvp」サブコマンドを用いると、和文のVF(.tfm.vfの組)をテキスト形式(ZVP形式1)に変換することができる。

> jfmutil vf2zvp upjisr-h

これを実行する2と、upjisr-h(upTeXの標準和文フォントの一つ)の情報をテキストで表した、以下のようなファイルが出力される。

[upjisr-h.zvp]

(VTITLE )
(FAMILY UPJIS KANJI)
(FACE F MRR)
(CODINGSCHEME TEX KANJI TEXT)
(DESIGNSIZE R 10.0)
(CHECKSUM O 0)
(FONTDIMEN
   (SLANT R 0.0)
   (SPACE R 0.0)
   (STRETCH R 0.1)
   (SHRINK R 0.0)
   (XHEIGHT R 1.0)
   (QUAD R 1.0)
   (EXTRASPACE R 0.25)
   (EXTRASTRETCH R 0.2)
   (EXTRASHRINK R 0.125)
   )
(MAPFONT D 0
   (FONTNAME uprml-h)
   (FONTCHECKSUM O 0)
   (FONTAT R 1.0)
   (FONTDSIZE R 10.0)
   )
(MAPFONT D 2
   (FONTNAME upjisr-hq)
   (FONTCHECKSUM O 0)
   (FONTAT R 1.0)
   (FONTDSIZE R 10.0)
   )
(GLUEKERN
   (LABEL D 0)
   (GLUE D 1 R 0.5 R 0.0 R 0.5)
   (GLUE D 3 R 0.25 R 0.0 R 0.25)
   (STOP)
;(…略…)
(TYPE D 0
   (CHARWD R 1.0)
   (CHARHT R 0.88)
   (CHARDP R 0.12)
   (MAP
      (SETCHAR)
      )
   )
(TYPE D 1
   (CHARWD R 0.5)
   (CHARHT R 0.88)
   (CHARDP R 0.12)
   (MAP
      (MOVERIGHT R -0.5)
      (SETCHAR)
      )
   )
;(…略…)

このZVP形式のテキストファイルから本来のVF(.tfm.vfの組)に変換するためのサブコマンド「zvp2vf」も用意されているので、これらを利用することで既存の和文VFの編集が可能になる。

upnmlminr-hを変換しようとするとアレ

ところが、フォントによってはvf2zvpの変換が失敗することがある。例えば、[japanese-otf]パッケージのupTeX用のフォントの一つであるupnmlminr-hの中身を調べたいと思って、vf2zvpを使おうとしても、エラーが出て変換は失敗していしまう。(ちなみに、pTeX用のnmlminr-hは大丈夫である。)

> jfmutil vf2zvp upnmlminr-h
jfmutil: CHARWD value mismatch: code 00B7

このエラーの意味は「符号位置0xB7の文字の幅について、.tfm.vfに記録された値が食い違っている3」ということである。この動作自体は正常であり、実際にupnmlminr-hにおいて、0xB7の文字の幅の値は食い違っている4。従って、厳密に言うと当該のVFは「壊れている」ので変換のしようがない、ということになる。

しかし、実際の運用としては、どのDVIウェアも「.vfの方の文字幅の情報は無視する」という動作をする。なのでupnmlminr-hは「実際にはちゃんと使える」のである。となると、この「.vfの文字幅は無視する」という慣習を前提にした上でとにかく変換はしてほしい、と思うことになるだろう。

アレでもとにかく変換してしまう件(--lenientオプション)

このような事態に対処するため、jfmutilの1.2.0版(2019-02-02付)において、--lenientというオプションが追加された。

  --lenient       ignore non-fatal error on VFs

--lenientを指定すると、「実用上は問題ない軽微なエラー」を無視して(実用に即した)変換を行うようになる。

> jfmutil vf2zvp --lenient upnmlminr-h

[upnmlminr-h.zvp;抜粋]

(CHARSINTYPE D 3
   X00B7 X30FB XFF1A XFF1B
   )
;(…略…)
(TYPE D 3
   (CHARWD R 0.5)
   (CHARHT R 0.88)
   (CHARDP R 0.12)
   (MAP
      (MOVERIGHT R -0.25)
      (SETCHAR)
      )
   )

この結果をみると、0xB7の文字(符号化がUnicodeであるので、これは「U+00B7 MIDDLE DOT」のことである)の文字クラスは3で、.tfmの情報に従って文字幅は0.5zw5となっている。


  1. 欧文のVPL形式に相当するもので、書式はVPL形式とJPL形式を混ぜたようなものになっている。

  2. なお、.vfファイルや.tfmファイルなどの“TeXシステムにおける標準的なファイル形式”に関しては、Kpathseaの検索が有効なので、upjisr-h.tfmやupjisr-h.vfはカレントにある必要はなく、インストールされていれば十分である。jfmutilの独自形式(.zvp等)のファイルはKpathseaの検索対象にならない。

  3. つまり、VFの仕様としては、文字の幅は.vf.tfmの両方に記録されることになっている。なぜそのような仕様であるかはよく解らない。なお、文字の高さと深さの情報は.tfmのみに記録される。

  4. 0xB7の文字の幅は、upnmlminr-h.tfmでは0.5zwでupnmlminr-h.vfでは1zwとなっている。jfmutilの「tfm2zpl」や「vf2zvp0」のサブコマンドを使うと、.tfm.vfを単独でテキスト形式に変換できるので、これらを使うとそれぞれの文字幅の情報が確認できる。

  5. 厳密にいうとCHARWDの0.5は「要求フォントサイズを単位として0.5単位」を表す。upnmlminr-hでは1単位が1zwに等しいので0.5zwということになる。

2019 年のパズル年賀状

今年の年賀状。

以前に述べたとおり、年賀状にはその年の数に関連した数学パズルを載せるのが通例である。*1しかし去年はパズルを作っている時間が 4 時間しかなかったので、割と普通の(えっ違う?)虫食い算で済ませた。

肝心のパズル問題の部分の文字が小さくで読みづらいが、以下のように書かれている。

以下の条件に従って、乗算の虫食い算を解きなさい。

  • 「縦または横に隣り合う2つのマスの間の線は、その2つのマスに入る数字どうしの関係を示す。二重線の場合は同じ数字、一重線の場合は隣り合う(差が1)数字、線なしの場合は離れた(差が2以上)数字が入る。
(問題の図)

一番上にあるのはグルジア語ジョージア語)での新年の挨拶。

*1:SATySFi の型システムを理解するのに時間を取られている人が多いためか、年を追うごとに年賀状で数学パズルを見ることが少なくなっているようで感じる。

暮れのご挨拶(非常に)的な何か

本年は当(くだらない)ブログをご愛好していただき、誠にありがとうございました。

来年も宜しくおねがいいたします。

*  *  *

*「エッ! これだけ? 何でなんでナンデ!?」
ZR「まあ、平成最後の暮れのご挨拶だから仕方がないね」
*「ところで、はてなブログへの移転は今年中に済ませる予定じゃなかったの?」
ZR「…………」

今年も(非常な意味で)Merry TeXmas! ― \end{texadvent2018}


 
TeX & LaTeX Advent Calendar
 

2018/12/01 〜 2018/12/25
〜とにかくLua(La)TeXしよう〜
*  *  *

アドベントカレンダー(某ZR氏的な意味で)完走

^^`\let~^^`\catcode~`"~`~\let"^^`\count~`‌~`^~`​"~`‌~`‍~`^^"\let​​~^^`
\let​‍   \let       ​‍​‍‍\edef       ​‍\expandaf‍ter​‍     ​​‌‌ ‌‌\‌‌expandafter
​​​​‌‌ ​​  ​‍\expanda‍fter​‍‍      ​​‌‌  \count​​‌‌ ​​    ​​‌‌  ‌‌\‌‌r‌‌elax  \​relax  \​expandafter
​‍​‌\noexpand ​‍​ \count ​‍‍\expand‍after   ​‍\expandafte‍r    ‌‌\‌‌c​‍‍​atcode  
​‍‍\expandafter‍  ​‍‍​‌​‍‍‍\expandafte‍r \expandafte‍r \the​​‌‌ ​ ​​    \expandafter‍
​‍\‍‍expandafter \csname ​‍\expandafter‍‍           \endcsname ​‍‍​‍‍‍‍ ​‍‍‍\csna​​‌‌m​‍‍‍‌e​​ 
​‍‍​‍‍‍‍‍ ​‍‍‍\co​​u​‍‍‍‌nt​​  ​‍‍​‍‍‍‍‍ \‍‍expandafter  ‌‌\c​‍‍‍‌ount \t​​h​‍‍‍‌e \expand​​a​‍‍‍‌fte​​r\expandafter‍‍
             ​‌\the \‍‍expandafter ​‍‍‍\cs​​n​‍‍‍‍‍​‍‍‍‌a​​m​‍‍‍‌e ​​‌‌\exp​‍‍‍‌andafte​​r\expandafter‍‍ 
    ​‍‍\expandafter‍ ‌‌ ​‍‍‍\e​​x​‍‍‍‌pandafter​​​‍‍‍‍​‍‍‍‍​​‌‌ ​‌​   ​‍‍\e‍x‍pandafter ​‍‍‍‍‍\expandafter‍ 
​‍‍\expandafter‍ ‌‌ ​‍‍‍\expand​​a​‍‍‍‌fter​​​‍‍‍‍​​‌‌ ​‌​‌​‍‍‍‍​​‌‌ ​‌​‌  ​‍‍\expand‍a‍fter ​‍‍‍‍‍\expandafter‍  ​‍‍​‍‍‍‍   
  \expand‍a‍fter‌‌\e​‍‍‍‌xpandaft​​e​‍‍‍‌r​​ \e‍x‍pandafter ​‍‍‍\c​​ou​‍‍‍‍​‌n​‍‍‍‌t  ​​‌‌\ex​‍‍‍‌pandaf​​t​‍‍‍‌er​​
   \e‍x‍pandafter‌‌\e​‍‍‍‌xpand​​a​‍‍‍‌fte​​r​‍‍‍\expand​​‌‌a​‍‍‍‌fter​​    ​‍‍​‍‍‍‍ ‌‌   ​‍​‍‍‍‍‍​  ​‍‍‍‍‍​​‌‌ ​‌​​‌‌ ​‌‌\‌‌expandafter
​​​‍‍‍‍​_​‍‍‍‍‍​​​‍‍‍‍​‌​‍‍‍‍‍​​​‍‍‍‍​‌​​​‍‍‍‍​​​​​‍‍‍‍​.​​​‍‍‍‍​_​‍​‍​‍___..___._._...._\‍‍expandafter__._‌‌\e​‍‍‍​‌xpa​​nd​‍‍‍​‌after​​._\expandafter‍‍__
(plain XeTeX 文書)

というわけで、7 回目の開催となる TeX & LaTeX Advent Calendar 2018 も、途中は非常にアレみの高い状況もありましたが、最終的には素敵な TeX ネタが出揃って無事にクリスマスの日*1を迎えられました。今年の参加者は全部で 22 名でした。参加者の皆様に心からの感謝を捧げたいと思います。*2

今年の重点テーマは「とにかく Lua(La)TeX しよう」でした。改めて振り返ってみると、様々な面から「LuaTeX の特徴」を紹介した記事が多く、テーマとして一定の役割を果たせたのではないでしょうか。これをきっかけにして Lua(La)TeX に興味を持つ人が増えることになれば幸いです。

TeX & LaTeX Advent Calendar 2018 を楽しんでくれた皆さんに、

ありがとう!

そして

あっ。“メリーゆきだるま”については、TeX 以外の方をご覧ください。*3

*1:記事が出揃った日がクリスマスですよ!

*2:特に、再募集以降に記事を書いてくださった方、本当にありがとうございました。

*3:もしかしたら、“TeX 以外”はまだクリスマスになっていないのかも。

例の「verb の呪い」を LuaTeX の力で打破する(bxrawstr パッケージ)

これは「TeX & LaTeX Advent Caleandar 2018」の 22 日目の記事です。
(21 日目は tasusu さん でした。23 日目は munepi さん です。)

「verb の呪い」について語ってみる

タイトルにある「verb の呪い」というのは、多くの LaTeX ユーザを悩ませるあの規則のことです。

verb 系の命令は、他の命令の引数の中では使えない。

ここで「verb 系の命令」というのは LaTeX 標準の \verb 命令や verbatim 環境のような、「LaTeX特殊文字を非特殊なものとして入力する*1」機能を指します。例えば、以下のようなことをしようとすると「verb の呪い」のせいで失敗してしまいます。

% \verb は \textsf の引数の中なので使えない!
環境変数\textsf{\verb!%USERPROFILE%!}の値が参照される。

「verb の呪い」の非常に厄介な点は、その原因が TeX 自体の入力機構であるため「実装を工夫すること」では回避できないことです。((jsclasses のクラスや fancyvrb パッケージの \VerbatimFootnotes 命令のように「\footnote の引数の中で \verb を使えるようにする」というものがありますが、これは所望の機能を実現するために \footnote の側に細工をしています。従って、同様の方法では「任意の命令の引数で \verb を使える」ようにすることは不可能です。))パッケージの実装者としては、当然「どこでも使える verb 系命令」を作りたいわけですが、TeX 言語の力をもってしてもそれは実現できないわけです。

LuaTeX の力を使ってみる

「verb の呪い」を打破して「どこでも使える verb 系命令」を実装することはできないのでしょうか。一見すると「本質的に解決困難な問題」のようにも思えますが、今はまだ 12 月ですので、もう少し一般の*2解決策を探ってみましょう。確かに TeX 言語での解決は無理ですが、エンジンを LuaTeX に限定するのであれば状況は変わります。LuaTeX では Lua 言語の力を利用して「TeX の入力規則を変える」ことができるのです。この機構を利用すると「どこでも使える verb 系命令」が実現できそうです。

というわけで、作りました

bxrawstr を使ってみる

bxrawstr の最も単純な使い方は以下のようになります。

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{ltjsarticle}
\usepackage{bxrawstr}
\rawstrenable % これ以降 [[|...|]] が"生文字列"になる
\begin{document}
% 全て非特殊文字として扱われ, 現在のフォントで出力される.
環境変数[[|%USERPROFILE%|]]の値が参照される。
\end{document}

bxrawstr パッケージを読み込んで \rawstrenable を実行すると、それ以降のソース中の [​[|…|]] で囲まれた部分(これを「生文字列」(raw-string)と呼びます)が「LaTeX 的に非特殊な文字の列」として扱われるようになります。先の例のように、生文字列を“単純に出力”した場合は、当該の文字列が現在のフォント設定の下でそのまま出力されることになります。*3

もっと生文字列を“単純に出力”してみる

そして、bxrawstr の生文字列は「verb 系命令とは異なり、他の命令の引数でもその機能を保つ」という特長をもっています。従って、生文字列に装飾を加えて出力することが自由にできます。

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{ltjsarticle}
\usepackage{xcolor}
\usepackage{bxrawstr}
\rawstrenable
% \xHL{<text>} : 地と文字に色を付けて出力.
\newcommand*{\xHL}[1]{\colorbox{red!15}{\color{red}#1}}
\begin{document}
% 生文字列は命令の引数にも使える.
環境変数\xHL{\textsf{[[|%USERPROFILE%|]]}}の値が参照される。
\end{document}

生文字列はかなり“込み入った箇所”でも使うことができます。次の例では、tikzducks の \duck 命令の key-value 形式の引数の中で生文字列を使うことで #%!& の文字列をそのまま出力しています。

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{article}
\usepackage{tikzducks,xcolor}% DUCK!!
\definecolor{myred}{rgb}{0.87,0.17,0.27}
\usepackage{bxrawstr}
\rawstrenable
\begin{document}

\begin{tikzpicture}% U+1F92Cなアヒル
  \duck[body=myred,bill=myred!75!black,signback=black!85,
    signpost=\scalebox{0.8}{\color{white}\textsf{[[|#%!&|]]}}]
\end{tikzpicture}

\end{document}

他の命令の引数で使えるということは、\newcommand の引数でも生文字列を使えるということになります。すなわち、特殊文字を含む文字列を(LaTeX の)マクロにすることも可能です。

% LuaLaTeX文書; UTF-8
\documentclass[yoko,paper=a5]{jlreq}
\newcommand*{\xIL}[1]{\par\noindent#1\par}
\usepackage{bxrawstr}
\rawstrenable
% 生文字列をマクロにする
\newcommand*{\xXA}{[[|\expandafter|]]}
\begin{document}

傍にいたW氏に訊いてみた。
\xIL{「\xXA ?」}

想定通りの答えが返ってきた。
\xIL{「\xXA\xXA\xXA !」}

\end{document}
\index で生文字列してみる

このように、生文字列を使うと命令の引数に任意の文字列が渡せるのですが、命令の種類によっては注意が必要です。実は、bxrawstr の仕様において、生文字列の形式([[|…|]])は「“展開”((LaTeX のマクロ(\newcommand で定義される命令)の“展開”と同様の概念です。厳密には、TeX 言語における“先頭完全展開”に相当します。))して初めて中身の文字列になる」と定められています。そのせいで少し複雑な扱いが必要になるケースがあるのです。

[[|%USERPROFILE%|]]  (生文字列形式)
↓"展開"
%USERPROFILE%  (非特殊な文字列)

makeidx による索引の自動生成を行う文書において、%USERPROFILE% という語句を索引に(このままの位置で)載せたいとします。通常は \index 命令は「引数に特殊文字をそのまま書ける」という性質をもつので問題は起こりません。ところが、\index 命令自体が他の命令の引数に入っている場合は、この性質が失われてしまう(これも「verb の呪い」の一種です。)ため、特殊文字を含む %USERPROFILE% を引数に渡すのが困難になります。

% これは大丈夫
[[|%USERPROFILE%|]]\index{%USERPROFILE%@\verb+%USERPROFILE%}は…

\xStrong{% 他の命令の引数の中
  % これはダメ!
  [[|%USERPROFILE%|]]\index{%USERPROFILE%@\verb+%USERPROFILE%}は…
}

一見すると、\index の引数の中も生文字列を使えばよいように思えます。

  [[|%USERPROFILE%|]]\index{[[|%USERPROFILE%@\verb+%USERPROFILE%|]]}は…

ところが \index は引数を“展開”せずに用いるという性質があるため、これでは [[|%USERPROFILE%@\verb+%USERPROFILE%|]] という生文字列形式そのもの*4が引数と見なされるため、所望の動作になりません。

bxrawstr パッケージでは、このような状況に対処するため、生文字列の扱いを \rawstr という命令で制御できるようにしています。

  • \rawstr\命令[[|生文字列|]] を((生文字列形式を囲む {} がないことに注意してください。))実行すると、生文字列の内容をもつ(“展開”結果の)非特殊文字列を引数にして \命令 が呼び出される。*5
  • \rawstr[[|生文字列|]](命令を間に挟まない形)は [[|生文字列|]] と同じで非特殊文字列をそのまま出力する。

この \rawstr を使うと、先の問題を解決できます。((ちなみに、[[|…|]] はネストできないので、\index の引数中の \verb を生文字列に置き換えることはできません。))

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{ltjsarticle}
\usepackage{makeidx}
\makeindex
\usepackage{bxrawstr}
\rawstrenable
\newcommand*{\xStrong}[1]{\textbf{#1}}
\begin{document}
\xStrong{環境変数\texttt{[[|%USERPROFILE%|]]%
  % 生文字列の内容を \index の引数に渡す
  \rawstr\index[[|%USERPROFILE%@\verb+%USERPROFILE%+|]]
  の}}値が参照される。
\end{document}

\makeindex により生成される .idx ファイルの内容は以下の通りで、所望の内容になっていることがわかります。

\indexentry{%USERPROFILE%@\verb+%USERPROFILE%+}{1}
複数行にまたがって生文字列してみる

生文字列形式は複数行にまたがって書くことができます。

Hello, [[|%%%
&&&
$$$|]] world!

生文字列形式の中に現れる改行文字の扱いは \rawstr のオプション引数で決められます。

  • \rawstr[s](既定): 元の改行文字は空白文字に変換される。つまり「%%% &&& $$$」となる。
  • \rawstr[d] : 元の改行文字は削除される。つまり「%%%&&&$$$」となる。
  • \rawstr[k] : 元の改行文字を LF 文字(U+000A)として扱う。つまり Lua の文字列リテラルで書くと "%%%\n&&&\n$$$" に相当する文字列になる。

前述の通り、単独で [[|…|]] だけ書いた場合は \rawstr[[|…|]] と同等になるため、この場合は改行文字は空白と見なされます。

[k] オプションの用途はやや特殊です。LaTeX で“LF 文字を出力”しても組版結果で改行が起こるわけではないので、[k] は単純に出力する目的では使えません。この指定が役立つ場面と例としては、「\directlua の引数に LaTeX特殊文字を含む Lua コードを書く」というものがあります。(([TeX 言語者向け情報]オプション付きの \rawstr も完全展開可能であるため、これにより完全展開可能性を保ったまま、\directlua 中に LaTeX特殊文字を書くことが可能になります。))

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{ltjsarticle}
\usepackage{bxrawstr}
\rawstrenable
\begin{document}

% \fizzbuzz{<カウンタ名>}: FizzBuzzなカウンタ出力命令.
% ※カウンタ出力命令なので完全展開可能にしたい!
\newcommand{\fizzbuzz}[1]{\directlua{
  local val = \arabic{#1}%←これは展開される
  \rawstr[k][[| --改行保持の生文字列.
    if val % 15 == 0 then val = 'FizzBuzz'
    elseif val % 3 == 0 then val = 'Fizz'
    elseif val % 5 == 0 then val = 'Buzz'
    else val = tostring(val)
    end
    tex.sprint(val)
  |]]
}}
% 1段目enumerateの番号をFizzBuzzにする.
\renewcommand{\theenumi}{\fizzbuzz{enumi}}

技術書は以下の要素で構成される:
\begin{enumerate}
\item 本体
\item バケツ
\item 枝
\item ボタン
\item マフラー \label{itm:muffler}
\end{enumerate}
% 参照も正しく行われる.
特に、\ref{itm:muffler}は重要。

\end{document}
rescan モードを使ってみる

生文字列を直接出力すると \verb と同じ動作になります。しかし、\verb には「空白文字を空白記号〈␣〉(U+2423)として出力する」ための \verb* という変種が存在します。この \verb* を他の命令の引数中で使いたい(つまり \xHL{\verb*!line width!} を実現したい)場合はどうすればよいでしょうか。

\xHL{% 引数の中
  % ダメ
  [[|\verb*!line width!|]]%
}

これでは \verb*!line width! という文字列自体が出力されてしまうのでダメです。\rawstr を使うのはどうでしょうか。

\xHL{% 引数の中
  % ダメ
  \rawstr\verb*[[|!line width!|]]%
}

これだと \verb*{!line width!} という形に帰着されてしまい、\verb* の書式と合致しないのでやはりダメです。

今の場合、「非特殊文字化」は \verb* 自体がやってくれる前提であるため、必要なのは、\verb*!line width! という文字列を LaTeX のコードとして実行することのはずです。これを実現するのが rescan モードで、\rawstr のオプションに r を指定するとこのモードが適用されます。

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{ltjsarticle}
\usepackage{xcolor}
\usepackage{fontspec}% \verb* を使うためfontspecが必要
\usepackage{bxrawstr}
\rawstrenable
% \xHL{<テキスト>} : 地と文字に色を付けて出力.
\newcommand*{\xHL}[1]{\colorbox{blue!15}{\color{blue}#1}}
\begin{document}
線幅を自由に指定するには
% rescanモードなので, \verb*!line width! が実行される.
\xHL{\rawstr[r][[|\verb*!line width!|]]}%
キーを利用します。
\end{document}

ちなみに、\xHL{\rawstr[r]…} の部分をマクロにすることもできます。((つまり、\rawstr を本体中に含んで生文字列形式を引数にとるマクロは可能です。これに対して、生文字列の部分をマクロにしてそれを \rawstr の引数に渡すことはできません。))

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{ltjsarticle}
\usepackage{xcolor}
\usepackage{fontspec}% \verb* を使うためfontspecが必要
\usepackage{bxrawstr}
\rawstrenable
% \xHL{<テキスト>} : 地と文字に色を付けて出力.
\newcommand*{\xHL}[1]{\colorbox{blue!15}{\color{blue}#1}}
% \xHLr{<生文字列>} : \xHL と \rawstr[r] の合成.
\newcommand*{\xHLverb}[1]{\xHL{\rawstr[r]#1}}
\begin{document}
線幅を自由に指定するには
% \xHLverb は普通のマクロだから引数に{}が必要.
\xHLverb{[[|\verb*!line width!|]]}%
キーを利用します。
\verb*!line width!
\end{document}

rescan モードを使うと、verbatim 環境を他の命令の引数に入れることも可能になります。この場合、生文字列中の改行を保持する必要があるため、\rawstr のオプションを [rk] と指定することになります。

例えば、LaTeX のコード断片とそれに対応する出力を横に並べた上で、それを(tcfaspin パッケージを利用して)回転させるという、トッテモ素敵な文書を作ってみましょう。

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{article}
\usepackage[svgnames]{xcolor}
\usepackage{scsnowman}% ゆきだるま!
\usepackage{tcfaspin}% 回転!
\usepackage{bxrawstr}
\rawstrenable
\begin{document}

\faSpin{%
  \begin{minipage}[c]{8em}
    \footnotesize\color{Blue}%
    % rescan モードでかつ改行を保持する.
    \rawstr[rk][[|\begin{verbatim}
\scsnowman[
  scale=8,
  muffler=Red,
  hat=Green,
  buttons=Blue,
  arms=Brown,
  snow=SkyBlue]
\end{verbatim}|]]
  \end{minipage}%
  \begin{minipage}[c]{7em}%
    \normalsize\centering
    \scsnowman[
      scale=8,
      muffler=Red,
      hat=Green,
      buttons=Blue,
      arms=Brown,
      snow=SkyBlue]
  \end{minipage}%
}
\end{document}

まとめ

パッケージのデバ​ッグをしながら記事を書くのはツライ。

やっぱり LuaLaTeX はスゴイ!

*1:必ずしも「そのまま出力する」機能とは限りません。

*2:もし今が 8 月なら画期的な解決策を模索したいところでしょう。

*3:入力の文字コードを“そのまま”出力するので、現在のフォントエンコーディングが OT1 だと文字化けが起こります。2017 年以降の LaTeX であれば既定エンコーディングUnicode なので大丈夫ですが、それより古い場合は fontspec を読み込んだ方がいいでしょう。

*4:実際には、字句解析段階で変換した後の中間形式の文字列。

*5:TeX 言語者向け情報]この形式は完全展開可能です。

SATySFi で文書作成が容易なクラスファイルを作成した(※ただし画期的)

これは「SATySFi Advent Caleandar 2018」の 18 日目の記事です。
(17 日目は matsud224 さん でした。19 日目は puripuri2100 さん です。)

みなさん、サティスファイしていますか!?

えっ、あんまり SATySFi していない? ナンデ?

えっ、ぜひとも SATySFi してみたいけど難しくて使いこなせない? じゃあ、代わりに scSATySFi とかを使うのはどうでしょうか?

えっ、確かに scSATySFi の出力はトッテモ素敵だけど、型システムが簡単すぎるので使う気が起きない? やっぱり複雑な型システムをもつ SATySFi を使いこなしてドヤ顔したい? なるほど……。

というわけで、画期的な文書クラスを作りました

簡単! 素敵! 本質的!

scarticle クラスを使うと、誰でも簡単に、scSATySFi と同じくらい素敵な文書を作れます。具体的には、SATySFi 文書ソースを次のようになります。

% scarticle を使おう!
@require: scarticle

document (|
  title = `サンプル`; % タイトル
  show-title = true; % タイトルを出力するか
|) '<
  % 本文の記述
  +p {scarticle で誰でも\emph{簡単}に\SATySFi;文書を作れます。}
>

えっ、stdja クラスの文書とほとんど同じでやっぱり難しそう? いえ、心配は無用です。タイトルのところを見てください。

  title = `サンプル`; % タイトルは文字列で指定

stdja などの多くのクラスでは title の値は {...} という書き方をします。これは「SATySFi の組版機能を使って組まれるテキスト」*1を表していて、高機能ではあるのですが、その代わりに、「正しい書き方を習得しないといけない」という難しさを伴っています。

このため、scarticle ではタイトルは単なる文字列の値として指定します。SATySFi の文字列値の書き方は非常に単純で、` ` の中にそのまま文字列を書くだけです。{...} のテキストを書くときにはエスケープが必要な <; も含めて、あらゆる文字をそのまま書けます。(ただし ` だけは書けない((もちろん、SATySFi の文法を知っている強い人なら ` を含む文字列値も書けます。『The SATySFibook』の「基本的な基本的なデータ」(3.2.3 節)で説明されています。))のですが、これが必要なことはまずないでしょう。)

では本文('< > の中身)はどうでしょうか。これは stdja などと同じ書き方をしていて((実際は stdjabook と同じコマンド(+p+section\emph\SATySFi など)が使えるようになっています。ただし数式関連の命令には対応していません。))何か難しそうです。しかしこれも心配はいりません。scarticle クラスは文書を本質的にすることを指向していて、そこでは本文の内容は全く本質的でないので完全に無視されます。ヘタに書いても型エラーの原因になるだけなので、思い切って本文は省略しましょう!

[thesis.saty]
@require: scarticle

document (|
  title = `卒業論文`;
  show-title = true;
|) '< % 省略!!
>

タイトルの文字列以外はそのまま書き写すだけなので、とても簡単になりました! えっ、本文を省略してしまうと、内容がない文書になってしまう? いえ、そんなことはありません。実際にこの文書ソースをコンパイルしてみましょう。

> satysfi thesis.saty
 ---- ---- ---- ----
  target file: 'thesis.pdf'
  dump file: 'thesis.satysfi-aux' (will be created)
  parsing 'thesis.saty' ...
  parsing 'scarticle.satyh' ...
  parsing 'pervasives.satyh' ...
  parsing 'list.satyg' ...
 ---- ---- ---- ----
……(略)……
  writing pages ...
 ---- ---- ---- ----
  output written on 'thesis.pdf'.

この通り、本質的な内容をもつトッテモ素敵な文書になっています。しかも、箒もついてきます。*2スバラシイ!☃︎

まとめ

というわけで皆さん、scarticle クラスを使って、本質的な文書をサティスファイしましょう!

*1:SATySFi の用語では「インラインテキスト」(inline text)と呼びます。

*2:なんといっても、今日は 18 日ですからね。