Hatena::ブログ(Diary)

マクロツイーター このページをアンテナに追加 RSSフィード Twitter

2012-12-31

「TeXを電卓として使おう」を何とかする件

アドベントカレンダーの 3 日目の記事「TeXを電卓として使おう」(k16.shikano さん)では TeX処理系(= TeX 言語のインタプリタ)を電卓として使うという技が紹介されています。なかなか面白いアイデアですが、しかしこの方法には弱点が存在すると考えています。

例えば、100 円のハンバーガー 2 個と 150 円のコーヒーを 6 人で買って割り勘の支払い金額を求めたいという状況を考えます。「TeX で電卓」を使うと次のような入力をする必要があります。

>pdftex

This is pdfTeX, Version 3.1415926-2.4-1.40.13 (TeX Live 2012/W32TeX)
 restricted \write18 enabled.
**\relax
entering extended mode

*\newcount\ct

*\ct=100 \multiply\ct 2 \advance\ct 150

*\divide\ct 6 \showthe\ct
> 58.
<*> \divide\ct 3 \showthe\ct

?

これはただ単に面倒であるという問題もありますが、何より、私たちが普段使っている数式の姿から大きく離れているため、操作法を直感的に把握しにくいという大きな問題を抱えています。同じ計算を行うのに、例えば Scheme処理系Gauche)なら

>gosh
gosh> (/ (+ (* 100. 2) 150) 6)
58.333333333333336
gosh>

という入力で可能ですし、また PostScript処理系(GHostscript)なら

>gswin32c
GPL Ghostscript 9.05 (2012-02-08)
Copyright (C) 2010 Artifex Software, Inc.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
GS>100 2 mul 150 add 6 div =
58.3333
GS>

という極めて自然な形式で式を入力できるのと比較すると、TeX での入力の複雑さは大きな弱点と言わざるを得ません。

非常に残念な気持ちになったので、30 分ほどかけて作ってみました。

この tccalc.tex を普通に latexコンパイルする(lualatex とか --shell-escape とかじゃないよ!)と普通に式を入力して計算できるようになります。

<latex tccalc.tex
This is pdfTeX, Version 3.1415926-2.4-1.40.13 (TeX Live 2012/W32TeX)
……(中略)……
)

*=(100*2+150)/6
->58.33333333333333

*=

とても自然ですね!

もっと高度な演算もサポートされています。例えば、たまたま道端に落ちていた、半径 15、中心角 42°の弓形*1の面積を知りたいと思った時でも簡単に答えが得られます。

(先の続き)
*=15^2*42deg/2 - 15^2*sin(42deg)/2
->7.18961394136055

また、ある時ふと 1000 の階乗の近似値をスターリングの公式で求めようと思った場合も楽勝です。

*=(2pi*1000)^.5*(1000/exp1)^1000
->4.023537292037633e2567

(結果は指数表記での表示。つまり 4.02×102567。)

また IEEE 754 の仕様に準じた非有限数(無限大、NaN)もサポートされているので、安心してゼロで割ることができます。(ただし答えが NaN になる場合はエラーが発生する。)

*=9/0
->inf

*=+9/-0
->-inf

*==0/0 + ln(42)
! Undefined control sequence.
<argument> \LaTeX3 error:
                           Invalid operation (0)/(0)
l.27   }

?
->nan

サポートされている演算を優先順位の降順に挙げると以下のようになります。

  • 連接による乗算(2pi30deg 等)*2
  • 関数適用関数の一覧は次の通り:
    abs, exp, ln, max, min, round, round0, round+, round-, sin, cos, tan, cot, csc, sec
  • べき乗 ^(又は **
  • 単項演算子 +, -, !(論理否定)
  • 乗除 *, /
  • 加減 +, -
  • 比較 <, <=, >, >=, =, !=
  • 論理積 &&
  • 論理和 ||
  • 三項 ? :

また次の定数が定義されています。

  • inf, nan, pi(= π), deg(= π/180), true(= 1), false(= +0)*3
    pdfTeX の有効な長さ単位、pt, cm, in, em 等。これらは pt 単位での数値を表し、例えば 32768sp は 0.5 に等しい。

単位の定数を使うと、例えば、「A4 縦、和文サイズ 10pt スケール 95 %で 1 行 42 文字とする場合の左右のマージン(1in のオフセット込み)を cm で表した数値」を次のようにして求められます。

*=((210mm - 10pt*0.95 * 42) / 2 - 1in) / cm
->0.9483769198837687

便利ですね! というわけで、これからの時代は、計算したくなった時には Ghostscript ではなくて LaTeX を起動しましょう!!

*  *  *

*「こんな高機能なものが 30 分で作れるわけがないだろjk」
ZR「え?」
*「どうせまた裏で何か TeX でないものを動かしているんだろう?」
ZR「いや違いますよ。だって LuaLaTeX じゃなくて単なる LaTeX だし、--shell-escape つけてないから \write18 も使えないし……。ちなみに 30 分でできるのはそんなに不思議じゃなくて、ソースはたったのこれだけなので」

[tccalc.tex]
\documentclass{minimal}
\batchmode         % suppress terminal output
\usepackage{expl3}[2012/09/05]
\errorstopmode     % resume terminal output
\ExplSyntaxOn
%% calculator repl loop
\tl_new:N \l_tchcl_input_tl
\tl_new:N \l_tchcl_answer_tl
\bool_new:N \l_tchcl_quit_bool
\group_begin:
  \char_set_catcode_active:N \*
  \cs_gset:Nn \tchcl_readin:
    { 
      \ior_get_str:NN \c_term_ior *
      \tl_set_eq:NN \l_tchcl_input_tl *
    }
\group_end:
\bool_until_do:Nn \l_tchcl_quit_bool
  {
    \tchcl_readin:
    \tl_if_blank:VTF \l_tchcl_input_tl
      { \bool_set_true:N \l_tchcl_quit_bool }
      {
        \tl_set:Nf \l_tchcl_answer_tl { \fp_to_tl:n { \l_tchcl_input_tl } }
        \iow_term:x { -> \l_tchcl_answer_tl }
      }
  }
\ExplSyntaxOff
\stop   % quit immediately
\begin{document}
\end{document}

*「………………」
ZR「ん?」
*「なんだこのプログラムのようななにかおそろしいものは……」
ZR「expl3ですが何か?」
*「……何ソレおいしいの?」
ZR「まあ詳しい話はどこかのZRさんのブログで『expl3』を検索してもらうとして……、まあこういうことですね」

LaTeX2eむかつく!レイアウトいじるのに何でワケワカランTeX言語せなあかんねん!
↓
せや、新しいLaTeXを作ればええんや!
↓
LaTeX3作るで!
↓ (コード書き中)
TeX言語ワケワカラン! むかつく! どないしよ?
↓ (考え中) 
せや、TeXを使うてもっと書きやすい言語を実装して、それを使うたらええんや!
 えらいええこと考えたもんや!
↓ (expl3実装中)
でけた!!!
↓
よっしゃぁ! これからLaTeX3作るでぇ!
↓
イマココ

*「……ヒィィィィィTeXな人こわい…」(((;゚Д゚))ガクガクブルブル
ZR「コワクナイヨー」

*  *  *

今年は、色んな人の色んな TeX 芸を見ることができて非常に充実した一年となりました。

来年は「TUG 2013 日本開催」「美文書第 6 版出版(多分)」と日本の TeX コミュニティにとって重要なイベントが待ち構えています。日本に、そして世界に、より多くの TeX 芸人が現れることを心から願いつつ、今年のブログを締めくくりたいと思います。

*1:円 O の周上に点 A, B があったときに扇形 OAB から三角形 OAB を取り除いた図形のこと。

*2:この書式が可能なのは「数字×定数」「数字×関数適用」等の一部の組み合わせに限られるようで、例えば「6/2(1+2)」はエラーになる。

*3自然対数の底 e に対する定数はなく exp1 とする必要がある。e とすると 3e-5 等の指数表記との混同が起こるためである

2012-12-28

「スヤァTeX」の作り方

「スヤァTeX」TeX拡張機能である「encTeX 拡張」という機能を用いて実現されている。現在利用されている TeX 配布物においては、非 Unicode な欧文 TeX エンジン(つまりオリジナル TeX と pdfTeX)でこの拡張が実装されているが、標準のフォーマット(つまり既存の texlatexpdflatex 等のコマンド)では無効になっているので、恐らくは世界中の TeX ユーザのほとんど全てがこの機能に気付いていないはずである。

encTeX 拡張

encTeX 拡張機能は、多バイトエンコーディングUTF-8 等)をエンジンでサポートする用途のために用意されている。(単バイトエンコーディングのエンジン内でのサポートについては、これとは別の「TCX ファイル」という機構が利用されている。*1なお、LaTeX ではエンジン側の文字コード処理は用いずに TeX 上のマクロを用いて入力文字コードを処理している(inputenc パッケージ)ので、LaTeX ユーザはこれらのエンジン拡張機能に触れる機会は全くない。)核となるのが \mubyte というプリミティブで、これは入力ファイル中に現れる特定のバイト列を特定の制御綴(または文字)で置換することを指示する。例えばキリル文字の〈Д〉(U+0414;UTF-8 で〈D0 94〉)を出力するための命令 \CYRD が別に定義されていたとして、〈D0 94〉を \cyrd に置き換えるには次のコードを実行すればよい。

\mubyte \CYRD ^^d0^^94\endmubyte

あるいはこのコードを記したファイルが UTF-8 で書かれているなら次のように書いても同じことになる。

% 文字コードは UTF-8
\mubyte \CYRD Д\endmubyte

また \mubyte で指定した変換を実際に入力時に有効にするにはパラメタ \mubytein を設定する必要がある。*2

\mubytein=1 % 1で有効, 0で無効

ここまでの内容が解れば、「スヤァTeX」の実装コードを書くのは容易い。これだけでよい。

\mubyte \relax ( ˘ω˘ )スヤァ…\endmubyte
\mubytein=1

ところが、これを単に LaTeX のパッケージにするのでは上手くいかない。*3何故かというと、encTeX 拡張は既定では無効になっていてからである。有効にするには TeX のコマンドの起動時に --enc というオプションを指定しなければいけない。さらに、この指定が意味をもつのは、フォーマット作成時(INI モード)に限られるという制約がある。つまり、フォーマットには encTeX が有効であるかの情報が含まれていて、そのフォーマットを読んで TeX を起動した時にその情報に基づいて設定が決まるのである。*4既存の latex や pdflatex のフォーマットは既に encTeX が無効の状態で作成されているので、ここで \mubyte 命令を有効化することは不可能である。これが、「スヤァTeX」が新しいコマンド(つまりフォーマット)でなければならない理由である。

独自フォーマットの作り方

ここでは、「LaTeX に少しだけ修正を加えた独自フォーマット」の作り方を簡単に解説する。例として、次のようなフォーマット「TC-LaTeX」(コマンド名 tclatex)を作りたいとする。

!〉が既定でアクティブになっていて、!\expandafter と等価になっている。その他は LaTeX と全く同じ。

この場合、次のようなファイル tclatex.ini を用意する。

[tclatex.ini]
% 通常の latex フォーマット作成と同じ処理を行う.
% ただしダンプの実行を抑止する.
\input pdftexconfig
\pdfoutput=0
\scrollmode
\let\xxorgdump=\dump
\let\dump=\endinput % \dump を抑止する
\input latex.ltx
\let\dump=\xxorgdump

% ここに「修正部分」のコードを入れる
\catcode`\!=13
\let!\expandafter

% ダンプして終わり
\dump

拡張子 .ini のファイルはフォーマットの生成の際のソースとして使われる。LaTeX のフォーマット(コマンド名 latex)を作るための latex.ini はシステム標準で用意されていて、それはちょうど上記のソースの「修正部分」の 2 行を除いたものと(動作として)等価になっている。

あとは、「スヤァTeX」(suyahtex.ini)と同じ手順を踏めばよい。(suyahtex パッケージの README ファイルを参照)

  • fmtutil.cnf に次の行を加える。
    tclatex pdftex language.dat --etex --translate-file=cp227.tcx tclatex.ini
    %                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    ここで、波線部分が「フォーマット作成時」に指定されるオプションの列である。通常は latex と同じ内容でよいが、「スヤァTeX」の場合は、ここで「--encオプションを追加することになる。
  • texmf.cnf に「TEXINPUTS.tclatex=...」の行を加える。
  • 実行ファイル tclatex を(pdftex のコピーとして)作成する。
  • fmtutil --byfmt tclatex」を実行する。

それぞれの設定や実行の意味の説明には長い前説明が必要となるので今は割愛する。とにかくこれで所望の「TC-LaTeX」を手に入れることができる。

[test-tclatex.tex]
\documentclass[a4paper]{article}
\def\A#1#2#3{#1#2#3a}\def\B#1#2{#1#2b}\def\C#1{#1c}\def\D{d}
\begin{document}
% 短く書けてとっても便利
\typeout{!!!!!!!\A!!!\B!\C\D}
\end{document}
>tclatex test-tclatex
This is pdfTeX, Version 3.1415926-2.4-1.40.13 (TeX Live 2012/W32TeX)
……(中略)……
No file test-tclatex.aux.
dcba
(./test-tclatex.aux) )
No pages of output.
Transcript written on test-tclatex.log.
練習問題

次のように、「\expandafter」の代わりに「ほむ」と記述できるような LaTeX である「ほむTeX」のフォーマット(コマンド名 homutex)を作成せよ。

\documentclass[a4paper]{article}
\def\A#1#2#3{#1#2#3a}\def\B#1#2{#1#2b}\def\C#1{#1c}\def\D{d}
\begin{document}
ほむほむほむほむほむほむほむ\A
ほむほむほむ\B ほむ\C\D
\end{document}

*1:TCX ファイルは --translate-file というオプションで指定する。この機構を用いると、EBCDIC 等の「ASCII と互換のない」文字コードでの入力が可能になる。fmtutil.cnf の中でよく見かける --translate-file=cp227.tcx が TCX の指定で、「何も変換しない」(つまり入力ファイル中の 0〜255 のバイトがそのまま TeX の符号値 0〜255 の入力文字として扱われる)TCX ファイルの指定を表す。ちなみに、cp227 というのは「コードページ227」ではないらしい。

*2:スヤァTeX では使っていないが、出力用の \mubyteout というパラメタもあり、これを正値にすると、\[write 実行時に「逆変換」が施されるようになる。

*3:「\mybyte は未定義だ」とエラーが出る。

*4:ちなみに、e-TeX 拡張の有効性も同じようにして決まる。

2012-12-25

Merry TeXmas! ― \end{texadvent}

2012/12/01 〜 2012/12/25

TeX & LaTeX Advent Calendar

祝! アドベントカレンダー完走!

\expandafter{ \endlinechar- \string` \aftergroup~ \noexpand{ \ifcat^ \endinput`
\afterassignment.  \csname/ \meaning: \endcsname+  \fi}  \futurelet~  \catcode`
K7SKK7eKK60I13EKK5cKK6cKK65KK4IKK7eIKK60G10I86G10I83V7I72G1I90V0I77V2I82G0VSS25
I68V13GZKK64SS65KK66DSS231HIKK60SS2319GMDHSS20MDUUDXDNUXDNZGDUBXBDKK20QGEDRSVXS
XGYNB      UNXUQBO      UDOSSIXX65VNXS13GXDXYDNXWBSQNBUSYGGRKK64KK65KK66AHKK60M
YQXXOB    BQUSOXX     NOWDZRYD9XDWFBQWD8ZKK67KK64KK65KK66KK7eHZKK65KK6eKK64MDZH
9D4RGF Y  S8GSRW 8   4YWWYHIHAZU11VBWDH7BUDZM449D584DU9D2O4DWJGVDCIQAZN11V24WDL
ON9OB 2H   4XYF WF   UX2F     BUY  4     7HF  9    79F    WEYH  2DNRIRAZXM4S11V
NXGBF OHG  W9F J5   J2O   8UF  WL 8  JMG HL N  JY8  9  M  H8NF  DXH5SWTMBDT2VQH
2RXR JQ9W   F J5S   W8C       UH8OF  QOGMOYSF  8S5BQGGN  XO29  JM92SOHH5HH4O85H
MSO  Q4SHO   588   JRHL  9YOG8 5O8  J2UQ2NXG  JWHGSS4NM  44W   H88H2B4UHNN8MBYH
YF    7S9G  MSF     7YGL      JRHM  HQG8XMNG  N8Y2MUUWOC      RRRYGG85BRBNMWS8G
9NBXW5S4SO9UHSHOX9YWGN52SGUMW5XXRRBRHMBWXYWX2WMWUOBOMS  998  YWYNQYMMUXYMM884H8
NRYQ5NG49NMWS8C      OB      WHX22W28GG5O4QQ4GNWSR55SB      ORRHROWU5922UBF  QG
OUY5424X9HBB4YXN   5OWSRF 594OSMI94QQA5X2XZY54O11BVOUQONO48YNDGXWQBQ9R8MRF  J8R
8OBU8XOXRQSRNRQBH   QBF 2M95N25X88SR4MXMRQWOB8MMG9B4QBYS4RHW2B8GNB2XSGQG5   HGO
W2Q2XNYX4924UU5MSX    8WRBUBW  T     7       42XGWF      9G22SUF     9U4M  UHRO
9HWRHMWXBBH2M5BG5F    S8NY4B 9Q   GOR   JRN   SGRF  JMBR  OU4WR  JMB  SBY  XS8O
RS2O24BO8H8EBG2CF OB   WM2N5YWN   OOF  J49   OU5Y   855  JR4Q55L    NY4QRBY899H
5B2GY5SUO5XGGRF 4Y5WC   RS9HYU   XUM   RSN   9 4N  GO5F  W JU  GOQ5  XWF  OM25R
SN4G9YYD3UBF    7MGF      7B2L  OMHL  UU5MH   OQOL     8   WYS     OMO4L JMRBDD
IAZ2O43IAZ6V3DQHO894BY5WIHUADZR4XM11RV8X3NWYWNX4O33UMWMW4334N83BU4BG9WYNHYQBOUD
BH8NUI3OAENH92VH459X3D5D4BNQ9MOIHNDAUBBZAQIAPVSM93ARSUKK21OG8NOHB9DI466VKK2013E
(↑ tex または pdftex でコンパイル

奇跡が起こりましたよ!

去年のこの時期、TeX に技術系アドベントカレンダーないことについて寂しい思いをしました。そして今年は必ず「TeX & LaTeX Advent Calendar」をやると心に決めました。リアルなイベントと異なり、アドベントカレンダーを開くこと自体は簡単なお仕事なのですが、特に TeX のようなマイナーな分野では、始まってすぐに途切れてしまってとっても残念な感じになることが予想されました。取りあえず、7〜8 人の参加者を集めて「10 日続ける」ことを目標にしていました。ところが始まってみると、最終的に 15 人の参加者を得て、また途中の一部の方の「何とかして続けよう」という執念もあって、無事今日のクリスマスの日まで、気合のこもったネタを送り続けることができました。参加者の皆様に心からの感謝を捧げたいと思います。

ありがとう!

7 日目の kuroky さんの記事で既に触れられているように、来年(2013 年)は TeX 界の最大の年次の国際会議である TUG が日本で開催されます。このアドベントカレンダーの読者で TeX の発展に興味のある方は是非とも参加を検討してみてください。

それでは、最後は例の挨拶で締めましょう。

f:id:zrbabbler:20121221222229p:image

LaTeX 文書で発生するエラーを一気に消し去る話(tcclearerr パッケージ)

2012/12/01 〜 2012/12/25

TeX & LaTeX Advent Calendar

k16.shikano さん(12/24)|(12/01)\@undefined→

最終日(25日目)の記事です。締め括りの記事はまた後で!

*  *  *

LaTeX におけるエラーは初心者・初級者にとって大きな悩みの種です。残念ながら、LaTeX におけるエラー表示というのはあまり的確でないことが多々あります。元々、LaTeXTeX インタプリタ上のプログラムとして実装されているので、特に単純なケアレスミス}\ が抜けた)の場合は、その TeX 実装に依存する TeX レベルのエラー*1が起こり、多くの場合はそれは(LaTeX ユーザには)意味不明なものになるからです。この「意味の解らない」エラーに対処し続けなければならないという苦労が、LaTeX の学習を挫折させ、また LaTeX の利用を放棄させる一つの原因になっていると推測されます。

入門者にとってエラーへの対処が困難という問題は、TeX の別のレベルでも起こり得ます。TeX on LaTeX*2の初級者は多くの場合自分の作ったコードのデバッグに苦労しています。その理由としては、TeX が提供するデバッグ用機能*3が最低限のものしかなく、またコードが自在に書けない段階だと「printf デバッグ」も自身を持って行えない(「デバッグコードを入れると実行行程が変わってしまうかも知れない」と恐れる)ということが考えられます。

しかし、文書(あるいはプログラム)中に紛れ込んでいるエラーを、手動で一つずつ探し当てて潰していくというのは、非効率極まりないことは少し考えてみれば明らかです。全てのエラーが一度に、それも自動で消え去ってくれれば、文書やプログラムの作成の効率が飛躍的に向上することは疑う余地のないところです。

というわけで、パッケージを作ってみました。

使い方は非常に簡単で、LaTeX 文書の場合は、プレアンブルの先頭でいつも通り \usepackage で読み込みます。(オプションはありません。)これだけで、その文書に元々存在していたエラーが一切発生しなくなります。(単に発生したエラーが「表示されない」のではなく、エラーの発生自体が無くなります。)

使用例

例として次のような(悲惨極まる)LaTeX 文書を見てみましょう。

[test-tcclearerr1.tex]
\documentclass[12pt]{jsarticle}
 \usepackage[dvips]{color}
 \usepackage[dvipdfm,
     bookmarks=true,
      bookmarksnumbered=true,
      colorlinks=true,%]{hyperref}
 \oddsidemargin=\z@
%\pagestyle{empty}

\def\ps@plainfoot{%
  \let\@mkboth\@gobbletwo
  \let\@oddhead\@empty
  \def\@oddfoot{\normalfont\hfil\it{\thepage}\hfil}%
  \let\@evenhead\@empty
  \let\@evenfoot\@oddfoot}
\def\ps@plainhead{%
  %タイトルを印刷する
\title{\TeXを使ってみよう!}
\etitle{Let's try to using a \TeX}
\auther{T研のてふにしゃん}
\date{2012年12月25日}
\let\@date\@empty  %あとで意味を調べる
 %ーーーーーー
  %ここから文章
  %ーーーーーー
\bigin{document}
¥maketitle
 \TeXを使えば誰でも簡単に\underline{美しい}文章を
作れるワープロです。数式も作れます。
こrからの時代はみんなでMSワードじゃなくて\Texを使って
レポートを提出しよう。\\
\\
\\

\chapter{\TeX が使えるようにしよう!}  
 まず\TeXをパソコンに入れる
\footnote{正しくはインストウール(install)と言います}
必要がありますが、とても簡単です。\\
 本棚にある「すぐわかる!\LaTeX」の本のCD-ROM
をパソコンに入れると画面が出てくるので 
あとはOKを3回クリックしたらできます。\\

\chapter{\TeX を実行しよう!}   
パソコンのディスプレイの上にあるアイコン
\footnote{小さな\Texの絵のことです}
をダブル クリックしたら\TeXが起動します。
そしたら、右のほうの画面に\\
~~~~~~~~~\verb|\|documentclass[12pt]{jsarticle}\\
~~~~~~~~~\verb|\|begin{document}\\
~~~~~~~~~Hello, \TeX world
~~~~~~~~~\verb|\|end{document}\\
と書きます。\TeXの上のほうにある緑のボタンをクリックして、
OKのボタンをクリックしたら \TeXの文章の画面が出るだろう。
"Hello, \TeX world"と出てきたら完成です!\\
\\
 書き込んだ中身を説明しよう。最初の\documentclass
から初める行は”おまじない”で、\Texのファイルは全部
このコマンドから始まります。\\

\relax % 要確認:動かない
%%参考文献
\bibliography{99}
\bibitem{wakaru}
 森口,「すぐわかる!\LaTeX」,朝昼新聞出版,1995年
\end{document}

これを pLaTeX で処理しようとすると、当然ながら次のようにエラーが発生します。(なお、解り易くするため、出力メッセージ中の全角空白を で表しました。)

C>platex test-tcclearerr1.tex
This is e-pTeX, Version 3.1415926-p3.3-110825-2.4 (sjis) (TeX Live 2012/W32TeX)
……(中略)……
Document Class: jsarticle 2011/05/10 okumura
)

! LaTeX Error: Missing \begin{document}.

See the LaTeX manual or LaTeX Companion for explanation.
Type  H <return>  for immediate help.
 ...

l.2 □
      \usepackage[dvips]{color}
?

このエラーに対処するには、2 行目の先頭にある和文文字を取り除く必要がありますが、そうしても今度は 3 行目で同様のエラーが発生します。(原因も全く同じ。)このように、上掲のソースからエラーのない正しい文書を得るには、数えきれないほどの問題点を修正する必要があり大変な作業になってしまいます。

しかし、tcclearerr パッケージをつかうとこれらの大量のエラーが一度に解決します。次のようにプレアンブルの先頭でパッケージを読み込みます。*4

\documentclass[12pt]{jsarticle}
\usepackage{tcclearerr}
 \usepackage[dvips]{color}
……(残りは全てそのまま)

すると、残りの部分は全く何も手を付けなくても、エラー無しで出力の DVI 文書を得ることができます。もちろん、これを dvipdfmx 等で変換すれば PDF 文書も得られます。おめでとうございます!

C>platex test-tcclearerr1.tex
This is e-pTeX, Version 3.1415926-p3.3-110825-2.4 (sjis) (TeX Live 2012/W32TeX)
……(中略)……
Document Class: jsarticle 2011/05/10 okumura
) (./tcclearerr.sty [1]
 ) )
Output written on test-tcclearerr1.dvi (1 page, 520 bytes).
Transcript written on test-tcclearerr1.log.
「プレアンブルより前」でエラーが起こる場合の対処

文書によっては、プレアンブルの開始より前、つまり \documentclass のある行*5でエラーが起こる場合があるでしょう。

[test-tcclearerr2.tex]
¥documentclass{jsarticle}
%¥usepackage{graphicx}
¥begin{document}
テストしています。。。
¥end{document}
C>platex test-tcclearerr2.tex
This is e-pTeX, Version 3.1415926-p3.3-110825-2.4 (sjis) (TeX Live 2012/W32TeX)
……(中略)……
! LaTeX Error: Missing \begin{document}.

See the LaTeX manual or LaTeX Companion for explanation.
Type  H <return>  for immediate help.
 ...

l.1 ¥
      documentclass{jsarticle}
? 

1 行目で既にエラーが発生しているので、プレアンブル先頭(2 行目)に \usepackage{tcclearerr} を書いてもエラーを消すことができません。しかし、このような場合でも対処方法が存在します。次のように文書の先頭で \RequirePackage という特別な命令*6を用いてパッケージを読み込めばエラーが無くなります。*7おめでとうございます!

\RequirePackage{tcclearerr}
¥documentclass{jsarticle}
¥usepackage{graphicx}
¥begin{document}
テストしています。。。
¥end{document}
C>platex test-tcclearerr2.tex
This is e-pTeX, Version 3.1415926-p3.3-110825-2.4 (sjis) (TeX Live 2012/W32TeX)
……(中略)……
Output written on test-tcclearerr2.dvi (1 page, 520 bytes).
Transcript written on test-tcclearerr2.log.
plain TeX 文書の場合

tcclearerr パッケージは plain TeX でも使うことができます。例えば、次のような plain pTeX 文書*8があったとします。

[test-tcclearerr3.tex]
\documentclass[a4paper,12pt]{jsarticle}
\begin{document}
これはTEXのテストです。
\end{document}
C>ptex test-tcclearerr3.tex
This is pTeX, Version 3.1415926-p3.3 (sjis) (TeX Live 2012/W32TeX)
 restricted \write18 enabled.
(./test-tcclearerr3.tex
! Undefined control sequence.
l.1 \documentclass
                  [a4paper,12pt]{jsarticle}
? 

plain TeX 文書の場合、先頭で \input でパッケージのファイル tcclearerr.sty を読み込みます。

\input tcclearerr.sty
\documentclass[a4paper,12pt]{jsarticle}
\begin{document}
これはTEXのテストです。
\end{document}
C>ptex test-tcclearerr3.tex
This is pTeX, Version 3.1415926-p3.3 (sjis) (TeX Live 2012/W32TeX)
 restricted \write18 enabled.
(./test-tcclearerr3.tex (./tcclearerr.sty [1]
 ) )
Output written on test-tcclearerr3.dvi (1 page, 528 bytes).
Transcript written on test-tcclearerr3.log.

おめでとうございます!

*1:「! Missing number, treated as zero.」などのように、! の後に「LaTeX Error」や「Package xxx Error:」が付いていないのが TeX レベルのエラーである。

*2LaTeX の機能拡張を TeX 言語で実装して行うこと。要するに LaTeX のパッケージや文書クラスの作成。

*3\tracingmacros\showbox 等。

*4:なおここで誤って \usepackage の前に全角空白を入れないように十分注意しよう。

*5:あるいは、\documentclass があるはずなのに \documentclass がない行。

*6\RequirePackage 命令を使うと、\documentclass より前にパッケージファイルを読み込むことができる。パッケージによっては、この命令を使うことを要求される場合が稀にある。

*7:なおここで誤って \RequirePackage の〈\〉を全角で入力しないように十分注意しよう。

*8:どう見ても pLaTeX 文書にしか見えないかも知れないが、書いた人が plain pTeXptex コマンド)で処理しようとしているんだから plain pTeX 文書なんだろう。きっと。

2012-12-24

writeLaTeX で日本語してみたかった件について

【名残惜しく宣伝】
2012/12/01 〜 2012/12/25

TeX & LaTeX Advent Calendar

→21 日目は例のアレ

*  *  *

Web 上で (La)TeXコンパイルを提供するサービスというのは既にいくつか存在するが、最近、writeLaTeX というサービスが話題になっている。LaTeX 文書のコンパイルを行うだけでなく保存や共有が可能で、また、文書中で参照するファイル(画像や非標準のパッケージファイルなど)がアップロード可能、リアルタイムでの組版結果の表示などの特徴を備えている。

しかし残念ながら、writeLaTeX は pdfTeX エンジンしかサポートしていないので、pLaTeX を前提として日本語文書は取り扱うことができない。最近では日本語文書を XeLaTeX や LuaLaTeX で作成している人もいるが、当然それらも使えない。しかし何とかして writeLaTeX で日本語できればよかったのに、というのが今回の話。

pdfLaTeX(を含む 8 ビット欧文 LaTeX)で日本語を扱うための(TeX Live *1で標準で使える)方法といえば CJK パッケージしかない。そこでまずは、CJK パッケージを用いた日本語を含む文書の組版が正常に行えるかを、次のような非常に基本的なソースで試してみよう。

\documentclass[a4paper]{article}
\usepackage[pass]{geometry}
\usepackage{CJK,CJKspace,CJKpunct}
\begin{document}
\begin{CJK*}{UTF8}{min}
国民は、すべての基本的人権の享有を妨げられない。
この憲法が国民に保障する基本的人権は、
侵すことのできない永久の権利として、
現在及び将来の国民に与へられる。
\end{CJK*}
\end{document}

2 行目の pass 付きの geometry はドライバ側の用紙設定を LaTeX 側の設定と合致させるために入れている。(writeLaTeX の場合、既定は A4 のようなので、上の文書の場合は不要ではある。*23 行目では CJK パッケージの他に CJKspace と CJKpunct というパッケージを読み込んでいるが、これについては「今時 CJK を用いるのならこの 2 つも併用するのが常識」と思っていてほしい。*3writeLaTeX は入力ファイルを UTF-8 として扱うのでエンコーディング指定は UTF8、またファミリ指定は min (日本語明朝体、実体は和田研明朝)とする。これを writeLaTeX のソース編集エリアに入力すると無事にコンパイル結果が表示される。PDFエクスポートして調べてみると、和田研明朝の Type1 フォントが埋め込まれていることが確認できる。

f:id:zrbabbler:20121224065944p:image

もちろんこのままでは和田研明朝にしかならず実用できないので、IPA 明朝を埋め込みたい。最近の TeX Live であれば IPA 明朝はきっと使えるはずである。

しかしここで問題がある。CJK パッケージで新しいフォントファミリを使えるようにしようとすると、大量の TFM ファイルを新たに(作成した上で)導入しなければならないが、リモートのシステムには「インストール」することができないはずである。実際は、「カレントディレクトリ」に置くことも可能なので、おそらくは、(画像などと同じく)TFM ファイルをアップロードすることはできるかも知れないが、毎回大量の TFM ファイルのアップロードが必要というのは実用的ではないだろう。どうにかならないものか。

最近の TeX Live には zhmetrics というパッケージが含まれている。これは「ドライバ側のフォントマップ設定で任意の TrueType フォントを指定する」ことを想定した、「汎用的な」等幅の Unicode サブフォントを提供している。(何となく pTeX での和文フォントの扱いの慣習と似ている。)この中の、UTF8 の明朝体に相当する zhsong ファミリを用いることにして、文書中に pdfTeX のフォントマップの設定を追加する。

\documentclass[a4paper]{article}
\usepackage[pass]{geometry}
\usepackage{CJK,CJKspace,CJKpunct}
\pdfmapline{=unisong@Unicode@ <ipam.ttf}
\begin{document}
\begin{CJK*}{UTF8}{zhsong} % ファミリ変更
国民は、すべての基本的人権の享有を妨げられない。
この憲法が国民に保障する基本的人権は、
侵すことのできない永久の権利として、
現在及び将来の国民に与へられる。
\end{CJK*}
\end{document}

ローカルの TeX Live 2012 で実行すると、これは正常にコンパイルが行われ、IPA明朝(IPAMincho)が埋め込まれた*4 PDF ファイルが生成される。しかし、残念なことに、writeLaTeX ではエラーになってしまう。どうやら zhmetrics がインストールされていないらしい。*5となると、TeX Live の版がかなり古い可能性がある。[2012-12-29訂正]writeLaTeX のシステムで zhmetrics が入っていないというのは、こちらの確認作業のミスによる誤認でした。実際には使えます。(追記参照)

フォント定義ファイルの存在を色々と調査してみたところ、どうやら Cyberbit 用の設定、つまり cyberb@Unicode@ の TFM と c70song.fdインストールされているようだ。(これは CJK パッケージの構成要素でかなり昔からあるので当然であるが。なお、Cyberbit フォント自身はフリーでないので TeX Live には含まれていない。)1 ファミリしか使えないのはそもそも実用性に欠けるのは確かだが、とにかくこの設定が writeLaTeX で使えるかどうかを確かめてみる。

\documentclass[a4paper]{article}
\usepackage[pass]{geometry}
\usepackage{CJK,CJKspace,CJKpunct}
\pdfmapline{=cyberb@Unicode@ <ipam.ttf} % Cyberbit 用の TFM を
\begin{document}                        % IPA明朝にマップする
\begin{CJK*}{UTF8}{song} % ファミリは song
国民は、すべての基本的人権の享有を妨げられない。
この憲法が国民に保障する基本的人権は、
侵すことのできない永久の権利として、
現在及び将来の国民に与へられる。
\end{CJK*}
\end{document}

……すると、かなり予想外のことが起きた。なんと「compile timeout」というアラートが返ってきた。さらに調べたところ、どうやら今回は予定通りの処理(IPA明朝を埋め込む)が行われていて、それが本当にタイムアウトしているらしい。pdfTeX では TrueType フォントの埋込の処理が Type1 と比べてかなり重くて、実際にローカルで上記の文書をコンパイルした時も 15 秒ほどかかっている。

実際に使われるサブフォントの個数が少なければ軽くなるから成功するかもしれない、ということで、CJK* 環境の中を「ほげ」だけにしたものを writeLaTeX に入力してみた。すると、案の定、今回は成功してしかもエクスポートした PDF にはちゃんと IPA 明朝が埋め込まれていた。つまり結論は次のようになる。

writeLaTeX は確かに CJK パッケージで IPA 明朝を埋め込んだ文書を作成できる環境にはなっているのだが、実際の文書でそれを行おうとすると処理が重すぎて受け付けてくれない。

というわけで、残念であるが、「writeLaTeX で日本語する件」は諦めるしかなさそうだ。


[2012-12-29 追記] 記事中に「writeLaTeX の TeX システムには zhmetrics が入っていない」と書いたがこれは誤りであった。zhmetrics があるかどうかの判定は \IfFileExists{c70zhsong.fd}{yes}{no} のような TeX コードで行ったのであるが、この時に肝心のファイル名を誤記していたようだ。改めて同じコードを試したら「yes」が出力され、すなわち、zhmetrics は入っていることが判明した。

また後で判ったことだが、TeX Live に含まれる cyberb@Unicode@ の TFM ファイル(cyberb30.tfm 等)は CJK パッケージではなく zhmetrics に含まれるものであった。詳細は次の通り:c70song.fd は CJK パッケージが用意した Cyberbit 用の設定であることは確かだが、cyberb@Unicode@ の TFM は Cyberbit.ttf から(ttf2tfm 等で)生成してできるもので、従って、(Cyberbit のない)TeX Live には本来は含まれていないはずである。しかし zhmetrics では、自身が提供する zhsong 等のファミリの他に、song ファミリについても「代替のための」等幅メトリクスの cyberb@Unicode@ を提供しているのである。

*1:サイトの説明にあるように、writeLaTeX で稼働している TeX システムは TeX Live である。

*2:しかし一部の同様のサービスでは既定がレターサイズである場合があるので注意が必要である。既定以外の b5paper 等を用いる場合はもちろんこの設定が必要となる。

*3:CJKspace はソース中の空白文字の扱いを調整する。CJKpunct は約物の周りの空きの自動調整を行うもので、\punctstyle という命令で「句読点スタイル」を選択することができる。xeCJK のインタフェースは、実はこのパッケージが手本となっている。

*4:サブフォント毎に別のフォントとして埋め込まれるので、埋め込みフォントの一覧の中に IPAMincho が大量に存在する。

*5:パッケージの一員である c70zhsong.fd というファイルが見つからない。

2012-12-21

3 の倍数と 3 がつく数字の時だけ慌てふためきます、フォントが

2012/12/01 〜 2012/12/25

TeX & LaTeX Advent Calendar

k16.shikano さん(12/20)|(12/22)\@undefined→

21 日目は、毎度おなじみの ZR さん(私)が担当します。

*  *  *

nnnnn フォントの紹介

今日の記事は、「3 の倍数と 3 がつく数字の時だけ慌てふためくフォントの紹介です。

え、何のことかわからないって? 早速ダウンロードしてみましょう!

nnnnn.vpl ファイルが得られたら、次のコマンドを実行します。

vptovf nnnnn

nnnnn.tfm と nnnnn.vf が生成されれば成功です。これで取り敢えず*1「nnnnn」フォントが使えるようになります。

ではまず動作確認として次の文書を組版してみましょう。

[test-nnnnn-1.tex]
\documentclass[a4paper]{article}
\newfont\nnnnn{nnnnn}\nnnnn
\begin{document}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
\end{document}

プレアンブルでさっき用意した「nnnnn」フォントを現在フォントに指定しています。*2その後はマクロの定義も TeX の怪しげな設定(\catcode とか endlinechar とか \everypar とか)も何もなくて、本体部には単に数字が羅列されているだけです。これをいつも通り (pdf)latex コマンドで処理すると次のような出力が得られます。

f:id:zrbabbler:20121216041113p:image

はい、確かに 3 の倍数と 3 がつく数字の時だけ慌てふためいてますね。素晴らしい。

英語文書での使用例

それでは早速このフォントを使って文書を組んでみましょう。TeX の Advent Calendar なので、例のクリスマスソング「The 12 Days of Christmas」を出力してみましょう。

[test-nnnnn-2.tex]
\documentclass[a4paper]{article}
\DeclareFontFamily{OT1}{nnnnn}{}
\DeclareFontShape{OT1}{nnnnn}{m}{n}{<->nnnnn}{}
\begin{document}
\usefont{OT1}{nnnnn}{m}{n}
\noindent{\Large The 12 Days of Christmas\par}
\begin{verse}
On the twelfth day of Christmas my true love gave to me\\
12 drummers drumming\\
11 pipers piping\\
10 lords a leaping\\
9 ladies dancing\\
8 maids a milking\\
7 swans a swimming\\
6 geese a laying\\
5 gold rings\\
4 calling birds\\
3 french hens\\
2 turtle doves\\
and a partridge in a pear tree.
\end{verse}
\end{document}

プレアンブルの 2 行は「単一の TFM をファミリとして NFSS に登録する」設定(参考)でこれを行うと nnnnn フォントを好きな文字サイズで使うことができます。結果は次の通りです。

f:id:zrbabbler:20121216041112p:image

なお、数字列の後に単語区切りが必要なので、“twelfth” を “12th” と書いてもそこでは慌てふためきませんのでご注意を。

日本語文書での使用例

もちろん、日本語の文書でも使用することができます。

[test-nnnnn-3.tex]
\documentclass[a4paper]{jsarticle}
\DeclareFontFamily{OT1}{nnnnn}{}
\DeclareFontShape{OT1}{nnnnn}{m}{n}{<->nnnnn}{}
\newcommand\dblrow[2]{%
\raisebox{-#1zw}[0.88zw][0pt]{\shortstack{#2}}}
\begin{document}
\begin{center}\small\usefont{OT1}{nnnnn}{m}{n}
\begin{tabular}{l|cccc@{}c}
\hline
\multicolumn{1}{c|}{党派} &
候補者 & 獲得 & 公示前 &
\multicolumn{2}{c}{増減} \\
\hline
自由民主党   & 337 & 294 & 118 &$+$&176 \\
民主党       & 267 &  57 & 230 &$-$&173 \\
日本維新の会 & 172 &  54 &  11 &$+$& 43 \\
公明党       &  54 &  31 &  21 &$+$& 10 \\
みんなの党   &  69 &  18 &   8 &$+$& 10 \\
日本未来の党 & 121 &   9 &  61 &$-$& 52 \\
日本共産党   & 322 &   8 &   9 &$-$&  1 \\
社会民主党   &  33 &   2 &   5 &$-$&  3 \\
国民新党     &   3 &   1 &   3 &$-$&  2 \\
新党大地     &   7 &   1 &   3 &$-$&  2 \\
新党日本     &   1 &   0 &   1 &$-$&  1 \\
新党改革     &   2 &   0 &   0 &$ $&  0 \\
幸福実現党   &  62 &   0 &   0 &$ $&  0 \\
(諸派)     &   5 &   0 &   0 &$ $&  0 \\
(無所属)   &  49 &   5 &   9 &$-$&  4 \\
\hline
\multicolumn{1}{c|}{合計} &
1504 & 480 & 479 &$+$&  1 \\
\hline
\end{tabular}
\end{center}
\end{document}
f:id:zrbabbler:20121221014821p:image

また一つ世の中が便利になった気がしますね。何に便利なのかは全く判らないけど。

*  *  *

いや、本当は「3 の倍数と 3 がつく数字の時だけアホになる」フォントを作りたかったんだが、流石にそれは TFM の能力*3では無理みたい。「3 桁まで限定」とかだと何とかなるかも知れないが。

*1:nnnnn.tfm/vf がカレントにある状態では、ということ。カレントに置かなくても使えるようにするには、例のごとく、「TEXMF ツリーのしかるべき場所にインストール(コピー)する」必要がある。

*2\newfontTFM 名を直接指定して現在フォントを切り替える命令を作る命令。NFSS をバイパスしてフォント切替が行われるので不可解な動作の原因になりやすく、初級者の使用は推奨されない。

*3:リガチャで別の文字に置き換えるという機構を利用して有限状態変換器(transducer)を組み立てている。

2012-12-19

TeX芸人実力判定問題、その6 (解答編-3)

【躊躇いなく宣伝】
2012/12/01 〜 2012/12/25

TeX & LaTeX Advent Calendar

→18 日目は例のアレ

こちらは平常運行(ry

*  *  *

前回の続き)

\tcln@knumeral の実装の再考

\tcln@kanji\tcln@zeropad が実装できたら、あとは単純に組み合わせれば \tcln@knumeral は完成しそうである。ところが、実際にはそのようなコードでは正しく動かない。何故だか解るだろうか?

% これはダメ
\def\tcln@knumeral#1{%
  \tcln@kanji{\tcln@zeropad{#1}}%
}

規約により、\tcln@kanji引数は 12 桁の数字列でなければならない。しかし、上のコードの引数は明らかにそれと食い違っている。確かに引数 \tcln@zeropad{#1} は「完全展開すれば」数字列になるが、単純に「関数の合成」をしただけでは引数が展開される訳でないので上手くいかないのである。

引数を先に展開しなければいけない、というと、そう、

\expandafter

の出番である。しかし厄介なことに、今のケースだと、\tcln@zeropad{#1} を展開して数字列を得るためには 4 回展開を施す必要がある……となると、こういう有様になってしまう。*1

\def\tcln@knumeral#1{%
  \expandafter\expandafter\expandafter\expandafter
  \expandafter\expandafter\expandafter\expandafter
  \expandafter\expandafter\expandafter\expandafter
  \expandafter\expandafter\expandafter\tcln@kanji
  \expandafter\expandafter\expandafter\expandafter
  \expandafter\expandafter\expandafter\expandafter
  \expandafter\expandafter\expandafter\expandafter
  \expandafter\expandafter\expandafter{\tcln@zeropad{#1}}%
}

……いや、もちろん、

三度のビールより \expandafter が好き

という人にとってはこれは大いに歓迎すべきことなのかも知れない。それは否定すべくもない。しかし、当然ながら世の中にはそうでない人もいることもまた確かである。*2

そういう人にとってはこれは惨状という他なく喫緊の対策が不可欠となる。

一番安直な方法は、\tcln@zeropad を分離するのを諦めてそのコードを \tcln@knumeral の一部としてしまうことである。つまり、\tcln@zeropad の最後の展開を

\def\tcln@zeropad@c#1/#2\relax{%
  \tcln@kanji{#2#1}% ←ここに \tcln@kanji の適用を組み込む
}

という形に変えて、その修正を行った \tcln@zeropad\tcln@knumeral という名前に変えればよい。

これで一応正しい \tcln@knumeral を実装することはできる。しかしそのためにコードのモジュール化が犠牲になったことは確かである。何とかして、\tcln@zeropad を分離したまま \tcln@kanji と合成することはできないか。それを実現するためのやや特殊な技巧を紹介する。それは、「後で他の関数と合成する予定のある関数」について、「後で適用する関数」を指定する引数を最後に追加するのである。つまり、今の場合は \tcln@zeropad の仕様を次のように変更する。

% \tcln@zeropad{<n>}\CS
% (複数回で) \CS{nのゼロ付12桁表記} に展開される.
\def\tcln@zeropad#1#2{...}

仕様変更後の実装としては、最後の結果のトークン列について、単に受け取った制御綴 \CS適用する形に変えればよいだけである。しかも、上のように \CS最後の引数にしておくと、展開が複数の段階にまたがる場合に、最後の段階のマクロ以外は何も変更する必要がなくなる。つまり、\tcln@zeropad\tcln@zeropad@a\tcln@zeropad@b はそのままでよくて、\tcln@zeropad@c だけを次のように変えれば済む。*3

\def\tcln@zeropad@c#1/#2\relax#3{% 最後に \CS を受ける引数を追加
  #3{#2#1}% ←\CS の適用を組み込む
}

これで \tcln@zeropad は「任意の関数と合成できる」ことになるので部品としての独立性が保たれることになる。*4そして、合成関数 \tcln@knumeral の定義は以下のようになる。

\def\tcln@knumeral#1{%
  \tcln@zeropad{#1}\tcln@kanji
}

*1:代入が禁止されているので「\edef で一気に展開させてしまう」という手段が使えないことに注意。

*2:引用のツイート主からの弁明:「もちろん \expandafter をどうしても 5 個以上並べざるを得ない理由――例えばそれがネタである等――がある場合は別であるということは明確にしておきたい」

*3:これは、\CS が最後(\tcln@zeropad@c)の段階まで「直後の位置」に残り続けるからである。よく解らない人は実際に展開ステップを書き出してみよう。

*4:単独で使いたい場合は \CS を恒等関数LaTeX\@firstofone)とすればよい。

2012-12-18

TeX で Windows をシャットダウンする件について

2012/12/01 〜 2012/12/25

TeX & LaTeX Advent Calendar

@egtra さん(12/17)|(12/19)@neruko3114 さん

残念ながら、18 日目の担当が登録されなかったので、再び私(ZR)が担当します。

*  *  *

LaTeX で文書を作成している時に、今使っている PC をシャットダウンしたくなった、というのは (La)TeX ユーザの多くが経験していることでしょう。*1しかし、シャットダウンは文書を構成する要素と見做されていないせいか、LaTeX にはマシンをシャットダウンする命令は用意されていません。その上、CTAN を漁ってみても、シャットダウンする機能を提供するパッケージは、未だに誰も作っていないようです。これでは、PC をシャットダウンする LaTeX 文書を作成しようとした時に途方に暮れることになりそうです。

というわけで、作ってみました。

ただし、シャットダウンの処理は使用する OS に依存するので、このパッケージは Windows 専用*2となっています。処理系は LuaLaTeX(または plain LuaTeX)に限ります。

インストール

例えば W32TeXC:\usr\localインストールしている場合、アーカイブに含まれるファイルを次の場所にコピーした後、(ls-R を使う運用をしている場合は)mktexlsr を実行してください。*3

  • tcshutwindown.sty → C:\usr\local\share\texmf-local\tex\lualatex\tcshutwindown\
  • shutwindown.dllC:\usr\local\bin\lib\lua\

なお、shutwindown.c は上記の shutwindown.dll のソースですが動作には必要ありません。(コンパイルには Visual Studio が必要。*4

使い方

LuaLaTeX 文書のプレアンブル中に次の記述を行います。

\usepackage[grace=<整数>]{tcshutwindown}

ここで <整数> にはシャットダウン開始までの猶予時間を秒単位で指定します。(オプションの記述は必須。)

使用例
[test-tcshutwindown.tex]
\documentclass[a4paper]{article}
\usepackage[grace=10]{tcshutwindown}
\begin{document}
Welcome to Lua{\TeX} world!
\end{document}

コマンドプロンプトでこの文書を lualatexコンパイルすると以下の表示が出て Windows のシャットダウンが開始されます。

C>lualatex test-tcshutwindown.tex
This is LuaTeX, Version beta-0.70.2-2012052919 (TeX Live 2012/W32TeX)
 restricted \write18 enabled.
……(中略)……!!!!!!!!WARNING!!!!!!!!

System will be shutdown in 10 seconds...
System will be shutdown in 5 seconds...
System will be shutdown in 4 seconds...
System will be shutdown in 3 seconds...
System will be shutdown in 2 seconds...
System will be shutdown in 1 second...
System will be shutdown right now...
FAREWELL!
(→シャットダウン開始)

そういう訳で、文書の組版と PC のシャットダウンとで別々の操作を行う必要がなくなりました。世の中が少しだけ便利になった感じがしますね。

*1:異論はクリスマスまで保留しておくことにしよう。

*2:現在製品サポートが続けられているバージョンに限る。

*3Windows 上の TeX Live の場合のインストール手順は README ファイルを参照。

*4:ただし数万円から数十万円以上する専門的な有償版を入手する必要はなくて Express Edition で十分である。

2012-12-17

TeX芸人実力判定問題、その6 (解答編-2)

【淀みなく宣伝】
2012/12/01 〜 2012/12/25

TeX & LaTeX Advent Calendar

こちらはいたって平常運行。

*  *  *

前回の続き)

\tcln@zeropad の実装

基本的に、「関数のようなものを(1)」で紹介されているロジック「それでも(14)」でも利用した)をここでも利用する。ただし今回は 12 桁で表すので単純に考えると 14 個の引数が必要になってしまい、TeX で可能な上限の 9 個を超えてしまう。

% ((num.."/00000000000"):sub(1,13):gsub("^(.*)/(.*)$","%2%1"))
\def\tcln@zeropad#1\relax{% #1 は10進表記
  \tcln@zeropad@A#1/00000000000\relax
}
% ↓引数が14個(実際には無理)
\def\tcln@zeropad@A#1#2#3#4#5#6#7#8#9#A#B#C#D#E\relax{%
  \tcln@zeropad@B#1#2#3#4#5#6#7#8#9#A#B#C#D\relax % 最初の13文字を残したい
}
\def\tcln@zeropad@B#1/#2\relax{%
  #2#1%
}

そこで、「最初の 9 個の引数」を一旦グループにして後で再びバラすというトリックを用いることにする。

\def\tcln@zeropad#1{%
  \tcln@zeropad@a#1/00000000000\relax
}
\def\tcln@zeropad@a#1#2#3#4#5#6#7#8#9{%
  \tcln@zeropad@b{#1#2#3#4#5#6#7#8#9}% 最初の9個をグループにする
}
 % #1が最初の9文字、#2は10文字目、#5は13文字目、#6は14文字目以降
\def\tcln@zeropad@b#1#2#3#4#5#6\relax{%
  \tcln@zeropad@c#1#2#3#4#5\relax % これで最初の13文字を並べたことになる
}
\def\tcln@zeropad@c#1/#2\relax{%
  #2#1%
}
\tcln@kanji の実装

まずは 4 桁のブロックの処理を書こう。規則*1さえきちんと理解していれば簡単である。なお、4 桁が「0000」である場合はブロック全体を無出力とすべきだが、数字列が「0000」であるかの判定は、「数値と見做して値が 0 と等しいか」とすればよい。*2

%% \tcln@onepos3{十} → 三十 ; \tcln@onepos0{百} →(空)
\def\tcln@onepos#1#2{%
  \ifcase#1 \or #2\else \csname tcln@digit/#1\endcsname#2\fi
}
%% \tcln@block{0103}{万} → 百三万 ; \tcln@block{0000}{億} →(空)
\def\tcln@block#1#2{%
  % #1が0000なら無出力
  \ifnum#1>0 \tcln@block@a#1#2\fi % #2はそのまま出力
}
\def\tcln@block@a#1#2#3#4{%
  \tcln@onepos#1{千}\tcln@onepos#2{百}%
  \tcln@onepos#3{十}\csname tcln@digit/#4\endcsname
}% tcln@digit/0 は空であることに注意

\tcln@block ができてしまうと \tcln@kanji の実装も容易である。

%% 丁度12桁の数字列を漢数字に変換する
\def\tcln@kanji#1#2#3#4#5#6#7#8#9#A#B#C{%
  \tcln@block{#1#2#3#4}{億}%
  \tcln@block{#5#6#7#8}{万}%
  \tcln@block{#9#A#B#C}{}%
}

おっと、また引数が 9 個を超えてしまった。なので処理を途中で分ける。

%% \tcln@kanji{000100000009} → 一億九
\def\tcln@kanji#1#2#3#4{%
  \tcln@block{#1#2#3#4}{億}\tcln@kanji@a
}
\def\tcln@kanji@a#1#2#3#4#5#6#7#8{%
  \tcln@block{#1#2#3#4}{万}\tcln@block{#5#6#7#8}{}%
}

*1:百の位が 2 なら「二百」、1 なら「百」、0 なら出力無しにする、等。

*2:代入が禁止されているので \ifx でやろうとしても上手くいかない。

2012-12-16

TeX芸人実力判定問題、その6 (解答編-1)

【疑いなく宣伝】
2012/12/01 〜 2012/12/25

TeX & LaTeX Advent Calendar

→14 日目は 例のアレ

こちらは平常運行。

*  *  *

問題はこちら。簡単にいうと、e-TeX なしの pTeX で、正整数を「えるたそ名」に変換するような、完全展開可能な「関数」を作れという問題である。完全展開可能であるので次のように「関数」のように使えるはずである。

\edef\result{1→\eltasoname{1}; 1000→\eltasonamae{1000}}
\show\result %==> 1→一反田えー; 1000→千反田える

ここでは、問題の \eltasoname の作り方を順を追って説明する。なお、この記事では plain TeX で実装することとし*1プログラムで使う名前空間は「tcln」とする。(想定する読者のレベルは一般の TeX 言語の記事と同じである。*2

完全展開可能の鉄則

このブログでは幾度となく述べられているが、完全展開可能なマクロを作るときに第一に守らなければならない鉄則はこれである。

「代入」(assignment)の操作は禁止

ここで「代入」というのはレジスタの代入文や <coe>\let に限らず、マクロの定義や算術演算*3も含み、要するに TeX の状態を変化させる全ての操作が含まれる。つまり、関数型言語で「副作用」と呼ばれるものに相当する。これが一切禁止されているということは、完全展開可能のプログラミングの世界は Haskell のような純粋な関数型言語に似ているということになるのは間違いない。

ただし、TeX 言語自体があまり「完全展開可能でのプログラミング」のことを考慮して設計されていない*4ので、色々と理不尽な制限が課せられることになる。その最たるものが「算術演算が不可能」ということだろう。この「理不尽な制限」を緩和したものが e-TeX 拡張である。しかしこの問題では e-TeX 拡張の使用が禁止されている。すなわち、この問題は理不尽であり、ひいては問題の出題者が理不尽であるということは疑いない所であろう。

配列」の作成

さて、完全展開可能でない普通の「えるたそ」のプログラム(tceltaso.sty)においては、余りの数から「えー」「びー」……への文字列への変換を「擬似配列」を用いて実現した。配列変数の参照(\csname tcln@alpha/\the\tcln@k\endcsname)は完全展開可能だから、この方法は完全展開可能な実装のときにも有効である。さらに大事なことは、配列データを作成するのは、(完全展開可能を保証する必要がある)\eltasoname の実行(展開)時ではなく、プログラムの読込時となるので、ここでは完全展開可能の制約を気にする必要が全くない。なので、tceltaso.sty で用いていたロジックがそのまま通用することになる。

ただし、今は LaTeX ではなく plain TeX を使っているので、\@for が使えない。だから次のようなパターンを利用する。

\@for\tcln@x:=ぜっと,えー,びー,...\do{<\tcln@x を用いたコード>}
↓
\def\do#1{<#1 を用いたコード>}
\do{ぜっと}\do{えー}\do{びー}...

これは所謂「\do-リスト」の利用と見做すことができる。*5これに従って alphadigit配列を作成するコードを書くと以下のようになる。*6

\newcount\tcln@x
\def\do#1{%
  \expandafter\def\csname tcln@\tcln@tempa/\the\tcln@x\endcsname{#1}%
  \advance\tcln@x 1
}
\tcln@x=0 \def\tcln@tempa{alpha}
\do{"ぜっと}\do{えー}\do{びー}\do{しー}\do{でー}\do{いー}\do{えふ}%
\do{じー}\do{えいち}\do{あい}\do{じぇー}\do{けー}\do{える}\do{えむ}%
\do{えぬ}\do{おー}\do{ぴー}\do{きゅー}\do{あーる}\do{えす}\do{てぃー}%
\do{ゆー}\do{ぶい}\do{だぶりゅー}\do{えっくす}\do{わい}
\tcln@x=0 \def\tcln@tempa{digit}
\do{}\do{一}\do{二}\do{三}\do{四}\do{五}\do{六}\do{七}\do{八}\do{九}
\eltasoname の設計試案

\eltasoname を実装するのに必要な部品(関数)は次の 2 つである。

  • \tcln@knumeral整数を漢数字に変換する。(例: 42 → 四十二)
  • \tcln@index整数に対応するアルファベットの名前の添字(つまり 26 で割った余り)を求める。(例: 42 → 16)

ここで、\tcln@knumeral はさらに 2 つの部品(関数)に分けることができる。

  • \tcln@zeropad整数をゼロ付きの 12 桁の十進表記で著す。(例: 42 → 000000000042)
    整数は最大 10 桁であるが、漢数字への変換を考えると 12 桁で表す方が便利である。
  • \tcln@kanji : 12 桁の数字を漢数字に変換する。(例: 000000000042 → 四十二)

ここで「関数」の取扱に注意が必要である。「変換して TeX プログラミング」において全面的に採用したように、通常の TeX プログラミングでは「関数」を「特定の変数への代入」の形で実現するのが一つの「容易な方法」であった。これに対して、完全展開可能の場合は代入が使えないので、関数をそのまま関数のような形式で表すことになる。

%%(公開) \eltasoname{<整数n>}
% 整数nの「えるたそ名」.
\def\eltasoname#1{%
  \tcln@knumeral{#1}反田%
  \csname tcln@alpha/\tcln@index{#1}\endcsname
}
%% \tcln@knumeral{<整数n>}
% 整数nの漢数字表記.
\def\tcln@knumeral#1{%
  \tcln@kanji{\tcln@zeropad{#1}}%
}
%% \tcln@zeropad{<整数n>} : 12桁の数字列に変換.
%% \tcln@kanji{<数字列>} : 漢数字表記に変換.
%% \tcln@index{<整数n>} : アルファベットの名前の添字.

それでは、3 つの部品(\tcln@zeropad\tcln@kanji\tcln@index)の実装を進めていこう。

*1:ただし LaTeX でも動作可能とする。

*2:つまり、基本的な TeX 言語のプログラミングの知識は既に習得していることを想定する。

*3:何故なら、TeX の算術演算命令(\advance 等)は全て「複合代入」(C 言語の += 演算子等)に相当するからである。

*4:そもそも、TeX は「プログラムするための言語」として設計されたのではない。

*5:だから \do という(自身の名前空間にない短い)制御綴が使える。

*6alpha への代入と digit への代入のコードは変数名の部分を除いて全く同じなので、変数名を <cod>\tcln@tempa という変数にして一つにまとめている。

2012-12-14

スヤァTeX を作ってみた

2012/12/01 〜 2012/12/25

TeX & LaTeX Advent Calendar

誠に遺憾ながら、14 日目の担当が登録されなかったので、再び私(ZR)が担当します。

*  *  *

確かに、\relax より ( ˘ω˘ )スヤァ… の方が直感的なので、これが使えれば (La)TeX の使い勝手が向上することは間違いないでしょう。しかし残念なことに ( ˘ω˘ )スヤァ… は普通の制御綴の表記を成していないので、単純に

\let( ˘ω˘ )スヤァ…\relax

とすることはできません。*1

というわけで、( ˘ω˘ )スヤァ… が使えるように LaTeX を拡張した新フォーマット「スヤァTeXを開発しました!

インストール方法

アーカイブに含まれる README ファイルを参照して下さい。新しいフォーマットの導入なので、初心者にはハードルが高いことは確かですが、( ˘ω˘ )スヤァ… を是非とも使いたいという人にとっては手を煩わすだけの価値がきっとあるはずです!

作業が済んだら、スヤァTeX が正常にインストールされたかを次の文書を用いて確認しましょう。

[test-suyahtex0.tex]
% 文字コードUTF-8
\documentclass[a4paper]{article}
\nonstopmode % \showで止まらなくする
% ( ˘ω˘ )スヤァ… を含むマクロを定義して中身を表示させる
\def\test{Hello, \TeX( ˘ω˘ )スヤァ…}
\show\test
\begin{document}
( ˘ω˘ )スヤァ…
\end{document}

suyahtex test-suyahtex0.tex」を実行すると、この文書が スヤァTeX で処理されます。画面出力の中に次のような記述があり、かつエラーなく終了すればインストールは成功しています。

> \test=macro:
->Hello, \TeX \relax .
l.4 \show\test

スヤァTeX では文書中の「( ˘ω˘ )スヤァ…」が全て「\relax」と解釈されるので、\test マクロの定義本体にも \relax が含まれることになります。

使用例

スヤァTeX では \relax の代わりに ( ˘ω˘ )スヤァ… が使えるので、例えば修正ユリウス通日を求める TeX コードを次のように書くことができます。

[julian-suyah.sty]
% 文字コードUTF-8
\newcount\xx@cnta
\newcount\xx@cntb
%%<*> \julian{年/月/日}
% 指定の日付に対する修正ユリウス通日の値をマクロ
% \thejulian に返す.
\newcommand*\julian[1]{%
  \edef\xx@tmpa{#1}%
  \expandafter\xx@julian@a\xx@tmpa///( ˘ω˘ )スヤァ…
}
\def\xx@julian@a#1/#2/#3/#4( ˘ω˘ )スヤァ…{%
  \xx@cnta=#1( ˘ω˘ )スヤァ…
  \xx@cntb=#2( ˘ω˘ )スヤァ…
  \ifnum\xx@cnta<\@ne \xx@cnta=\@ne \fi
  \ifnum\xx@cntb<\@ne \xx@cntb=\@ne \fi
  \ifnum\xx@cntb<3( ˘ω˘ )スヤァ…
    \advance\xx@cntb12( ˘ω˘ )スヤァ…
    \advance\xx@cnta-1( ˘ω˘ )スヤァ…
  \fi
  \mathchardef\xx@year\xx@cnta
  \chardef\xx@month\xx@cntb
  \multiply\xx@cnta1461( ˘ω˘ )スヤァ…
  \divide\xx@cnta4( ˘ω˘ )スヤァ…
  \xx@cntb\xx@year
  \divide\xx@cntb100( ˘ω˘ )スヤァ…
  \advance\xx@cnta-\xx@cntb
  \divide\xx@cntb4( ˘ω˘ )スヤァ…
  \advance\xx@cnta\xx@cntb
  \xx@cntb\xx@month
  \advance\xx@cntb-2( ˘ω˘ )スヤァ…
  \multiply\xx@cntb520( ˘ω˘ )スヤァ…
  \divide\xx@cntb17( ˘ω˘ )スヤァ…
  \advance\xx@cnta\xx@cntb
  \advance\xx@cnta#3( ˘ω˘ )スヤァ…
  \advance\xx@cnta-678912( ˘ω˘ )スヤァ…
  \edef\thejulian{\the\xx@cnta}%
}

このコードが正しく動くか確認してみましょう。

[test-suyahtex1.tex]
\documentclass[a4paper]{article}
\usepackage{julian-suyah}
\begin{document}
\julian{2012/12/25}
MJD number for 2012/12/25 = \thejulian.
\end{document}
f:id:zrbabbler:20121214213453p:image

というわけで、これからは ( ˘ω˘ )スヤァ…したい人も安心して TeX プログラミングができますね!

Happy スヤァTeXing!

*1:これを実現しようとすると、カテゴリコードの設定について「先頭文字を 0 にして残りの文字を 11 にする」必要があるが、「( ˘ω˘ )スヤァ…」は ASCII 文字や空白文字を含むので、普通の LaTeX が動く状況でそのような設定を行うことは無理である。

2012-12-13

それでも TeX で「ガウス素数の模様」したい人のための何か (2)

【歪みなく宣伝】
2012/12/01 〜 2012/12/25

TeX & LaTeX Advent Calendar

こちらは平常(ry

*  *  *

前回の続き)

TeX で「ガウス素数の模様」してみた

このプログラムgptexture()\gptexture)の引数に 39 を指定した場合、絶対値 39 以下のガウス整数が「模様の描画」の対象になるので、全体の模様は (−39,−39) から (+39,+39) の範囲に収まることになる。元の Luaプログラムではこれを 79 行 79 桁のテキストで表現しているが、TeX では次のような図として出力することにする。

\begin{picture}(79,79)(-39,-39)
%......
% 1-4i がガウス素数であるなら次の命令を実行
\put(1,-4){\rule{\unitlength}{\unitlength}}
%......
\end{picture}

ここで図の描画で用いる \unitlength の値を \gptunitlength という寸法値マクロ\renewcomand で設定する)で設定できるようにした。

gptexture1.luaset()reset()cur() にあたるマクロは次のように成る。

\def\tcgp@set#1#2{\@namedef{tcgp@a/\number#1/\number#2}{1}}
\def\tcgp@reset#1#2{\@namedef{tcgp@a/\number#1/\number#2}{0}}
\def\tcgp@cur#1#2{\@nameuse{tcgp@a/\number#1/\number#2}}

\tcgp@set\tcgp@reset は通常通りの書き換えになっている*1が、\tcgp@cur は少し異様である。cur() は値を返す関数であるから、本則に従うと「手続きへの変換」が必要になるところであろう。ここでは \tcgp@cur を(Lua や C 言語の)「関数」ではなく、単純に TeX の本来の意味での「マクロ」である(つまり単純に文字列の置き換えをするもの)と捉えてほしい。つまり、\tcgp@cur\@nameuse{...} に展開されるマクロであるから、TeX プログラム中の「値」を表すコードとして \@nameuse{...} があるなら、その代わりに \tcgp@cur を書いてもよいはずである。*2

同様な考えで、絶対値(math.abs())を表すコード(前回に紹介した)を「マクロ\tcgp@abs にすることができる。

\def\tcgp@abs#1{\ifnum#1<0 -\fi #1}

すなわち、\tcgp@abs\tcgp@x は「\ifnum\tcgp@x<0 -\fi \tcgp@x」に展開される。

最後に not の扱いの話をする。if 文であれば、not を処理するのは容易で、単に then と else の部分を入れ替えればよい。

if not (a == 0) then b = a end
↓
\ifnum \tcgp@a=0 \else \tcgp@b=\tcgp@a \fi

これに対して、while 文(\@whilenum)の場合はそのような方法が通用せず、従って、「元の式の否定と同値になる数値比較の式」を何とかして捻り出す必要がある。ここで TeX の if 文が三項演算子*3として使えることを思い出すと、「数値比較の結果の真偽値を一度 0/1 の整数に変換してから 0 と比較する」ことで所望の式が得られる。

while not (a == 0) do ... end
↓
while ((a == 0) ? 1 : 0) == 0 do ... end
↓
\@whilenum{\ifnum\tcgp@a=0 1\else0 \fi =0 }\do{ ... }

この定型のコードもマクロ \tcgp@not として定義しよう。*4

\def\tcgp@not#1{\ifnum#1 1 \else 0 \fi =0 }
% これで \@whilenum{\tcgp@not{\tcgp@a=0}}\do{ ... }

これで gptexture1.luaTeX プログラムに書き直す準備が整った。

完成したパッケージのコードは以下の通り。パッケージ名は tcgptexture、名前空間は tcgp。公開命令は \gptunitlength\gptexture の 2 つである。

% tcgptexture.sty
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{tcgptexture}

%% 変数定義
%(整数値)
\newcount\tcgp@t
\newcount\tcgp@n
\newcount\tcgp@rn
\newcount\tcgp@x
\newcount\tcgp@y
\newcount\tcgp@px
\newcount\tcgp@py
\newcount\tcgp@qx
\newcount\tcgp@qy
%(配列型)
%\tcgp@ct/*

%%(公開) \gptunitlength{<寸法>}
% 1つの点(四角)の大きさ.
\newcommand*{\gptunitlength}{2.5pt}

%%(公開) \gptexture{<>}
% 絶対値がn以下の範囲での模様を出力する.
\newcommand*{\gptexture}[1]{%
  \begingroup
    \setlength{\unitlength}{\gptunitlength}%
    \tcgp@n=#1
    \ifnum\tcgp@n<1 \tcgp@n=1 \fi
    \tcgp@gpt@setrn
    \tcgp@t=\tcgp@n \multiply\tcgp@t2 \advance\tcgp@t1
    \begin{picture}(\tcgp@t,\tcgp@t)(-\tcgp@n,-\tcgp@n)%
      \tcgp@gpt@init
      \tcgp@gpt@sieve
      \tcgp@gpt@show
    \end{picture}
  \endgroup
}

%% 補助的なマクロ
\def\tcgp@set#1#2{\@namedef{tcgp@a/\number#1/\number#2}{1}}
\def\tcgp@reset#1#2{\@namedef{tcgp@a/\number#1/\number#2}{0}}
\def\tcgp@cur#1#2{\@nameuse{tcgp@a/\number#1/\number#2}}
\def\tcgp@abs#1{\ifnum#1<0 -\fi #1}
\def\tcgp@not#1{\ifnum#1 1 \else 0 \fi =0 }

%% \tcgp@pixel
\def\tcgp@pixel#1#2{%
  \put(#1,#2){\rule{\unitlength}{\unitlength}}%
}

%% \tcgp@gpt@setrn
\def\tcgp@gpt@setrn{%
  \tcgp@rn=0 \tcgp@t=0
  \@whilenum\tcgp@not{\tcgp@t>\tcgp@n}\do{%
    \advance\tcgp@rn1 \tcgp@t=\tcgp@rn \multiply\tcgp@t\tcgp@rn
  }%
  \advance\tcgp@rn-1
}

%% \tcgp@gpt@init
\def\tcgp@gpt@init{%
  \tcgp@x=-1
  \@whilenum \tcgp@x<\tcgp@n \do{%
    \advance\tcgp@x1 \tcgp@y=-1
    \@whilenum \tcgp@y<\tcgp@n \do{%
      \advance\tcgp@y1 \tcgp@set{\tcgp@x}{\tcgp@y}%
    }%
  }%
  \tcgp@reset{0}{0}\tcgp@reset{1}{0}\tcgp@reset{0}{1}%
}

%% \tcgp@gpt@sieve
\def\tcgp@gpt@sieve{%
  \tcgp@x=0
  \@whilenum \tcgp@x<\tcgp@rn \do{%
    \advance\tcgp@x1 \tcgp@y=-1
    \@whilenum \tcgp@y<\tcgp@x \do{%
      \advance\tcgp@y1
      \ifnum \tcgp@cur{\tcgp@x}{\tcgp@y}>0
        \tcgp@px=\tcgp@x \tcgp@py=\tcgp@y
        \@whilenum\tcgp@not{\tcgp@px>\tcgp@n}\do{%
          \tcgp@qx=\tcgp@px \tcgp@qy=\tcgp@py \tcgp@t=1
          \@whilenum \tcgp@t>0 \do{%
            \tcgp@reset{\tcgp@qx}{\tcgp@qy}\tcgp@reset{\tcgp@qy}{\tcgp@qx}%
            \advance\tcgp@qx-\tcgp@y \advance\tcgp@qy\tcgp@x
            \ifnum \tcgp@qx<0 \tcgp@t=0 \fi
            \ifnum \tcgp@qy>\tcgp@n \tcgp@t=0 \fi
          }%
          \tcgp@qx=\tcgp@px \tcgp@qy=\tcgp@py \tcgp@t=1
          \@whilenum \tcgp@t>0 \do{%
            \tcgp@reset{\tcgp@qx}{\tcgp@qy}\tcgp@reset{\tcgp@qy}{\tcgp@qx}%
            \advance\tcgp@qx\tcgp@y \advance\tcgp@qy-\tcgp@x
            \ifnum \tcgp@qx>\tcgp@n \tcgp@t=0 \fi
            \ifnum \tcgp@qy<0 \tcgp@t=0 \fi
          }%
          \advance\tcgp@px\tcgp@x \advance\tcgp@py\tcgp@y
        }%
        \tcgp@set{\tcgp@x}{\tcgp@y}\tcgp@set{\tcgp@y}{\tcgp@x}%
      \fi
    }%
  }%
}

%% \tcgp@gpt@show
\def\tcgp@gpt@show{%
  \tcgp@x=-\tcgp@n \advance\tcgp@x-1
  \@whilenum \tcgp@x<\tcgp@n \do{%
    \advance\tcgp@x1 \tcgp@y=-\tcgp@n \advance\tcgp@y-1
    \@whilenum \tcgp@y<\tcgp@n \do{%
      \advance\tcgp@y1
      \tcgp@px=\tcgp@abs\tcgp@x \tcgp@py=\tcgp@abs\tcgp@y
      \ifnum \tcgp@cur{\tcgp@px}{\tcgp@py}>0
        \tcgp@qx=\tcgp@x \multiply\tcgp@qx\tcgp@x
        \tcgp@qy=\tcgp@y \multiply\tcgp@qy\tcgp@y \advance\tcgp@qx\tcgp@qy
        \tcgp@qy=\tcgp@n \multiply\tcgp@qy\tcgp@n
        \ifnum\tcgp@not{\tcgp@qx>\tcgp@qy}%
          \tcgp@pixel{\tcgp@x}{\tcgp@y}%
        \fi
      \fi
    }%
  }%
}

\endinput

テスト用文書は以下の通り。

[test-tcgptexture.tex]
\documentclass[a4paper]{article}
\usepackage[scale=0.9]{geometry}
\usepackage{tcgptexture}
\begin{document}
\begin{center}
\gptexture{99}
\end{center}
\end{document}
f:id:zrbabbler:20121214015117p:image

ここまでの内容をマスターしたら、もう「TeX でマンデルブロ集合」も怖くない!?

*1:ここでは名前参照のマクロに代入される値が定数(0 や 1)なので「nameedef」は不要で \@namedef で済む。

*2:「マクロ」にならない「関数」との違いについて少し補足する。例えば、\def\ansA{42} というマクロがあると、コード中の「42」という記述を \ansA に置き換えることができる(かも知れない)。これに対して、\def\ansB{\count@=42 \the\count@} のようなマクロは確かに実行すると \ansA と同じく「42」という出力を行うが、これはコード中の「42」という記述の代わりにはならない。ただ実際にはこの推論についても「値を表すコード」(= 必要に応じてマクロが展開される文脈)という留保条件がつく。

*3Luaイディオムでは判り難いのでここでは敢えて C 言語の書き方「(cond ? yes : no)」を採った。

*4:実はこの定義だと #1 の直後の空白文字トークンの扱いが微妙で、例えば \tcgp@{0=\tcgp@a} のように用いると「前の数値」によって吸収されず残ってしまう。ところが \ifnum...\fi の部分自体が比較式の左辺の数値の部分に相当しているので、「1」という数字を読む前にある空白トークンは結局無視されることになる。

2012-12-12

それでも TeX で「ガウス素数の模様」したい人のための何か (1)

【頼りなく宣伝】
2012/12/01 〜 2012/12/25

TeX & LaTeX Advent Calendar

こちらは(ry

*  *  *

「変換して TeX プログラミングの例」シリーズの最終回。今回は「ガウス素数の模様」の図を TeX で描いてみる。

ガウス素数の模様って何?

Wikipedia*1を参照。右にある図がその模様である。ということで説明終わり。

……でよいと思うがもう少し説明してみる。素数というのは「自明でない約数を持たない自然数」のことで、列挙すると次のようになるのであった。

2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, ……

いま、1 から順に自然数を数えて、それが素数であれば黒、なければ白の四角を並べると次のようになる。

□■■□■□■□□□■□■□□□■□■□□□■□□□□□■□■□□□□□■□□□……

正の実数に対して定義されていた「整数」「素数」を拡張して、複素数の世界について《整数》や《素数》という概念を定義することができ、それぞれ「ガウス整数」「ガウス素数」と呼ばれる。*2その《素数》から上の図に相当するものを作ると、二次元の上下左右に無限に続く「模様」になる。それが「ガウス素数の模様」である。

Lua で「ガウス素数の模様」してみた

いつも通り、グローバル変数のみの Lua プログラム

[pascaltrig0.lua]
-- 整数型: n, rn, x, y, px, py, qx, qy
-- 配列型: a

-- gptexture(整数n)
-- 絶対値がn以下の範囲での模様を出力する.
function gptexture(_1)
  n = _1
  if n < 1 then n = 1 end
  gpt_setrn()
  gpt_init()
  gpt_sieve()
  gpt_show()
end

-- gpt_setrn()
-- nの平方根を整数に切り捨てた値をrnに代入する.
function gpt_setrn()
  rn = math.floor(math.sqrt(n))
end

-- gpt_init()
-- 篩の配列の初期化.
-- ※篩の2次元配列 a の要素は最初は全て 1 にしておき, x+yi が素数で
-- ないと判明したら, a[x][y] を 0 に変える.
function gpt_init()
  a = {}
  for x = 0, n do
    a[x] = {}
    for y = 0, n do
      a[x][y] = 1
    end
  end
  a[0][0] = 0
  a[1][0] = 0; a[0][1] = 0
end

-- gpt_sieve()
-- 篩のアルゴリズムを実行する.
function gpt_sieve()
  for x = 1, rn do
    for y = 0, x do
      if a[x][y] > 0 then
        px, py = x, y
        while px <= n do
          qx, qy = px, py
          while qx >= 0 and qy <= n do
            a[qx][qy] = 0; a[qy][qx] = 0
            qx, qy = qx - y, qy + x
          end
          qx, qy = px, py
          while qx <= n and qy >= 0 do
            a[qx][qy] = 0; a[qy][qx] = 0
            qx, qy = qx + y, qy - x
          end
          px, py = px + x, py + y
        end
        a[x][y] = 1; a[y][x] = 1
      end
    end
  end
end

-- gpt_show()
-- 篩の配列 a にある結果に基づいて模様を出力する.
-- (-n,-n)から(n,n)までの範囲を, (2n+1)行(2n+1)列の文字の
-- 集まりで表す. 絶対値がn以下でかつ素数ならば「#」、
-- それ以外は空白となる.
function gpt_show()
  for x = -n, n do
    for y = -n, n do
      px, py = math.abs(x), math.abs(y)
      if a[px][py] > 0 and x * x + y * y <= n * n then
        io.write("#")
      else
        io.write(" ")
      end
    end
    io.write("\n")
  end
end

-- メイン
gptexture(39)

これを実行すると以下のような「模様」が出力される。

                                                                               
                                #   #     #   #                                
                               #     #   #     #                               
                                  #   # #   #                                  
                       #       #     #   #     #       #                       
                        #   # #   #         #   # #   #                        
                   #           #     #   #     #           #                   
                        # #       # #     # #       # #                        
                   #   #     #   # #   #   # #   #     #   #                   
                #         # #                     # #         #                
                       #     #   # #       # #   #     #                       
              #         # #       #         #       # #         #              
                 # #         #       #   #       #         # #                 
              #   #         # #   #   # #   #   # #         #   #              
           # # # #     # # #     # #       # #     # # #     # # # #           
              #     #             #   # #   #             #     #              
         #       # # #     #   #       #       #   #     # # #       #         
            # # #     # # #       #         #       # # #     # # #            
             #               #     #       #     #               #             
      # #   #   #   #     # #   #   # # # #   #   # #     #   #   #   # #      
               #   #   # #   #   #     #     #   #   # #   #   #               
                #     #         # #         # #         #     #                
                 #   #     # # #     #   #     # # #     #   #                 
    #   # #   #     #         #   #   # #   #   #         #     #   # #   #    
     # #   #     #       #         # #   # #         #       #     #   # #     
              #     #   #   # #       # #       # #   #   #     #              
       # # #     # #       # # #     #   #     # # #       # #     # # #       
              # #     #   #     #             #     #   #     # #              
     #   #   #     #     #       # #   #   # #       #     #     #   #   #     
        # # #     # # #   #   # #   # # # #   # #   #   # # #     # # #        
     #       #         # #   #     #       #     #   # #         #       #     
  # # #         #     #   #     # # #     # # #     #   #     #         # # #  
 #                 # #     # # #     # # #     # # #     # #                 # 
        # #   #     #       #     #   # #   #     #       #     #   # #        
   # # #   # # # #   # #       # # # #   # # # #       # #   # # # #   # # #   
        # #   #   #     #   # #   #   # #   #   # #   #     #   #   # #        
 #     #           #         # #     # # #     # #         #           #     # 
  # # #     #         # # #     # # # # # # # #     # # #         #     # # #  
   #         # #   #   # #   #   # # ## ## # #   #   # #   #   # #         #   
        #       #   #       #   #   #     #   #   #       #   #       #        
   #         # #   #   # #   #   # # ## ## # #   #   # #   #   # #         #   
  # # #     #         # # #     # # # # # # # #     # # #         #     # # #  
 #     #           #         # #     # # #     # #         #           #     # 
        # #   #   #     #   # #   #   # #   #   # #   #     #   #   # #        
   # # #   # # # #   # #       # # # #   # # # #       # #   # # # #   # # #   
        # #   #     #       #     #   # #   #     #       #     #   # #        
 #                 # #     # # #     # # #     # # #     # #                 # 
  # # #         #     #   #     # # #     # # #     #   #     #         # # #  
     #       #         # #   #     #       #     #   # #         #       #     
        # # #     # # #   #   # #   # # # #   # #   #   # # #     # # #        
     #   #   #     #     #       # #   #   # #       #     #     #   #   #     
              # #     #   #     #             #     #   #     # #              
       # # #     # #       # # #     #   #     # # #       # #     # # #       
              #     #   #   # #       # #       # #   #   #     #              
     # #   #     #       #         # #   # #         #       #     #   # #     
    #   # #   #     #         #   #   # #   #   #         #     #   # #   #    
                 #   #     # # #     #   #     # # #     #   #                 
                #     #         # #         # #         #     #                
               #   #   # #   #   #     #     #   #   # #   #   #               
      # #   #   #   #     # #   #   # # # #   #   # #     #   #   #   # #      
             #               #     #       #     #               #             
            # # #     # # #       #         #       # # #     # # #            
         #       # # #     #   #       #       #   #     # # #       #         
              #     #             #   # #   #             #     #              
           # # # #     # # #     # #       # #     # # #     # # # #           
              #   #         # #   #   # #   #   # #         #   #              
                 # #         #       #   #       #         # #                 
              #         # #       #         #       # #         #              
                       #     #   # #       # #   #     #                       
                #         # #                     # #         #                
                   #   #     #   # #   #   # #   #     #   #                   
                        # #       # #     # #       # #                        
                   #           #     #   #     #           #                   
                        #   # #   #         #   # #   #                        
                       #       #     #   #     #       #                       
                                  #   # #   #                                  
                               #     #   #     #                               
                                #   #     #   #                                
                                                                               

このプログラムでは「エラトステネスの篩〈ふるい〉」をガウス整数に拡張したアルゴリズムを使っている。「篩」を表す配列は(複素数なので)2 次元のものが必要になる。これまで配列は全て名前参照として実現していたが、2 次元の配列も名前参照として容易に扱える。例えば次のように変換すればよい。

a[2][3]  →  _G["a/2/3"]
a[x][y]  →  _G["a/"..x.."/"..y]

この後者を TeX に直すと \@nameuse{tcgp@a/\number\tcgp@x/\number\tcgp@y} という非常に長いコードになる。これが何度も出てくるのは嫌なので、TeX ではマクロで表すことにしたい。そこで、「a[x][y]=1 の代入」「a[x][y]=0 の代入」「a[x][y] の参照」を set(x,y)reset(x,y)cur(x,y) という関数で表すことにする。(後掲の gptexture1.lua の (※1) の箇所。)

最終的な Lua 内変換を行った後のプログラムコードを示す。

-- 整数型: n, rn, x, y, px, py, qx, qy, t
-- 配列型: a

-- gptexture(整数n)
function gptexture(_1)
  n = _1
  if n < 1 then n = 1 end
  gpt_setrn()
  gpt_init()
  gpt_sieve()
  gpt_show()
end

-- (※1)
function set(_1,_2)   _G["a/".._1.."/".._2] = 1 end
function reset(_1,_2) _G["a/".._1.."/".._2] = 0 end
function cur(_1,_2)   return _G["a/".._1.."/".._2] end

-- gpt_setrn() (※2)
function gpt_setrn()
  rn = 0; t = 0
  while not (t > n) do
    rn = rn + 1; t = rn; t = t * rn
  end
  rn = rn - 1
end

-- gpt_init()
function gpt_init()
  x = -1
  while x < n do
    x = x + 1; y = -1
    while y < n do
      y = y + 1; set(x, y)
    end
  end
  reset(0, 0); reset(1, 0); reset(0, 1)
end

-- gpt_sieve()
function gpt_sieve()
  x = 0
  while x < rn do
    x = x + 1; y = -1
    while y < x do
      y = y + 1
      if cur(x, y) > 0 then
        px = x; py = y
        while not (px > n) do -- (※3)
          qx = px; qy = py; t = 1
          while t > 0 do  -- (※4)
            reset(qx, qy); reset(qy, qx)
            qx = qx - y; qy = qy + x
            if qx < 0 then t = 0 end
            if qy > n then t = 0 end
          end
          qx = px; qy = py; t = 1
          while t > 0 do
            reset(qx, qy); reset(qy, qx)
            qx = qx + y; qy = qy - x
            if qx > n then t = 0 end
            if qy < 0 then t = 0 end
          end
          px = px + x; py = py + y
        end
        set(x, y); set(y, x)
      end
    end
  end
end

-- gpt_show()
function gpt_show()
  x = -n; x = x - 1
  while x < n do
    x = x + 1; y = -n; y = y - 1
    while y < n do
      y = y + 1
      px = math.abs(x); py = math.abs(y) -- (※5)
      if cur(px, py) > 0 then
        qx = x; qx = qx * x
        qy = y; qy = qy * y; qx = qx + qy
        qy = n; qy = qy * n
        if not (qx > qy) then io.write("#")
        else io.write(" ")
        end
      else io.write(" ")
      end
    end
    io.write("\n")
  end
end

-- メイン
gptexture(39)

特に注意すべきことを挙げる。

  • ※2:gpt_setrn() では math.sqrt() を使っているが、TeX では平方根を自前で求める必要がある。ここでは効率は高くなくてもよい*3のでニュートン法等ではなく単純に rn をループさせて平方根(の切捨て)を求めている。
  • ※3: TeX には <= の比較が直接できないので、> の否定(not)として表す。ただし not を扱うのはそれほど簡単ではない。(後述)
  • ※4: 元々は while A and B do ... end というループであった。しかし TeX (の \@whilenum)では条件に単純な変数(や定数)同士の比較しか書けないので、何とかしてその形に持ち込む必要がある。ここでは整数変数 tフラグとして用いて、
    t = 1
    while t > 0 do
      ...
      if not A then t = 0 end
      if not B then t = 0 end
    end
    
    というパターンに書き換えている。これは元と完全に同値でなくて後判定(repeat ... while A and B に相当する形)に変わっているが、実際は初回は必ずループに入ることが判っているので動作は同じになる。
  • ※5: 絶対値 math.abs(x)
    \ifnum\tcgp@x<0 -\fi\tcgp@x
    
    で著すことができる。なので敢えて「関数を手続きに変換する」対象にしなかった。

(続く)

*1:「ガウス整数」の項目の中の一節。2012年6月8日更新の版。

*2ガウス整数は「(整数)+(整数)·i」の形の複素数。そして「《自明約数》を持たないガウス整数」がガウス素数である。

*3:明らかに gpt_sieve() の処理が所要時間の大部分を占める。

2012-12-07

それでも TeX でパスカルの三角形したい人のための何か

【限りなく宣伝】
2012/12/01 〜 2012/12/25

TeX & LaTeX Advent Calendar

こち(ry

*  *  *

「変換して TeX プログラミング」の例シリーズ。

Lua で「パスカルの三角形」してみた

いつも通り、グローバル変数のみの Lua プログラム

[pascaltrig0.lua]
-- 整数型: n, m
-- 配列型: v
v = {} -- 固定

--(公開) pascal_trig(整数n)
-- 第n行までのパスカルの三角形を出力する.
function pascal_trig(_1)
  for n = 0, _1 do
    v[n] = 1
    for m = n - 1, 1, -1 do
      v[m] = v[m] + v[m - 1]
    end
    for m = 0, n do
      io.write(("%4d"):format(v[m]))
    end
    io.write("\n")
  end
end

-- メイン
pascal_trig(10)

実行結果。

   1
   1   1
   1   2   1
   1   3   3   1
   1   4   6   4   1
   1   5  10  10   5   1
   1   6  15  20  15   6   1
   1   7  21  35  35  21   7   1
   1   8  28  56  70  56  28   8   1
   1   9  36  84 126 126  84  36   9   1
   1  10  45 120 210 252 210 120  45  10   1

配列を名前参照に書き換えた後のプログラム

[pascaltrig1.lua]
-- 整数型: n, m, j, k
-- 配列型: v

--(公開) pascal_trig(整数n)
function pascal_trig(_1)
  n = -1
  while n < _1 do
    n = n + 1
    _G["v/"..n] = 1
    m = n; m = m - 1
    while m > 0 do
      j = m; j = j - 1;
      k = _G["v/"..j]; k = k + _G["v/"..m]
      _G["v/"..m] = k; m = j
    end
    m = -1
    while m < n do
      m = m + 1
      io.write(("%4d"):format(_G["v/"..m]))
    end
    io.write("\n")
  end
end

-- メイン
pascal_trig(10)
TeX で「パスカルの三角形」してみた

出力は次のような感じで行う。

\begin{center}\small
\makebox{2.5em}{1}\par
\makebox{2.5em}{1}\makebox{2.5em}{1}\par
\makebox{2.5em}{1}\makebox{2.5em}{2}\makebox{2.5em}{1}\par
\end{center}

つまり 1 つの数を一定の幅(中央寄せ)で出力し、全体も中央揃えとする。これで左右対称の綺麗な三角形の形に全体が出力される。ここで、\small2.5em は出力する環境(フォントの種類)や pascal_trig引数(何段まで出力するか)により最適な値が依存する。なので、ここはマクロ \pascaltrigfont\pascaltrigcellwidth として可変にしておく(\renewcommand で設定を変えられる)。

完成したパッケージのコードは以下の通り。パッケージ名は tcpascaltrig、名前空間は tcpc。公開命令は \pascaltrig である。

[tcpascaltrig.sty]
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{tcpascaltrig}

%% 変数定義
%(整数値)
\newcount\tcpc@n
\newcount\tcpc@m
\newcount\tcpc@j
\newcount\tcpc@k
%(配列型)
%\tcpc@v/*

\def\tcpc@nameedef#1{%
  \expandafter\edef\csname#1\endcsname
}

%%(公開:マクロ) \pascaltrigfont - 出力に用いるフォント指定
\newcommand*{\pascaltrigfont}{\small}
%%(公開:マクロ) \pascaltrigcellwidth - 1つの数の出力の幅
\newcommand*{\pascaltrigcellwidth}{2.5em}

%%(公開) \pascaltrig{整数n}
% 第n行(0から)までのパスカルの三角形を出力する.
\newcommand*{\pascaltrig}[1]{%
  \begin{center}\pascaltrigfont
    \tcpc@n=-1
    \@whilenum \tcpc@n<#1\relax \do{%
      \advance\tcpc@n 1
      \tcpc@nameedef{tcpc@v/\the\tcpc@n}{1}%
      \tcpc@m=\tcpc@n \advance\tcpc@m -1
      \@whilenum \tcpc@m>0 \do{%
        \tcpc@j=\tcpc@m \advance\tcpc@j -1
        \tcpc@k=\@nameuse{tcpc@v/\the\tcpc@j}%
        \advance\tcpc@k\@nameuse{tcpc@v/\the\tcpc@m}%
        \tcpc@nameedef{tcpc@v/\the\tcpc@m}{\the\tcpc@k}%
        \tcpc@m=\tcpc@j
      }%
      \tcpc@m=-1
      \@whilenum \tcpc@m<\tcpc@n \do{%
        \advance\tcpc@m 1
        \makebox[\pascaltrigcellwidth]{\@nameuse{tcpc@v/\the\tcpc@m}}
      }%
      \par
    }%
  \end{center}%
}

\endinput

テスト用文書は以下の通り。

[test-tcpascaltrig.tex]
\documentclass[a4paper]{article}
\usepackage[scale=.8]{geometry}
\usepackage{tcpascaltrig}
\begin{document}
\pascaltrig{15}
\end{document}
f:id:zrbabbler:20121207223528p:image

2012-12-06

それでも TeX でビンソートしたい人のための何か

【はかなく宣伝】
2012/12/01 〜 2012/12/25

TeX & LaTeX Advent Calendar

こちらは(ry

*  *  *

例の「TeXsort」では満足しない人のために、「変換して TeX プログラミング」でビンソートを取り扱う。配列を含むプログラムの変換の例。

Lua で「ビンソート」してみた

bin_sort(ary)整数値の要素をもつ配列 ary を昇順に整列する(上書きする)。*1ここでの処理では、まず配列を一度スキャンして最小値と最大値を求めた上で、その範囲の添字をもつ係数の「配列ct*2を用意する、という手順をとっている。それ以外は通常のビンソートの手順(Wikipedia「バケットソート」を参照*3)と同じである。

なお、bin_sort() だけ TeX で実装しても LaTeX で実行を確認できるコードが書けないので、ここでは動作確認のデモとして demo_bin_sort(x,...) という関数を用意している。引数整数列からなる配列について bin_sort() を実行しその結果を表示している。

[binsort0.lua]
-- 整数型: j, k, m, min, max
-- 配列型: ct  (ary は引数)

--(公開) bin_sort(配列ary)
-- 整数配列 ary を昇順に整列する. (結果で上書きする.)
function bin_sort (ary)
  -- 入力データの最小値, 最大値を求める
  min = ary[1]; max = min
  for k = 1, #ary do
    if min > ary[k] then min = ary[k] end
    if max < ary[k] then max = ary[k] end
  end
  -- 計数の配列を初期化
  ct = {}
  for m = min, max do
    ct[m] = 0
  end
  -- 計数を行う
  for k = 1, #ary do
    m = ary[k]; ct[m] = ct[m] + 1
  end
  -- 計数結果に基づき値を並べる
  k = 0
  for m = min, max do
    for j = 1, ct[m] do
      k = k + 1; ary[k] = m
    end
  end
end

---- 以下はデモ用コード

--(公開) demo_bin_sort(整数,...)
-- デモ用に以下の処理を行う.
-- * 引数(可変個数)の整数からなる配列を作成する.
-- * その内容を表示する.
-- * 配列を bin_sort() で整列する.
-- * 再び内容を表示する.
function demo_bin_sort(...)
  ary = { ... }
  print_array(ary)
  bin_sort(ary)
  print_array(ary)
end

--(公開) print_array(配列ary)
-- 配列 ary の内容を出力する.
function print_array(ary)
  for k = 1, #ary do
    io.write(""..ary[k].." ")
  end
  io.write("\n")
end

-- メイン
demo_bin_sort(3, 1, 4, 1, 5, 9, 2, 6, 3, 5, 8, 9)

実行結果。

3 1 4 1 5 9 2 6 3 5 8 9 
1 1 2 3 3 4 5 5 6 8 9 9 

さて、例によって、配列変数は「名前参照」の形に書き換えることになるが、その際に「配列変数引数」というのが問題になる。*4「名前参照」で配列 ary を表現した場合、「ary 自体に相当するオブジェクト(モノ)」は存在しない。従って、配列自体は "ary" という名前(文字列)で指示することになる。

もう一つの問題は「配列の長さ」である。Lua では配列 ary の長さを #ary で得ることができるが、「名前参照」ではそのような機能は備わっていないし、また配列の長さを求める手段も存在しない。*5そこで、ここでは、「ary という名前の『配列』の長さは『ary/*』という名前の変数に入っている」という規則を採用することにする。もちろん ary の要素を書き換えたときに ary/* が自動更新されるわけではないので、必要な時に正しい値を入れておく必要がある。

書き換え後のコードは以下のようになった。

[binsort1.lua]
-- 整数型: j, k, l, m, min, max
-- 配列型: ct

--(公開) bin_sort(配列名)
function bin_sort (_1)
  min = _G[_1.."/1"]; max = min
  l = _G[_1.."/*"] -- 配列の長さ
  k = 0
  while k < l do
    k = k + 1
    m = _G[_1.."/"..k]
    if min > m then min = m end
    if max < m then max = m end
  end
  m = min; m = m - 1
  while m < max do
    m = m + 1
    _G["ct/"..m] = 0
  end
  k = 0
  while k < l do
    k = k + 1
    m = _G[_1.."/"..k]
   _G["ct/"..m] = _G["ct/"..m] + 1
  end
  k = 0
  m = min; m = m - 1
  while m < max do
    m = m + 1
    l = _G["ct/"..m]
    j = 0
    while j < l do
      j = j + 1
      k = k + 1; _G[_1.."/"..k] = m
    end
  end
end

---- 以下はデモ用コード

--(公開) demo_bin_sort(整数,...)
function demo_bin_sort(...)
  k = 0
  for _, m in pairs({...}) do -- 可変個数引数部分の for-each
    k = k + 1
    _G["ary/"..k] = m
  end
  _G["ary/*"] = k  -- 長さを設定する
  print_array("ary")
  bin_sort("ary")
  print_array("ary")
end

-- print_array(配列名)
function print_array(_1)
  l = _G[_1.."/*"]
  k = 0
  while k < l do
    k = k + 1
    io.write("".._G[_1.."/"..k].." ")
  end
  io.write("\n")
end

-- メイン
demo_bin_sort(3, 1, 4, 1, 5, 9, 2, 6, 3, 5, 8, 9)

demo_bin_sort() 冒頭の「引数列を配列 ary に読み込む」部分は、「えるたそ」(eltaso4.lua)と同じように @for マクロで実装する予定なので、それに合わせた処理になっている。前述のように、ここで「ary/*」という変数配列の長さを設定する必要がある。

TeX で「ビンソート」してみた

パッケージ名は tcbinsort、名前空間は tcbs。公開命令は \binsort{<名前>} およびデモ用の \demobinsort{<整数>,...}(コンマ区切りで複数値指定)である。

[tcbinsort.sty]
% tcbinsort.sty
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{tcbinsort}

%% 変数定義
%(整数値)
\newcount\tcbs@j
\newcount\tcbs@k
\newcount\tcbs@l
\newcount\tcbs@m
\newcount\tcbs@min
\newcount\tcbs@max
%(配列型)
%\tcbs@ct/*

\def\tcbs@nameedef#1{%
  \expandafter\edef\csname#1\endcsname
}

%%(公開) \binsort{配列名}
\newcommand*{\binsort}[1]{%
  \tcbs@min=\@nameuse{tcbs@#1/1}\relax \tcbs@max=\tcbs@min
  \tcbs@l=\@nameuse{tcbs@#1/*}\relax
  \tcbs@k=0
  \@whilenum \tcbs@k<\tcbs@l \do{%
    \advance\tcbs@k 1
    \tcbs@m=\@nameuse{tcbs@#1/\the\tcbs@k}\relax
    \ifnum \tcbs@min>\tcbs@m \tcbs@min=\tcbs@m \fi
    \ifnum \tcbs@max<\tcbs@m \tcbs@max=\tcbs@m \fi
  }%
  \tcbs@m=\tcbs@min \advance\tcbs@m -1
  \@whilenum \tcbs@m<\tcbs@max \do{%
    \advance\tcbs@m 1
    \tcbs@nameedef{tcbs@ct/\the\tcbs@m}{0}%
  }%
  \tcbs@k=0
  \@whilenum \tcbs@k<\tcbs@l \do{%
    \advance\tcbs@k 1
    \tcbs@m=\@nameuse{tcbs@#1/\the\tcbs@k}\relax
    \tcbs@j=\@nameuse{tcbs@ct/\the\tcbs@m}\relax \advance\tcbs@j 1
    \tcbs@nameedef{tcbs@ct/\the\tcbs@m}{\the\tcbs@j}%
  }%
  \tcbs@k=0
  \tcbs@m=\tcbs@min \advance\tcbs@m -1
  \@whilenum \tcbs@m<\tcbs@max \do{%
    \advance\tcbs@m 1 \tcbs@j=0
    \tcbs@l=\@nameuse{tcbs@ct/\the\tcbs@m}\relax
    \@whilenum \tcbs@j<\tcbs@l \do{%
      \advance\tcbs@j 1 \advance\tcbs@k 1
      \tcbs@nameedef{tcbs@#1/\the\tcbs@k}{\the\tcbs@m}%
    }%
  }%
}%

%%%% 以下はデモ用コード

%%(公開) \demobinsort{値,...}
\newcommand*{\demobinsort}[1]{%
  \tcbs@k=0
  % \@for のループ「変数」はマクロ(=文字列変数)であることに注意
  \@for\tcbs@x:=#1\do{% \@for\tcbs@m:=... はダメ
    \advance\tcbs@k 1
    \tcbs@nameedef{tcbs@ary/\the\tcbs@k}{\tcbs@x}%
  }%
  \tcbs@nameedef{tcbs@ary/*}{\the\tcbs@k}%
  % ↑ここまで読込部
  \tcbs@print@array{ary}%
  \binsort{ary}%
  \tcbs@print@array{ary}%
}

%% \tcbs@print@array{配列名}
\def\tcbs@print@array#1{%
  \tcbs@l=\@nameuse{tcbs@#1/*}\relax
  \tcbs@k=0
  \@whilenum \tcbs@k<\tcbs@l \do{%
    \advance\tcbs@k 1
    \@nameuse{tcbs@#1/\the\tcbs@k}\space
  }%
  \par
}

\endinput

テスト用文書は以下の通り。

[test-tcbinsort.tex]
\documentclass[a4paper]{article}
\usepackage{tcbinsort}
\begin{document}
\demobinsort{3,1,4,1,5,9,2,6,5,3,5,8,9}
\end{document}
f:id:zrbabbler:20121206221524p:image

*1:なお、Lua では配列の添字は 1 から始まる。ary の長さ(=要素数=最大添字)を #ary と記述する。

*2ary 中の最小値を 10、最大値を 20 とすると、ct[10]ct[20] の範囲だけが非 nil の値をもつ、ということ。「長さ」の概念がないので厳密には配列でなく連想配列である。

*3:なお、ここでの実装はリンク先の記事で「分布数えソート」と呼ばれているものと同じである。

*4:このプログラムでは「配列のソート」自体が「提供する機能」なので操作対象の配列を指定できることが不可欠であることに注意。

*5:名前参照で構成しているものは連想配列である。だから「長さを求める」ということを考える場合は、まず「長さ」の定義を定めないといけない。

2012-12-03

それでも TeX で連分数展開したい人のための何か

【よしなく宣伝】
2012/12/01 〜 2012/12/25

TeX & LaTeX Advent Calendar

こちらは平常運行。

*  *  *

前回に引き続いて「変換して TeX プログラミング」の例。運用の詳細は前回と同じなのでその辺りの解説は省略。

Lua で「連分数展開」してみた

show_cont_frac(x, y)有理数 x/y を連分数展開した結果を出力する。

[contfrac0.lua]
-- contfrac0.lua
-- [変数一覧]
-- 整数値: x, y, q, r

--(公開) show_cont_frac(整数x, 整数y)
-- 有理数 x/y の連分数展開を出力する.
function show_cont_frac(_1, _2)
  x = _1; y = _2
  if x < 0 or y < 1 then return end
  q, r = int_divide(x, y)
  if r == 0 then io.write(""..q.."\n")
  else
    io.write("["..q..";")
    while r > 0 do
      x = y; y = r
      q, r = int_divide(x, y)
      io.write(""..q)
      if r > 0 then io.write(",") end
    end
    io.write("]\n")
  end
end

-- int_divide(整数x,整数y)
-- xをyで整除した時の商と余りを返す.
function int_divide(_1, _2)
  return math.floor(_1 / _2), _1 % _2
end

-- テスト
show_cont_frac(355, 113)
show_cont_frac(99, 70)
show_cont_frac(740785, 516901)

出力は以下の通り。なお、ここでは連分数を線形書式を用いて表した。(Wikipedia「連分数」を参照。)

[3;7,16]
[1;2,2,2,2,2]
[1;2,3,4,5,6,7,8,9]

これをもう一段書き換えるとすると、int_divide() を前回のコードと全く同様に書き換えることになるだろう。ここでは省略していきなり TeX のコードに書き直す。

TeX で「連分数展開」してみた

パッケージ名は tccontfrac、名前空間は tccf。公開命令は \showcontfrac{<x>}{<y>} のみ。

[tccontfrac.sty]
% tccontfrac.sty
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{tccontfrac}

%% 変数定義
%(整数値)
\newcount\tccf@x
\newcount\tccf@y
\newcount\tccf@q
\newcount\tccf@r

%%(公開) \showcontfrac{整数x}{整数y}
\newcommand*{\showcontfrac}[2]{%
  \tccf@x=#1\relax \tccf@y=#2\relax
  \ifnum \tccf@x<0 \tccf@y=0 \fi
  \ifnum \tccf@y<1 \else
    \tccf@int@divide{\tccf@x}{\tccf@y}%
    \ifnum \tccf@r=0 \the\tccf@q
    \else
      \the\tccf@q +%
      \@whilenum \tccf@r>0 \do{%
        \tccf@x=\tccf@y \tccf@y=\tccf@r
        \tccf@int@divide{\tccf@x}{\tccf@y}%
        \ifnum \tccf@r>0 \frac{1}{\the\tccf@q +{}}\,%
        \else \frac{1}{\the\tccf@q}%
        \fi
      }%
    \fi
  \fi
}

%% \tccf@int@divide{整数x}{整数y}
\def\tccf@int@divide#1#2{%
  \tccf@q=#1\relax \divide\tccf@q#2\relax
  \tccf@r=-\tccf@q \multiply\tccf@r#2\relax \advance\tccf@r#1\relax
}

\endinput

連分数の出力の書式は「美文書」*1に紹介されているものを用いた。テスト用文書は以下の通り。

[test-tccontfrac.tex]
\documentclass[a4paper]{article}
\usepackage{amsmath}
\usepackage{tccontfrac}
\begin{document}
\begin{align*}
\frac{355}{113}       &= \showcontfrac{355}{113} \\
\frac{99}{70}         &= \showcontfrac{99}{70} \\
\frac{740785}{516901} &= \showcontfrac{740785}{516901}
\end{align*}
\end{document}
f:id:zrbabbler:20121203061333p:image

*1:第 5 版では 106 ページ。

2012-12-02

それでも TeX で素因数分解したい人のための何か

【よしなく宣伝】
2012/12/01 〜 2012/12/25

TeX & LaTeX Advent Calendar

こちらは平常運航。

*  *  *

以前に「普通の言語から変換して TeX プログラムを作る」という方法論について解説した。今日からは、具体的な変換の例をいくつか見ていくことにする。最初の例は「素因数分解」である。

Lua で「素因数分解」してみた

元となる Luaプログラムは次の通り。factorize(n) で n の素因数分解を表示する。test_factorize() はテスト用の関数で、2 から 100 までの素因数分解を表示する。*1

[tactorize0.lua]
-- factorize0.lua
-- [変数一覧]
-- 真偽値値: fac_head, fac_stop
-- 整数値: nn, n, k, e, q, r

--(公開) test_factorize()
-- 2から100までの素因数分解を出力する.
function test_factorize()
  for nn = 2, 100 do
    io.write(nn.." = ")
    factorize(nn)
    io.write("\n")
  end
end

--(公開) factorize(整数n)
-- nを素因数分解した式を出力する.
function factorize(_1)
  -- n : 未分解の整数
  -- k : 試行する除数
  -- fac_head : 未出力の状態か?
  -- fac_stop : 停止するか?
  n = _1; fac_head = true
  fac_try(2)
  k = 3; fac_stop = false
  while not fac_stop do
    fac_try(k)
    k = k + 2
  end
  if n > 1 then
    fac_out(n, 1)
  end
end

-- int_divide(整数x,整数y)
-- xをyで整除した時の商と余りを返す.
function int_divide(_1, _2)
  return math.floor(_1 / _2), _1 % _2
end

-- fac_try(整数x)
-- 除数xにおける試行.
function fac_try(_1)
  -- e : 指数
  -- q : 整除の商
  -- r : 整除の余り
  e = 0
  while true do
    q, r = int_divide(n, _1)
    if r == 0 then
      n = q; e = e + 1
    else break
    end
  end
  fac_out(_1, e)
  -- 未分解の数(n)が現在の除数より小さい場合は
  -- nは素数または1であり分解は完了している
  if n < _1 then
    fac_stop = true
  end
end

-- fac_out(整数p,整数e)
-- 素因数pの項を出力する. eは指数.
function fac_out(_1, _2)
  -- 指数0は出力しない
  if _2 == 0 then return end
  -- 先頭以外では「x」を出力
  if fac_head then fac_head = false
  else io.write(" x ")
  end
  -- eが1なら「p」、それ以外は「p^e」と出力
  io.write(_1)
  if _2 > 1 then io.write("^".._2) end
end

-- メイン
test_factorize()

ただし、この最初の段階で、幾つかの「変換」を済ませた形で記述している。

  • ローカル変数を使わず、グローバル変数のみを用いている。
  • 関数引数_1_2、……の名前にしている。
  • 整除の商と余りは TeX では同時に得られる。それを見越して、「商と余りを返す関数int_divide() を用意している。

プログラムの出力(test_factorize() の出力)は次の通り。

2 = 2
3 = 3
4 = 2^2
5 = 5
6 = 2 x 3
……(92行省略)……
99 = 3^2 x 11
100 = 2^2 x 5^2

これを、TeX に直す直前の形に変換したのが次のプログラム

[factorize1.lua]
-- factorize0.lua
-- [変数一覧]
-- 真偽値値: fac_head, fac_stop
-- 整数値: nn, n, k, e, q, r

--(公開) test_factorize()
function test_factorize()
  nn = 1
  while nn < 100 do
    nn = nn + 1
    io.write(tostring(nn).." = ")
    factorize(nn)
    io.write("\n")
  end
end

--(公開) factorize(整数n)
function factorize(_1)
  n = _1; fac_head = true
  fac_try(2)
  k = 3; fac_stop = false
  while (fac_stop and 1 or 0) == 0 do -- (*1)
    fac_try(k)
    k = k + 2
  end
  if n > 1 then
    fac_out(n, 1)
  end
end

-- int_divide(整数x,整数y)
function int_divide(_1, _2)
  -- (*2) q = 商, r = 余り とする
  q = _1; q = math.floor(q / _2)
  r = -q; r = r * _2; r = r + _1
end

-- fac_try(整数x)
-- 除数xにおける試行.
function fac_try(_1)
  e = 0; r = 0
  while r == 0 do -- (*3) 少し細工する
    int_divide(n, _1) -- q, r に返る
    if r == 0 then
      n = q; e = e + 1
    end
  end
  fac_out(_1, e)
  if n < _1 then
    fac_stop = true
  end
end

-- fac_out(整数p,整数e)
function fac_out(_1, _2)
  if _2 > 0 then
    if fac_head then fac_head = false
    else io.write(" x ")
    end
    io.write(tostring(_1))
    if _2 > 1 then io.write("^"..tostring(_2)) end
  end
end

-- メイン
test_factorize()

もちろん、出力は元の factorize0.lua と同じである。次の点を改修している。

  • for 文を while 文に直した。
  • break の除去を行った。
  • 関数を手続きに変換した。
  • (*1) の行に奇妙な式がある。この while 文の条件部は元々は not fac_stop であったが、TeX では(\@whilenum だけを使うという前提では)条件部には整数の式を書く必要がある。従って、三項演算子*2を用いて真偽値を整数値(0 か 1)に変換した上で 0 との等式比較としている。
  • (*2) で関数を手続きに変換する際、「実際の実行での値の格納先である q, r に直接代入する」という方式を用いた。
  • (*3) のループは元々は「r == 0 でない」という条件で末尾で抜け出す。これを「r == 0」の条件の whlie ループ(先頭判定)に変えるとともに、初回にループに入ることを保証するために予め r = 0 の代入を行った。
TeX で「素因数分解」してみた

そして最終的に TeX(on LaTeX)のプログラムに変換したのがこれ。パッケージ名は tcfactorize、名前空間tcfr、そして公開命令は \factorize\testfactorize である。

[tcfactorize.sty]
% tcfactorize.sty
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{tcfactorize}

%% 変数定義
%(真偽値)
\newif\iftcfr@fac@head
\newif\iftcfr@fac@stop
%(整数値)
\newcount\tcfr@nn
\newcount\tcfr@n
\newcount\tcfr@k
\newcount\tcfr@e
\newcount\tcfr@q
\newcount\tcfr@r

%%(公開) \testfactorize
\newcommand*{\testfactorize}{%
  \par\noindent
  \tcfr@nn=1
  \@whilenum\tcfr@nn<100 \do{%
    \advance\tcfr@nn 1
    $\makebox[2em][r]{\the\tcfr@nn}=% 左辺を2em幅で出力
    \factorize{\tcfr@nn}$%
    \ifnum\tcfr@nn<100 \\\fi % 最後以外では改行
  }%
  \par
}
%%(公開) \factorize{整数n}
\newcommand*{\factorize}[1]{%
  \tcfr@n=#1\relax \tcfr@fac@headtrue
  \tcfr@fac@try{2}%
  \tcfr@k=3 \tcfr@fac@stopfalse
  \@whilenum \iftcfr@fac@stop1 \else0 \fi=0 \do{%
    \tcfr@fac@try{\tcfr@k}%
    \advance\tcfr@k 2
  }%
  \ifnum \tcfr@n>1
    \tcfr@fac@out{\tcfr@n}{1}%
  \fi
}

%% \tcfr@int@divide{整数x}{整数y}
\def\tcfr@int@divide#1#2{%
  \tcfr@q=#1\relax \divide\tcfr@q#2\relax
  \tcfr@r=-\tcfr@q \multiply\tcfr@r#2\relax \advance\tcfr@r#1\relax
}

%% \tcfr@fac@try{整数x}
\def\tcfr@fac@try#1{%
  \tcfr@e=0 \tcfr@r=0
  \@whilenum \tcfr@r=0 \do{%
    \tcfr@int@divide{\tcfr@n}{#1}%
    \ifnum \tcfr@r=0
      \tcfr@n=\tcfr@q \advance\tcfr@e 1
    \fi
  }%
  \tcfr@fac@out{#1}{\tcfr@e}%
  \ifnum \tcfr@n<#1\relax
    \tcfr@fac@stoptrue
  \fi
}

%% \tcfr@fac@out{整数p}{整数e}
\def\tcfr@fac@out#1#2{%
  \ifnum #2>0
    \iftcfr@fac@head \tcfr@fac@headfalse
    \else \times
    \fi
    \number#1\relax
    \ifnum #2>1 ^{\number#2}\fi
  \fi
}

\endinput
  • 折角 TeX で実行しているのだから、出力はしょぼい ASCII 文字列でなく格好良い TeX の数式組版で出力した。\factorize は数式の LaTeX テキストを出力するが、それ自身は数式モードへの切り替えを行わない(だから数式モードで実行する必要がある)ことに注意。
  • 真偽値の変数TeX ではスイッチ(\newif で定義される if-トークン)で表される。if aaa then ... end\ifxx@aaa ... \fi と書ける。三項演算子TeX の if 文で書けて、例えば (fac_stop) ? 1 : 0*3\iftcfr@fac@stop 1 \else 0 \fi*4 と置き換えられる。

テスト用の LaTeX 文書と、その組版結果を掲載する。

[test-tcfactorize.tex]
\documentclass[a4paper]{article}
\usepackage{tcfactorize}
\begin{document}
\testfactorize
\end{document}
f:id:zrbabbler:20121203053506p:image

*1:なお、io.write() は、print() と異なり、出力に改行文字を付けない。

*2c and t or fLua三項演算子模倣するためのイディオム。C 言語での c ? t : f に相当する。

*3:C 言語の表記。

*4:数字の後の空白は数字列を終結させるために必要。前の空白文字は制御語直後にあるから無視される。

2012-12-01

アドベントがはじまった ― \begin{texadvent}

2012/12/01 〜 2012/12/25

TeX & LaTeX Advent Calendar

|(12/02)hak7a3 さん

というわけで始まりました。アドベントカレンダー。主催者かつ初日担当の ZR です。

冬の訪れとともに外の冷え込みも一段と厳しさを増していますが、クリスマスまでの 25 日間をできるだけ多くの皆さんの心温まる TeXLaTeX 話で埋めていきましょう。

*  *  *

というわけで早速ネタ披露。

% pLaTeX 文書
\documentclass[a6paper,papersize]{jsarticle}
\usepackage{color}
\definecolor{mygreen}{rgb}{0.0,0.6,0.2} % 色 mygreen を定義
\usepackage{pxchfon}                    % 和文フォントを指定
\setminchofont[0]{mogamb.ttc}           % 明朝→MogaMincho Bold
\setgothicfont{mplus-1p-medium.ttf}     % ゴシック→M+ 1P medium
\usepackage{tctoarulogo}                % とあるロゴの拡張機能(パッケージ)
\begin{document}
\begin{center}\huge
% \toarulogo[<色名>]<和文10文字>{<和文文字列>}
\toarulogo[mygreen]とある命令の展開制御{エクスパンドアフター}
\end{center}
\end{document}
f:id:zrbabbler:20121201120919p:image

……えーと、TeX 芸ファンの人なら既にご存じのように、itchyny さんのネタの丸パクリです(すみません……)。

わざわざ他人のネタをパクってまで言いたいことが実はあります。

素敵な TeX/LaTeX ネタを作るのに、必ずしも高度な TeX の知識は要らない

元ネタの記事に掲載されたコードを見ると、少し TeX をかじった人なら知っている基本的なプリミティブ(\kern\lower 等)がある他は LaTeX 命令で構成されています。これだと普段少し凝ったレイアウトを含む LaTeX 文書を作成している人ならできそうですね。

いや実は、このネタをするのに TeX 言語の知識は 1 ミリも要りません。以下に tctoarulogo パッケージのコードを示しますが、全部 LaTeX 命令で書かれています。

% tctoarulogo.sty
%% パッケージ宣言
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{tctoarulogo}[2012/12/01 v1.0]

%% 前提パッケージの読込
\RequirePackage{ifthen}
\RequirePackage{graphicx,color}

%% とある青色の色彩定義
\definecolor{toarublue}{rgb}{0.35,0.60,0.80}
%% とある赤色の色彩定義
\definecolor{toarured}{rgb}{0.90,0.35,0.20}

%% 変数宣言
\newlength{\toaruDimen}{}    % 汎用の長さ変数
\newcommand*{\toaruUpper}{}  % 上段部を出力するコード
\newcommand*{\toaruLower}{}  % 下段部を出力するコード
\newcommand*{\toaruColor}{}  % 色名の文字列

%%(公開) メインタイトル部分のフォント切替マクロ
\newcommand{\toarumainfont}{\rmfamily}
%%(公開) サブタイトル部分のフォント切替マクロ
\newcommand{\toarusubfont}{\sffamily\bfseries}
  % 上の 2 つはユーザが \renewcommand で再定義する.

%%(公開) \toarulogo[<色名>]<メイン文字列>{<サブ文字列>}
% とあるロゴの出力命令.
% <メイン文字列> は丁度 10 個の和文文字からなる必要がある.
% (実際には「10 個の引数」として取り扱われる.)
  % ここでは最初の7個の引数(色名と上段の文字群)を読んで,
  % \toaruColor と \toaruUpper を再定義している.
\newcommand*{\toarulogo}[7][black]{%
    % 色名を記憶する
  \renewcommand*{\toaruColor}{#1}%
    % 上段部のコード. picture 環境中の命令列である.
  \renewcommand*{\toaruUpper}{%
      % \put(横位置,縦位置){\toaruPutChar{横幅}{縦幅}{文字}}
      % を並べる. 長さの単位は 1em とする. (だから和文スケール
      % 値の違いは出力に影響しない.)
    \put(0.04,1.18){\toaruPutChar{0.91}{0.91}{#2}}%
    \put(0.74,1.18){\toaruPutChar{0.53}{0.55}{#3}}%
    \put(1.16,1.36){\toaruPutChar{0.47}{0.47}{#4}}%
    \put(1.53,1.18){\toaruPutChar{0.91}{0.91}{#5}}%
    \put(2.38,1.24){\toaruPutChar{0.78}{0.72}{#6}}%
    \put(3.11,1.18){\toaruPutChar{0.82}{0.82}{#7}}%
  }%
    % 引き続いて実行. 直後に残りの 5 個の引数があることに注意.
  \toaruNext
}

%% \toaruNext<下段文字列>{<サブ文字列>}
% toarulogo の下請け.
\newcommand*{\toaruNext}[5]{%
  \renewcommand*{\toaruLower}{%
      % 塗りつぶした四角を描く
    \put(0.37,0.07){\rule{0.87em}{1.00em}}%
      % その後に白色で最初の文字を描く
    \put(0.37,0.19){\color{white}\toaruPutChar{0.87}{1.04}{#1}}%
      % その後の 3 文字
    \put(1.22,0.34){\toaruPutChar{0.78}{0.78}{#2}}%
    \put(1.90,0.33){\toaruPutChar{0.91}{0.91}{#3}}%
    \put(2.74,0.18){\toaruPutChar{0.90}{1.13}{#4}}%
      % サブ文字列の出力
    \put(1.30,0.06){\scalebox{0.20}{\toaruSubtitle{#5}}}%
  }%
    % 引き続いて実行
  \toaruDispatch
}

%% \toaruDispatch
% toarulogo の下請け. \toaruUpper/Lower のコードを実際に
% 実行してロゴを出力する.
\newcommand*{\toaruDispatch}{%
    % 設定を局所化する必要がある.
    % ここでは「念のため」\mbox を使っている.
  \mbox{%
      % 前景色とフォント(メイン用)の設定
    \color{\toaruColor}\toarumainfont
      % \unitlength は picture で使う長さ数値の単位
    \setlength{\unitlength}{1em}%
      % picture 環境の中は単に \toaruUpper/Lower を呼ぶだけ.
    \begin{picture}(4.00,2.00)%
      \toaruUpper
      \toaruLower
    \end{picture}}%
    % これで実行終了
}

%% \toaruPutChar{<横幅>}{<縦幅>}{<文字>}
% 指定の寸法に伸縮して文字を出力する. 縦幅, 横幅は実数で
% 指定し, \unitlength 単位の長さを表す.
\newcommand*{\toaruPutChar}[3]{%
    % \toaruDimen に<文字>の横幅を代入
  \settowidth{\toaruDimen}{#3}%
    % 伸縮変形前の文字の見かけの寸法を正方形にするために
    % 高さ/深さを横幅の 0.88倍/0.12倍に強制的に合わせる.
    % 使用する和文TFMの種類により「元の縦幅」が変化する
    % 影響を無くすため. (歴史的事情により min10 や jis の
    % 高さ/深さは「奇妙な」値になっている.)
  \resizebox*{#1\unitlength}{#2\unitlength}{%
    \raisebox{0pt}[0.88\toaruDimen][0.12\toaruDimen]{#3}}%
      % \raisebox のオプション引数, および \resizebox の
      % *-形式の意味については参考書で確認してほしい.
}

%% サブタイトル部分の縮小前の横幅.
  % つまりサブタイトル部分は 6.8 文字分の横幅を確保して
  % いる. (実際は和文スケールの影響を受ける.)
  % 実際に出力する際には 0.2 倍のスケールが施されるので
  % 横幅は 1.36em となる.
\newcommand*{\toaruSubtWidth}{6.8em}
\newcommand*{\toaruSubtitle}[1]{%
    % ここでは文字列の横幅に応じて処理を変える. そのため
    % 横幅を調べて \toaruDimen に代入する.
  \settowidth{\toaruDimen}{\toarusubfont #1}%
  \ifthenelse{\lengthtest{\toaruDimen < \toaruSubtWidth}}{%
      % 横幅が確保した幅より小さい場合は均等割りにする.
      % 均等割りは \kanjiskip による方法を利用した. ただし
      % この方法は min10 使用時に小書き仮名の出力が不正になる
      % という欠点がある.
    \setlength{\kanjiskip}{0pt plus 1fill minus 1fill}%
    \makebox[\toaruSubtWidth][s]{\toarusubfont #1}%
      % オプション s は「両端揃え」を表す.
  }{%
      % 横幅が確保した幅より大きい場合は横方向に縮小する.
    \resizebox{\toaruSubtWidth}{\height}{\toarusubfont #1}%
  }%
    % ちなみに, サブ用のフォントへの切替を一番内側で行って
    % いる理由は, em の長さが変わってしまうのを避けるため.
}

%% EOF

そうです。TeX を知らなくても TeX 芸はできます。\expandafter が何をするものか皆目わからなくても \expandafter ネタは出せます。TeX & LaTeX Advent Calendar は TeXnician に限らず、TeX 言語学習者に限らず、LaTeX 初心者を含む TeX ユーザみんなのイベントです。気おくれせずにどしどし参加してください。

おねがいします。

(2 日目は hak7a3 さんです。)