* 基礎知識 [#s5fc1cf9] TeX の特徴の一つにマクロが作成できることが挙げられます。 マクロを活用することによって、ミスの少ない仕上がりや作業効率の向上などの効果が期待できます。 また、実用的な面を持つだけではなく、知的な趣味としても愉しむことができます。 マクロの作成技術を身につけるにあたって必要な基本知識を列挙しておきましょう。 これらの事柄を調べてゆくと自ずとマクロ作成技術が身に付くことでしょう。 参考書について:マクロを作成する上で必要となる TeX のプリミティブ命令の多くは、LaTeX の入門レベルの解説書では触れられていないことが多いです。『TeXブック』には当然解説されていますが、入手は困難なようです。 とりあえず,この Wiki の“[[TeXの本]]”の項で挙げられているものを参考になさってください. - トークン (文字トークンとコントロールシークエンス(コントロールワード/コントロールシンボル)) - カテゴリーコード (\catcode, \makeatletter, \sanitize) - 基本レジスタ (\count, \dimen, \skip/\muskip, \box, \toks, 数値と数字の違い) - 定義と代入 (\def, \let, \newcommand, \DeclareRobustCommand) - グルーピング ({, }, \bgroup, \egroup, \begingroup, \endgroup) - 条件分岐 (\if, \ifx, などの各種 \if) - 展開の制御 (\expandafter, \edef, \noexpand, \afterassignment) // \aftergroup は有効な使用例が少ないため,保留(ただし,color パッケージで // きわめて効果的に用いられています). - トークンの制御 (\the, \string, \csname, 空白トークン) - ファイル入出力(\openin/\closein, \read, \openout/\closeout, \write, \immediate) - デバッグ (\show, \showthe, \meaning, プリミティブとマクロ) - 組版上の問題に由来する処理(水平モード/垂直モード, \par, \halign/\valign, \mark などの, 一般的なプログラミングの話では出てこない処理を扱うプリミティブ) // これは,“基本知識”として挙げるべきかどうかは微妙. // また,1 項目にしてしまうのは乱暴かもしれません. - LaTeX における慣用的な処理(\@elt を用いたリストの処理や, \@testopt 等の常用される内部処理用マクロの用法) - そのほか融合問題,分類が困難な問題 // 問題カテゴリを追加してみました - (あと、どうしましょう?) * 基本練習問題 [#l9a8a164] // いかがでしょうか。 学習効果が高い、しゃれた問題を作るのは大変そう。 // 『TeXbook』を読めばいいってのは言いっこなし(^^;) いい問題ができたら書き込みましょう。 // あと,“実際に処理させてみればわかる”という問題以外は“解答”も // コメントとして入れておいてはどうでしょう. // 以下の問題では,特に断らない限り, // - 個々の文字のカテゴリーコードは plain TeX におけるデフォルトの値である // - 個々の文字の \lccode/\uccode の値は plain TeX におけるデフォルトの値である // // ものと仮定します. // // 一応,上記の断り書きは入れておいたほうがよろしいのでは? ////// ごもっともです。 ////// 入門者向けということで、デフォルトのカテゴリーコードになっていない可能性が ////// あるなどとはつゆほども思わない読者を想定していました。断り書きがなければ ////// 欠陥問題じゃないかと思うような読者にはぜひ執筆側に回ってもらいましょう(^^) 問題によっては解答がコメントとして書かれており, 上の「差分」から見ることができます。 ** トークンの読み込み [#r9336262] +余分な空白を嫌って全ての行末に``%''が付加されているが、不要なものもある。どれか。 \def\macro{% \ifnum\count0<0% \setbox0=\hbox{% \count0=-\count0% \the\count0% }% \else% \setbox0=\hbox{% \the\count0% }% \fi% \box0% }% // ``0''の直後(五ヶ所) // (この例の定義中の 0 はいずれも“数値として”読み取られるので, // それに続く(多くとも 1 個の)空白文字は吸収される.) // コントロールワードの直後(``\else'', ``\fi'') // 最後の``}''の直後(このマクロの定義が水平モードで行われない場合) +前問の \macro の定義中の % のうち,単に不要というだけでなく “有害”であるものはどれか(前問では“あってもなくても構わないか, せいぜい空白文字の有無の差にしか影響しない %”と “有害な %”の両方を尋ねているが,本問では特に“有害”なもののみを尋ねている). なお,本問においては“有害な %”とは >その % の有無によって単なる“空白トークンの有無”''以外''の相違が生じる可能性があるような % <のことを指す. // 有害なものは“\count0=-\count0%”の行の % と // 定義の末尾の “\box0%”の行の % の2箇所. // \count0=-\count0% の % によって \count0 の後の“数値の終端”を // 欠いているため,代入の右辺の \count レジスタの番号は // その直後にある \the\count0 を展開して得られる文字列を読まないと確定しない. // そのため,\box0 の中身になるはずの \the\count0 の部分がその直前にある // \count0=-\count0 の代入を行う前に展開されてしまう. // 実際,上記のように \macro を定義した状態で“\count0=-1 [\macro]”を // 処理してみると,“[-1]”という出力が得られる(それが意図通りという // 可能性もなくはないが,むしろ“[1]”という出力を期待していることが // 多いだろう). // また,定義の末尾の \box0% のところも,\box0 の直後に空白文字などの // “数値の終端”を欠いているため,“\macro 1”のような記述を行うと // (おそらく)意図通りではない出力が得られる. // [補足] 今の例の \macro の定義の末尾に \def\macro{... \box0 } のごとく // 明示的に空白文字を書き込むのを好まなければ, // \def\macro{... \box\z@} のごとく 0 の別名を用いるのもよい // (0 の代わりに \z@ を用いるのにはこのような積極的な意味がある場合も // あることに注意). ** カテゴリーコード [#gfea22ef] + “i”のカテゴリーコードが 0 で、“ ”(空白文字)のカテゴリーコードが 11 である場合、\macro This is a test. はどのようにトークン化されるか? // \relax の代わりに [relax] と書いて,個々のコントロール・ワードを // 明示することにすると,この問題の \macro This is a test. は // [macro Th][s ][s a test]. // (最後のピリオドのみ,文字トークン)のように扱われる(ただし,問題文中で // 断ってある i と空白文字以外の文字については,カテゴリーコードの変更は // なされていないものと仮定). + ファイル latex.ltx における \strip@pt の定義を解析し, このマクロの内部処理で用いられる \rem@pt の定義を単に \def\rem@pt#1.#2pt{#1\ifnum#2>\z@ .#2\fi} としたのでは うまくいかない理由を述べよ. // \rem@pt では“\the + \dimen レジスタ”が与える文字列の末尾の“p_{12}t_{12}” // を取り除くことになるので,通常の文字列“p_{11}t_{11}”を書式指定文字列に // したのではパターンマッチが意図通りには行われない. ** 基本レジスタ [#o2f1fc77] + 整数型の数値の四則演算を行う方法を挙げよ. 実数型の数値の場合はどうか. // 実数型の数値の乗除算以外については,\advance,\multiply,\divide という // プリミティブを用いてできる(整数については \count レジスタを用い, // 実数型の数値に対しては \dimen レジスタを用いる). // 実数型の数値の乗算は,\dimen@=2.72\p@ \dimen@=3.14\dimen@ // (この時点で,\dimen@ の値は 2.72 * 3.14 pt となる)のように // “係数”を使えばいい. // 実数型の数値の除算は,整数の除算に帰着させる. // \dimen0 = x [pt],\dimen2 = y (≠ 0) [pt] のとき, // 単に \divide\dimen0 by \dimen2 とすると \dimen0 の値は x/y [sp] // (しかも,小数点以下を切り捨てた値)となってしまうが, // 何らかの形で 65536 x/y [sp] = x/y [pt] が得られればよい. // 例えば,\multiply\dimen0 by 256 \divide\dimen2 by 256 としてから // \divide\dimien0 by \dimen2 とすると \dimen0 の値は // (256x)/(y/256) = 65536 x/y [sp] = x/y [pt] となる. // (ただし,あくまで整数演算なので,分母は正確に y/256 になるとは限らず, // その分の狂いが生じる.それを気にするような用途には,後述するような // 正確な計算を用いることになる.) // もちろん,『LaTeX 自由自在』で例示されているように, // \count0 = \dimen0 のようにいったん \count レジスタに代入したのち, // 整数の除算を用いて計算してもいい(もちろん,小数点以下の部分を // 計算するには,“余りを 10 倍したのち除数で割る”操作を繰り返せばいい). + 以下の操作を行ったときの \skip0,\skip2 の最終的な値はどうなるか. \skip0=10pt plus 5pt minus 3pt \skip2=2\skip0 \multiply\skip0 by 2 // 最終的には,\skip0 = 20pt plus 10pt minus 6pt,\skip2 = 20pt となる. // “係数”をつけた場合には伸縮度が失われることに注意. // 問: \skip レジスタの値を伸縮度も含めて実数倍するマクロを作成せよ. + \macroA,\macroB は引数をとらないマクロであるものとするとき, \edef\macro{\macroA\macroB} と \toks@\expandafter{\macroA} \@temptokena\expandafter{\macroB} \edef\macro{\the\toks@\the\@temptokena} はどのように違うか(ただし,\toks@,\@temptokena は LaTeX がすでに 用意している \toks レジスタ)? また,後者の処理を \toks レジスタを用いずに実現するにはどうすればよいか. // \toks レジスタを用いないほうでは,\macroA,\macroB に含まれるマクロなどは // 展開され,条件処理も行われてしまうが,\toks レジスタを用いたほうでは // \macroA,\macroB の置換テキストが単純に連結される. // 例えば,\macroA,\macroB が // \def\macroA{[\the\year]} // \def\macroB{\ifnum\year>10000 ???\else OK\fi} // のように定義されているとき,(この記述を行っている時点 2006 年では) // \edef\macro{\macroA\macroB} は \def\macro{[2006]OK} と定義したのと // 同じことになる一方,\toks レジスタを用いたほうは // \def\macro{[\the\year]\ifnum\year>10000 ???\else OK\fi} // と定義したのと同じことになる. // [注] \the + \toks レジスタが与えるトークン列は \edef での置換テキスト // 作成時には基本的にはそれ以上展開されないが,数値の読み取りなどの都合で // 展開せざるを得ない場合には展開されてしまう. // そのため,今の例でも \toks@ と \@temptokena の代わりに \toks0 と \toks2 を // 用いた上で \edef\macro{\the\toks0\the\toks2} とはしていない. // もっとも,\edef\macro{\the\toks0 \the\toks2} とすれば問題ない. // // また,\toks レジスタを用いたのと同じ,置換テキストの単純な連結は // \expandafter\expandafter\expandafter\def // \expandafter\expandafter\expandafter\macro // \expandafter\expandafter\expandafter{\expandafter\macroA\macroB} // という記述でも可能. + 次の記述を (La)TeX で処理すると,どのような出力が得られるか? \setbox0\hbox to10mm{X\hfil Y} \hbox to40mm{\copy0 \hfil \copy0} \hbox to40mm{\unhcopy0 \hfil \unhcopy0} \hbox to40mm{\box0 \hfil \box0} \setbox0\hbox to10mm{X\hfil Y} \hbox to40mm{\unhbox0 \hfil \unhbox0} // 問: 不特定多数の整数や寸法を必要とする処理は,それらを文字列で // 表したものを適当なマクロに保存すれば実現できる. // では,不特定多数のボックスを必要とする処理を実現するには // どうすればよいか. ** 定義と代入 [#gcea75b2] + \def\macro#1#2a#3.{#1;#2;#3;} と定義したとき、 \macro This is a test. の展開結果はどうなるか? \def\macro#1a#2#3.{#1;#2;#3;} だとしたらどうか? + あるユーザが,次のような記述を行った(\@firstoftwo,\@secondoftwo については,必要があればファイル latex.ltx を参照のこと). \documentclass{article} \begin{document} \makeatletter \def\issubstring#1#2{% \def\@tempa##1#1##2\@nil{\def\@tempa{##2}}% \@tempa#2#1\@nil \ifx\@tempa\@empty \expandafter\@secondoftwo \else \expandafter\@firstoftwo \fi} \makeatother \def\test#1#2{\issubstring{#1}{#2}% {``#1'' is contained in ``#2''.}% {``#1'' is not contained in ``#2''.}} \test{cat}{dog} \end{document} // つまり, // \issubstring は第 1 引数が第 2 引数の部分文字列であれば第 3 引数を実行し, // そうでなければ第 4 引数を実行するようなマクロというつもりである. 実際,上記の \test{cat}{dog} の出力は意図通りのものであった. 問: \issubstring はどのようなマクロで, \test{cat}{dog} の出力はどうなっていたのか?~ しかし,\test{aa}{a} を処理させるととんでもない結果が得られ, このユーザは頭を抱えた. // 実際,“aa”が“a”の部分文字列と判定されてしまったのだが, 問: いったいどのような結果が得られたのか? また,\issubstring の定義のどこに問題があり,どう修正すべきであったか? // \@tempa#2#1\@nil の行が問題. // 実際,\issubstring の第 1 引数が ABA の形で,第 2 引数は AB の形である // 場合(e.g. \issubstring{cat-cat}{cat-} などの場合)には, // 第 2 引数と第 1 引数の先頭部分がつながって“意図しない位置に”\@tempa の // 書式指定文字列が生じてしまう. // ここでは,\@tempa#2\@empty#1\@nil のように \@empty を入れればよい. // なお,\@empty も \issubstring の引数として用いられかねないような状況では, // \@empty の代わりに %_{12} あるいは ^^@_{12} あたりを用いればよいだろう. // [余談] LaTeX の内部処理用マクロ \in@ にもまったく同じ問題があるので, // ユーザ自身が \in@ を用いる場合には注意が必要. +latex.ltx内で定義されているマクロ\@defaultunitsの使い方とその効果を述べ,定義を解析せよ. // 文法上は \@defaultunits <たいていの代入操作><ごみ(存在しなくてもいい)>\@nnil // という形式で用いることができるが,使用例は // \def\setparindent#1{\@defaultunits\parindent#1pt\relax\@nnil} // のような具合に // \@defaultunits<寸法を代入できるもの><寸法または実数><単位>\relax\@nnil // となっていることが大半(というより,そのような用法を念頭に置いていると // 思われる).ここで,\relax は“代入に伴って,単位の後に続くマクロが // 不用意に展開される”ことを防ぐために必要であることに注意. // なお,今例示した \setparindent は引数として寸法または実数値 //(を 10 進表記した文字列)をとり,引数が寸法であるときには // \parindent をその引数に設定し,数値であるときには単位 pt を補ってできる // 寸法を \parident に代入するマクロ.\@defaultunits の効果(として // 念頭に置いていると思われるもの)はこの例のように“必要に応じて // デフォルトの単位を補うような”寸法の代入ができる,ということである. // 問: \@defaultunits の文法上の書式の中で <たいていの代入操作> と // 書いているように,\@defaultunits の直後では使えないような代入操作も // 存在する.そのような操作の例を挙げよ(cf. “The TeXbook”の第 24 章に // おける \afterassignment の説明). + 2 個のマクロ \macroA,\macroB の定義を入れ換える際に, \def\temp{\macroA} \def\macroA{\macroB} \def\macroB{\temp} という記述と \let\temp\macroA \let\macroA\macroB \let\macroB\temp という記述のどちらが意図通りに処理されるか? また,意図通りに処理されないほうはどのような結果になるか. // // \def と \let の相違を確認させる問題で,もっと気の利いた問題が // // ありましたら,ぜひこの問題と差し替えてください(出題者より). // 意図通りに処理されるのは後者. // 前者では \macroA,\macroB のどちらを用いても無限ループに陥る(か, // \macroA,\macroB を用いるまでに \temp が再定義されていたら // “無関係なもの”が実行されてしまう). // 実際,\temp が再定義されなかった場合について展開過程を // 考えてみると,\macroA → \macroB → \temp → \macroA(以下, // 繰り返しになる)のようになっている. +LaTeX2eにはカウンタに親子関係を追加する\@addtoresetというマクロがある.では,この逆,「縁切り」のマクロ,つまり,<cntA>が子供<cntA1>,<cntA2>,・・・,<cntAn>をもっているときに,<cntAi>(i=1,2,...,n)を<cntA>の子供ではなくするマクロを実装せよ. // amsdtx.cls で定義されているマクロ \@removefromreset は // \@removefromreset{<child>}{<parent>} // (<child>,<parent> はともに LaTeX のカウンタの名称)のように用いて, // \cl@<parent> から \@elt{<child>} を取り除く // という操作を行う(つまり,1 個のカウンタについて“縁切り”を行う). // あとは,このマクロを \@for あたりのループ処理の中で使えばよい. + アルファベットからなる文字列の先頭の文字のみを大文字にして出力するマクロを以下の条件で作成せよ. (1) アクセント記号はどこにもつかないことを前提とする (2) 先頭の文字にアクセント記号がつくことも許容する // 条件(1)の場合: // \def\capitalize#1{\@capitalize #1\@empty} // \def\@capitalize#1{% // \ifx#1\@empty\else // \ifnum`#1<`a // #1% // \else // \ifnum`#1>`z // #1% // \else // \ifcase`#1% // \or\or\or\or\or\or\or\or\or\or\or\or\or\or\or\or // \or\or\or\or\or\or\or\or\or\or\or\or\or\or\or\or // \or\or\or\or\or\or\or\or\or\or\or\or\or\or\or\or // \or\or\or\or\or\or\or\or\or\or\or\or\or\or\or\or // \or\or\or\or\or\or\or\or\or\or\or\or\or\or\or\or // \or\or\or\or\or\or\or\or\or\or\or\or\or\or\or\or\or // A\or B\or C\or D\or E\or F\or G\or H\or I\or J\or // K\or L\or M\or N\or O\or P\or Q\or R\or S\or T\or // U\or V\or W\or X\or Y\or Z% // \fi // \fi // \fi // \fi} // なお,この定義の狙いは \edef\@tempa{\capitalize{alpha}} のようなことを // してみるとわかるはず(こういう処理を考えないのなら,\uppercase あたりを // 用いてもっと素直に定義すればいい). // // 条件(2)の場合: // ここでは,アクセント記号は \"a のごとく“コマンドで”書くものと仮定. // inputenc パッケージを併用して latin-1 エンコーディングあたりで // 記述する場合や,Babel パッケージを使って "a のような記法を // 有効にした場合については,別途考えて欲しい. // // 例のごとく『実践解説』2.1.3 項のサンプルコードと本質的には同じなので, // そのサンプルと同様の(処理対象のテキストに \bgroup があっては // いけないといった)制限がある. // \newif\if@capitalize@firstletter // \def\capitalize#1{% // \begingroup // \protected@edef\@tempa{#1}% // \begingroup // \def\@tempb##1##2{\def##1{##2}\@tempb}% // \expandafter\@tempb\@uclclist\@tempb\@empty // \def\i{I}\def\j{J}% // \global\@capitalize@firstlettertrue // \expandafter\@capitalize\@tempa\@nnil // \expandafter\endgroup\@tempa // \endgroup} // \def\@capitalize{% // \let\@tempa\@empty // \@@capitalize} // \def\@@capitalize{\futurelet\let@token\@@@capitalize} // \def\@@@capitalize{% // \let\@tempb\@capitalize@checkchar // \ifx \let@token\@nnil \let\@tempb\@gobble // \else \ifx\let@token\@sptoken \let\@tempb\@capitalize@space // \else \ifx\let@token\bgroup \let\@tempb\@capitalize@bgroup // \fi\fi\fi // \@tempb} // \expandafter\def\expandafter\@list@of@special@char\expandafter{\@uclclist\i\j} // \def\@capitalize@checkchar{% // \if@capitalize@firstletter \expandafter\@capitalize@checkchar@ // \else \expandafter\@capitalize@skip // \fi} // \def\@capitalize@checkchar@#1{% // \def\@tempb{#1}% // \def\@tempc{\@tfor\@tempc:=}% // \let\@tempd\@firstoftwo // \expandafter\@tempc\@list@of@special@char\do{% // \ifx\@tempb\@tempc // \global\@capitalize@firstletterfalse // \let\@tempd\@secondoftwo // \fi}% // \@tempd // \@capitalize@checkchar@@ // {\expandafter\def\expandafter\@tempb\expandafter{#1}}% // \expandafter\expandafter\expandafter\def // \expandafter\expandafter\expandafter\@tempa // \expandafter\expandafter\expandafter{\expandafter\@tempa\@tempb}% // \@@capitalize} // \def\@capitalize@checkchar@@{% // \let\@tempc\@tempb // \@onelevel@sanitize\@tempc // \edef\@tempd{\expandafter\@car\@tempc\@empty\@nil}% // \edef\@tempc{\expandafter\@cdr\@tempc\@empty\@nil}% // \ifx\@tempc\@empty // \edef\@tempd{\count@=`\@tempd\relax}\@tempd // \ifnum`A>\count@\else \ifnum`Z<\count@\else // \global\@capitalize@firstletterfalse // \fi\fi // \ifnum`a>\count@\else \ifnum`z<\count@\else // \global\@capitalize@firstletterfalse // \edef\@tempc{\uppercase{\def\noexpand\@tempb{\@tempb}}}% // \@tempc // \fi\fi // \fi} // \def\@capitalize@skip{\@capitalize@skip@\@empty} // \def\@capitalize@skip@#1\@nnil{% // \expandafter\expandafter\expandafter\def // \expandafter\expandafter\expandafter\@tempa // \expandafter\expandafter\expandafter{\expandafter\@tempa#1}} // \begingroup // \def\:{\def\@capitalize@space}% // \expandafter\endgroup\: {% // \expandafter\expandafter\expandafter\def // \expandafter\expandafter\expandafter\@tempa // \expandafter\expandafter\expandafter{\expandafter\@tempa\space}% // \@@capitalize} // \def\@capitalize@bgroup{% // \if@capitalize@firstletter \expandafter\@capitalize@bgroup@ // \else \expandafter\@capitalize@skip // \fi} // \def\@capitalize@bgroup@#1{% // \begingroup // \@capitalize#1\@nnil // \expandafter\endgroup // \expandafter\toks@\expandafter{\expandafter{\@tempa}}% // \@temptokena\expandafter{\@tempa}% // \edef\@tempa{\the\@temptokena\the\toks@}% // \@@capitalize} // // 問:“fake small caps”を出力するマクロ \fakesmallcaps を定義せよ. // 例えば,文字サイズが \normalsize のところで用いられた // \fakesmallcaps{\textsl{Fake Small Caps}} は // \textsl{F{\small AKE} S{\small MALL} C{\small APS}} のような感じで // 扱われて欲しい. // ただし,\fakesmallcaps の引数には,(1) \text... の形の書体変更コマンド, // (2)アクセント記号用のコマンド,(3) \l の類の特殊文字,の // 3 種のコマンド以外のコマンドは含まれないと仮定してよい. // // もちろん,まともに対処するには仮想フォントを利用するなり, // // まともに small caps としてデザインされたフォントを一から作成するなり // // することになるが,このようなマクロが役立つこともある. **グルーピング [#i714208c] + 何らかの処理をグループ内でローカルに行う場合, ときとして処理結果をグループの外に運び出したいことがある. 例えば, \begingroup <何らかの計算> \count@=...%%% 計算結果 \endgroup のようなことをして,しかも,“\endgroup の後においてもグループ内で設定した \count@ の値を使いたい”という場合,どのようにすればよいか. なお,\count@ は LaTeX 自身が提供するマクロなどのいたるところで用いられるので, \count@ に対してグローバルな代入を行ってはいけないことに注意. // 例えば, // (1) \expandafter\endgroup\expandafter\count@\expandafter=\the\count@\relax // (2) \edef\@tempa{\count@=\the\count@\relax} \expandafter\endgroup\@tempa // (2') \xdef\g@tempa{\count@=\the\count@\relax} \endgroup \g@tempa // (2'') \xdef\g@tempa{\count@=\the\count@\relax} \aftergroup\g@tempa // (3) \global\somecnt\count@ \endgroup \count@=\somecnt // (ただし,\somecnt はあらかじめユーザ側で用意しておいた, // グローバル代入を行っても構わないような \count レジスタ) // のような方法がある. +あるユーザが(color パッケージを用いた上で)次のような記述を行ったところ, \copy0 に対応する“青い strange”以降の“samples and”(ここは赤くするつもり) あるいは“cases”(ここは水色にするつもり)までも青くなってしまった. {\color{red}% There \setbox0=\hbox{\color{blue}strange}% are \copy0\ samples and {\color{cyan}some more \copy0\ cases}.} その理由および対処法を述べよ. // “色”の処理は TeX の機能などではないことによる. // つまり,色の設定・復元の処理は \special 経由で行わざるを得ず, // その \special の設定のタイミングが TeX 自身のグルーピングの開始・終了とは // 完全には一致していないことが原因. // さらに具体的には,color パッケージ(に対する各種 def ファイルでの定義で)は // “色の復元”をグループの終了と“概ね”連動させるために, // 色の復元のための \special を \aftergroup を用いて配置する. // // あくまで既存の def ファイルを調べた限りの話だが,本質的に異なる // // 実装法は(処理系自身を色を取り扱えるように拡張しない限り)考えにくい. // そのため,上記の例では,\color{blue} に対応する復元処理は // strange の直後の閉じ括弧の直後(つまり,\box0 の *外*)に置かれてしまい, // \copy0 の際には色に関しては“色の復元を伴わない青色への変更”だけが // 行われてしまう,ということになっている. // 対処法は,とりあえず // \setbox0=\hbox{\textcolor{blue}{strange}} // のごとく \textcolor を用いるとよい(\setbox0=\hbox{{\color{blue}...}} // のように“color special のためのグルーピング”を追加してもよいが, // これは \textcolor を用いるのとほとんど同じ). // [余談] この問題の例を,\box0 の中身を \textcolor を用いて書き直した状態で // 処理させたとしても,pdflatex(の,出題者の手元にある版 // (Version 3.141592-1.40.0-beta-20060213 (Web2C 7.5.5))では // 意図通りの結果は得られない.これは,pdftex.def での色の復元処理は // 明示的に“元の色であるはずの色に変更”するという処理であることによる // (実際,\box0 の中身を代入したときの周囲の色と,2 回目の \copy0 の時点での // ボックスの周囲の色とが異なっているために不都合が生じている). ** 条件分岐 [#eb863be6] +あるユーザが \def\mymacro#1#2{% #1: 出力形式コード, #2: 文字列 \leavevmode \ifcase#1\relax \textit{#2}% \or \textbf{#2}% \or \underline{#2}% \else {#2}% \fi} のようなマクロを作成して(これは,正常に動作する), もう少しコードを削減しようと \def\mymacro#1#2{% \leavevmode \ifcase#1\relax \textit \or \textbf \or \underline \fi {#2}} のように書き換えたら,とたんにエラーが生じるようになった. その理由および対処法を述べよ. // TeX の条件処理は,条件に合致する部分に対応する記述のみに // 置換されるのでは *なく*, // 必要な部分に到達するまで不要な部分を読み飛ばし, // そののち必要な部分の処理を実行して,さらに \fi までの不要な部分を // 読み飛ばす // という処理であることによる. // 例えば,上記の \mymacro の最初の定義では,引数 #1 が 1 であるときは // “1 番目の \or までを読み飛ばしたのち \textbf{#2} を処理し,そののち // 2 番目の \or から \fi までを読み飛ばす”ため意図通りに処理される. // 一方,2 番目の定義ではやはり引数 #1 が 1 であるときには, // 1 番目の \or までを読み飛ばしたのち“\textbf を処理”しようとし, // その際に \textbf の引数として \textbf の直後にある \or が用いられ, // それはユーザの意図とは異なる処理である(実際,\textbf の内部で用いられる // \ifmmode と \fi の間に \or が入り込み,エラーを引き起こす). // 対処法は,この例のマクロに関しては // \def\mymacro#1#2{% // \leavevmode // \ifcase#1\relax \expandafter\textit // \or \expandafter\textbf // \or \expandafter\underline // \fi // {#2}} // のように \expandafter を用いて \textbf などの展開を行う前に // 余分な \or ... \fi の読み飛ばしを行わせる,という方法がある. // もっと一般的には,最初の定義のように条件分岐の個々の分岐先のみで // 完結するように記述するか, // \def\mymacro#1#2{% // \leavevmode // \let\mymacro@temp\empty // \ifcase#1\relax \let\mymacro@temp\textit // \or \let\mymacro@temp\textbf // \or \let\mymacro@temp\underline // \fi // \mymacro@temp{#2}} // のような具合に“分岐先の処理を表すマクロ”を条件に応じて定義すればよい. + あるユーザが“ただの文字とコマンドを区別する”意図で \def\mymacro#1{% #1: 1 文字あるいは 1 コマンド \if\noexpand#1\relax <コマンドである場合の処理> \else <文字である場合の処理> \fi} という定義を行った. 問: \noexpand が必要である理由を述べよ. また,文字とコマンドの判別を誤る場合について述べよ. // \noexpand を用いずに \if#1\relax ... のようにしてしまうと, // #1 の部分を展開してから比較が行われ意図通りの比較にならないので, // \noexpand が必要. // 実際,\def\macroA{aa},\def\macroB{XY} のように定義された \macroA, // \macroB を考えると,\if\macroA\relax は \if aa\relax となり, // \if による比較では 2 個の a が比較される(\macroA と \relax の // 比較ではない).同様に,\if\macroB\relax では \if XY\relax となり, // 比較されるのは X と Y である. // また,\let\sb=_ で定義された \sb のような文字トークンのコピーに関しては, // \if あるいは \ifcat での比較の際にはコピーされた文字の文字コードあるいは // カテゴリーコードを持つものとして扱われる(実際,\if,\ifcat の比較では // \sb と _ は区別されない).そのため,この問題の \mymacro では, // 文字トークンのコピーに対しては文字であるか否かの判別を誤る. + 次に引用するコードは,ファイル amsgen.sty における \@ifempty の定義である (\@firstoftwo,\@secondoftwo については,必要があればファイル latex.ltx を参照のこと.なお,\@xp は \expandafter のコピーである). \long\def\@ifempty#1{\@xifempty#1@@..\@nil} \long\def\@xifempty#1#2@#3#4#5\@nil{% \ifx#3#4\@xp\@firstoftwo\else\@xp\@secondoftwo\fi} このマクロの用法と,このマクロが誤動作する場合について述べよ. // \@ifempty{<開き括弧と閉じ括弧の対応がとれたトークン列>} // {<そのトークン列が空文字列である場合の処理>} // {<そのトークン列が空文字列ではない場合の処理>} // のように用いる. // 実際,\@ifempty の引数 #1 に @_{11} が含まれない場合について考えると, // #1 が空文字列でない場合には \@xifempty は // \@xifempty <何かある>@@..\@nil という形で用いられ, // <何かある> の部分が \@xifempty の引数 #1, #2 となり, // #3 = @, #4 = . となって,\ifx#3#4 の比較結果は偽となる. // 一方,\@ifempty の引数 #1 が空文字列である場合には, // \@xifempty @@..\@nil を展開することになり,\@xifempty の各引数は // #1 = @, #2 は空文字列, #3 = ., #4 = . で,\ifx#3#4 の比較結果は真となる. // また,誤動作する場合については,\@ifempty の引数に @_{11} が含まれる // 場合を調べればいい.実際,\@ifempty の引数 #1 が // (1) <@_{11} を含まず,かつ,空でない文字列>@_{11}XX... // (2) @_{11}@_{11}XX... // (3) <@_{11} を含まない文字列><@_{11} の 2 個以上の繰り返し> // (XX は同じ文字の繰り返し,... は任意の“開き括弧と閉じ括弧の対応が // とれたトークン列”)といった場合に \@ifempty の引数 #1 が空文字列の // 場合の処理が実行されてしまう. // [余談] もっとも,@_{11} が紛れ込む場合,というのは TeX 文書中で // “ユーザが原稿中に書き込んだ文字列”が空であるか否かを判定する際には // 問題にならない.空文字列であるか否かの判定としてよく見かける // \if/#1/ ...\else ...\fi のような処理に比べはるかに器用なので取り上げてみた. **展開の制御 [#h72dcb23] + \def\A#1,#2{#2#1} \def\B#1;#2{#1,#2} \def\C{x;y} と定義されているとき、\A\B\C に\expandafter を適宜挿入して、展開結果が yx となるようにせよ。 // \expandafter\expandafter\expandafter\A\expandafter\B\C + \def\macrocopy#1#2{\expandafter\let\csname #1\expandafter\endcsname\csname #2\endcsname} のように定義された \macrocopy はどのようなマクロか? この定義の中の 2 番目の \expandafter を忘れると,どのような不都合があるか? // \macrocopy{macroA}{macroB} は \let\macroA\macroB という処理を行う, // という具合に,\macrocopy はその第 1 引数から作成される control sequence を // 第 2 引数から作成される control sequence のコピーにする. // また,\macrocopy の定義中の 2 番目の \expandafter を忘れると, // \macrocopy{<copy>}{<original>} は // \expandafter\let\csname <copy>\endcsname\csname <original>\endcsname // となるが,これは // \let\<copy>\csname <original>\endcsname // ということで,\<copy> を \csname のコピーにしたのち,<original> と // \endcsname が取り残されてしまう(その結果,たいていの場合には余分な // \endcsname があることによるエラーが生じる). + あるユーザが,“事物の名称とその事物の分類とを組にして登録するマクロ”と “(登録済みの)事物の名称を与えると, その事物と同じ分類先に属する(やはり,登録済みの)事物を与えるマクロ”を作成する, という問題に出くわした. 例えば,“猫”・“犬”・“人間”の 3 項目のみが登録されていて “猫”と“犬”の分類は“愛玩動物”で “人間”の分類は“無用なもの”となっていたとすると, 互いに同じ分類に属する“猫”・“犬”の一方を与えたときには “猫”・“犬”の両方が得られるが, 別の分類がなされている“人間”は出てこないようなマクロを作りたい, というのである(もちろん,そのためには“登録用”のマクロも要る). このユーザは“登録用”のマクロを次のように定義するところまでは自力でできた. \def\makeentry#1#2{% #1: 事物の名称,#2: 分類名 \expandafter\gdef\csname @category@#1\endcsname{#2}% \expandafter\ifx\csname @entrylist@#2\endcsname\relax \expandafter\gdef\csname @entrylist@#2\endcsname{#1}% \else \expandafter\gdef \csname @entrylist@#2\expandafter\expandafter\expandafter\endcsname \expandafter\expandafter\expandafter{% \csname @entrylist@#2\endcsname ,#1}% \fi} // もちろん,例えば次のように定義すると少しは読みやすいが, // “故意に”上記のような定義を採用している. // \def\makeentry#1#2{% #1: 事物の名称,#2: 分類名 // \expandafter\gdef\csname @category@#1\endcsname{#2}% // \expandafter\ifx\csname @entrylist@#2\endcsname\relax // \expandafter\gdef\csname @entrylist@#2\endcsname{#1}% // \else // \expandafter\let\expandafter\@tempa\csname @entrylist@#2\endcsname // \expandafter\gdef\csname @entrylist@#2\expandafter\endcsname // \expandafter{\@tempa,#1}% // \fi} // また,実際問題としては“重複登録”を回避する必要もあるが, // “展開順序の変更”に関係しない点には必ずしもこだわらずに出題している. 問: \makeentry{<item>}{<category>}(<item>,<category> は文字列)のように \makeentry を用いたときに定義される 2 個のマクロは “項目名 <item> が与えられたときにその <item> が属する分類 (<category>)を与えるマクロ”と“分類名 <category> が与えられたときに, その <category> に属する登録済み項目のコンマ区切りリストを与えるマクロ” であることを確認し,それに基づいて“(登録済みの)事物の名称を与えると, その事物と同じ分類先に属する(登録済みの)事物を与えるマクロ”を定義せよ. // \makeentry{<item>}{<category>} は次の 2 個のマクロを定義する. // ・\@category@<item> // これは <category> に展開されるので, // “<item> が属する分類を与えるマクロ”である. // ・\@entrylist@<category> // これは,このマクロが未定義であるときには <item> に展開されるように定義され, // 定義済みのときには“このマクロの置換テキストの後に ,<item> を追加したもの” // に展開されるように定義されるので,結局 \@entrylist@<category> は // “<category> に属する項目のコンマ区切りリスト”を与えるマクロである. // また,与えた事物と同じ分類に属する項目を与えるマクロは,例えば, // 次のように定義できる. // \def\showsiblings#1{% #1: 事物の名称 // \expandafter\ifx\csname @category@#1\endcsname\relax\else // \csname @entrylist@\csname @category@#1\endcsname\endcsname // \fi} // // [余談] \csname @<array>@<index>\endcsname を <array>[<index>] と // // 書き換えてみると,上記のコードの内側の // // \csname @entrylist@\csname @category@#1\endcsname\endcsname // // のところは entrylist[category[#1]] と書き換えられる // // (こう書いてみると,どこかで見たような気がするのではないだろうか). + \edef あるいは \xdef によるマクロ定義において, 定義されるマクロの置換テキスト中に“定義時には展開したくない” マクロ等が存在する場合には,そのマクロ等に \noexpand を前置して \edef\somecs{\noexpand\othercs{...}...} という具合に展開を抑制すればよい,ということは“The TeXbook”をはじめとする TeX のプリミティブを扱った解説書には(たいてい)書いてある. しかし,今の例のように単純に \noexpand を用いたのでは, 展開の抑制は 1 回限りとなる(今の例の \somecs を,別のマクロ定義時に \edef\yetanothercs{\somecs...} のような具合に用いると, \somecs の置換テキスト中の \othercs はもはや保護されずに展開されてしまう). では,何らかのマクロを“必要があれば何度でも保護”できるようにするには, どのようにすればよいか. // LaTeX ユーザであれば,“経験的”に“保護したいコマンドに \protect を // 前置する”というやり方を知っていると思われるが,実際, // \protected@edef(およびその変種)の実行時には // \let\protect\@unexpandable@protect としてからマクロ定義を行い, // そののち \protect の定義を必要があれば復元している. // その \@unexpandable@protect の定義は // \def\@unexpandable@protect{\noexpand\protect\noexpand} // となっているため,\protect = \@unexpandable@protect の状態では // \protect\somecs を展開しても再び \protect\somecs になり, // \somecs を保護するだけでなく保護に用いる \protect 自身も保存される. // そのため,\protect で保護したコマンドは必要があれば何度でも保護される // (その代わり単純に \edef/\xdef を用いるのではなく \protected@edef 等を // 用いることになるが,単純に \edef 等を用いたとしても“\protect の定義の // 管理”をユーザ自身で行えば問題ない). **デバッグ [#me44b82b] + あるユーザが,一時的に文字列の幅を測定してその結果を用いるつもりで \def\mymacro#1{% \newdimen\tempwidth \settowidth\tempwidth{#1}% ...} のような定義を行ったところ,別のユーザから“本当にそう定義していいかい? そのマクロを 256 回以上使ってみるとどうなる?”とツッコミが入った. 上記の定義の問題点および対処法を述べよ. // 問題点: 一時使用用のレジスタを \mymacro を用いるたびに新規割り当て // しているため,いずれレジスタを使い果たしてしまう(eTeX ベースの // システムではこの問題はあまり顕在化しないとは思うが,問題であることには // 変わりがない).つまり,“メモリリーク”が存在する. // 解決法: 一時使用用のレジスタはマクロの置換テキストの外部で割り当てればいい. // e.g. // \newdimen\tempwidth // \def\mymacro#1{\settowidth\tempwidth{#1}...} // なお,“ローカルに使う変数”が要る場合には,レジスタをローカルに // 割り当てようとするのではなく(実際,\newdimen などのレジスタ // 割り当て用マクロではグローバルにしか割り当てない), // “レジスタの値のスコープ”をグルーピングで制御すればよい. ** 組版上の問題に由来する処理 [#o0e908b4] +次のように書いたときに「あ」だけの行ができるのはなぜか?&br; また,どうすれば「あれ,なんで?」と一行にできるか?&br; \par \hbox{あ} れ,なんで? // \par によって垂直モードに移行し,次の \hbox{あ} はそのまま垂直モードで // 処理されていることによる.ここでは,\leavevmode を \hbox{あ} の // 直前に置いて明示的に水平モードに移行すれば,“あ”のみの行ができることは // 回避される(もっとも,“\hbox{あ}れ,…”ではなく単に“あれ,…” // としてもこの問題の文字列に関しては解決するが,これは出題意図から // 外れているだろう). // 確かに意図とは違いますが,正当な理解に基づいての\hboxを外すという解は // 正解ですね.ですので最初に「なぜか」という問いかけがあります +あるユーザが今何ページであるかを本文中に書くために\thepageを記述した.ところが,正しくノンブルを拾えているところと,そうではないところが発生してしまった.これはなぜか(正しく拾えているところとそうではないところに何らかの傾向があるはずである). // TeX の組版処理が“段落指向”であることによる. // 例えば,ある段落が複数のページにまたがっている場合, // その段落の中のページ分割位置以降の部分に \thepage が書かれていると, // その \thepage に対応する出力は段落の開始位置を含むページ(状況によっては // それ以前のページとなることもありうる)のページ番号となってしまう. // 実際,“段落の組版処理を済ませて個々の行を作成してから” // ページ分割位置が割り出されるため,段落の組版処理(その際に,段落全体が // 文字列レベルでは確定する)の際にはページ分割に伴う \thepage の更新など // 行わないで処理を済ませてしまう. + ページごとに脚注をリセットさせるという流儀がある.しかも,脚注が一つしかない場合は脚注のマーク(例えば,\dagger)のみで,複数ある場合は,脚注のマークに番号をつける(例えば,\dagger1,\dagger2,・・・)としたい.どうすればよいか? // とりあえず,次のようなところ(本質的には『基礎解説』3.3 節の // サンプルコードと同じ). // ただし,\pagenumbering などの使用に伴い“(文字列として)同じページ番号を // もったページ”が複数回現れることがあると,それらを区別できない場合がある // (出力ルーチンにも細工を加えて,\thepage とは独立に数えた各ページの // 通し番号を用いて個々のページを区別すれば,この問題は回避できる). // \newcount\@fn@id // \gdef\@top@fn@id{1} // \gdef\@bottom@fn@id{2147483647} // \def\@init@fn@idlist{% // \global\let\@fn@idlist\@empty // \gdef\@first@fn@id{1} // \gdef\@prev@fn@id{2147483647}% // \gdef\@prevpage@with@fn{}} // \@init@fn@idlist // \def\thefootnote{\textdagger\maybefnno} // \gdef\maybefnno{\arabic{footnote}} // \def\footnote{% // \@ifnextchar[% // \@xfootnote // {\expandafter\ifx\csname c@\@mpfn\endcsname\c@footnote // \@check@fn@id // \fi // \stepcounter\@mpfn // \protected@xdef\@thefnmark{\thempfn}% // \@footnotemark\@footnotetext}} // \def\footnotemark{% // \@ifnextchar[% // \@xfootnotemark // {\@check@fn@id // \stepcounter{footnote}% // \protected@xdef\@thefnmark{\thefootnote}% // \@footnotemark}} // \def\@check@fn@id{% // \global\advance\@fn@id\@ne // \ifnum\@bottom@fn@id<\@fn@id // \setcounter{footnote}\z@ // \@get@next@fn@id // \fi // \protected@write\@auxout{}{\string\@footnote@id{\the\@fn@id}{\thepage}}} // \def\@get@next@fn@id{% // \ifx\@fn@idlist\@empty // \gdef\@top@fn@id{1}% // \gdef\@bottom@fn@id{2147483647}% // \else // \expandafter\@get@next@fn@id@\@fn@idlist\@nil // \fi // \count@\@bottom@fn@id\relax // \advance\count@-\@top@fn@id\relax // \ifnum\count@>\z@ // \gdef\maybefnno{\arabic{footnote}}% // \else // \global\let\maybefnno\@empty // \fi} // \def\@get@next@fn@id@\@elt#1#2\@nil{% // \xdef\@top@fn@id{\@firstoftwo#1}% // \xdef\@bottom@fn@id{\@secondoftwo#1}% // \gdef\@fn@idlist{#2}} // \def\@footnote@id#1#2{% // \def\@tempa{#2}% // \ifx\@prevpage@with@fn\@tempa\else // \ifx\@prevpage@with@fn\@empty\else // \@cons\@fn@idlist{{{\@first@fn@id}{\@prev@fn@id}}}% // \gdef\@first@fn@id{#1}% // \fi // \fi // \gdef\@prev@fn@id{#1}% // \gdef\@prevpage@with@fn{#2}} // \AtEndDocument{% // \if@filesw // \clearpage // \immediate\write\@auxout{\string\@save@fn@idlist}% // \fi // \@init@fn@idlist // \let\@save@fn@idlist\@save@fn@idlist@} // \def\@save@fn@idlist{% // \@cons\@fn@idlist{{{\@first@fn@id}{\@prev@fn@id}}}% // \global\let\@saved@fn@idlist\@fn@idlist // \@get@next@fn@id} // \def\@save@fn@idlist@{% // \@cons\@fn@idlist{{{\@first@fn@id}{\@prev@fn@id}}}% // \ifx\@saved@fn@idlist\@fn@idlist\else \@tempswatrue \fi} ** フォントの取り扱い [#c4be128c] +LaTeX2eではフォントの絶対的なサイズは指定できるが,相対的なサイズを指定するマクロはデフォルトではない.そこで, \fontscale{<ratio>} とすると「現在のフォントサイズの<ratio>倍」にサイズ変更し,また \addfontsize{<dimen>} とすると「現在のフォントサイズに<dimen>を加えた」サイズに変更するという マクロを作成してみよ. // \DeclareRobustCommand*\fontscale[1]{% // \dimen@\f@size\p@ // \dimen@#1\dimen@ // \dimen@ii\f@baselineskip\relax // \dimen@ii#1\dimen@ii // \edef\f@baselineskip{\the\dimen@ii}% // \fontsize\dimen@\f@baselineskip\selectfont // \ignorespaces} // \DeclareRobustCommand*\addfontsize[1]{% // \let\@tempa\f@size // \@defaultunits\dimen@#1pt\relax\@nnil // \advance\dimen@\f@size\p@ // \edef\f@size{\strip@pt\dimen@}% // \dimen@ii\f@baselineskip\relax // \dimen@ii\f@size\dimen@ii%%% \dimen@ii = <original baselineskip> * <new size> // \dimen@\@tempa\p@%%% \dimen@ = <original size> // \advance\dimen@ii .005\dimen@ // %%% 以下の除算については,『LaTeX 自由自在』を参照. // \@tempcnta\dimen@ii // \count@\@tempcnta // \@tempcntb\dimen@ // \divide\@tempcnta\@tempcntb // \edef\f@baselineskip{\the\@tempcnta}% // \multiply\@tempcnta\@tempcntb // \advance\count@-\@tempcnta // \multiply\count@ 10\relax // \@tempcnta\count@ // \divide\@tempcnta\@tempcntb // \edef\f@baselineskip{\f@baselineskip.\the\@tempcnta}% // \multiply\@tempcnta\@tempcntb // \advance\count@-\@tempcnta // \multiply\count@ 10\relax // \divide\count@\@tempcntb // \edef\f@baselineskip{\f@baselineskip\the\count@ pt}% // %%% \f@baselineskip = <original baselineskip> * <new size> / <original size> // \fontsize\f@size\f@baselineskip\selectfont // \ignorespaces} // ただし,必要があればフォント定義あるいは NFSS の内部処理に手を加えて // “サイズ代用”を抑制することになる. // // 行送りについても,文字サイズと同じ比率で拡大・縮小するように変更. +TeXでは欧文は自動的にハイフネーションされる.しかし,ハイフネーションしてもハイフンを表示させたくない(つまり和文のように「普通に」改行しているように見せたい)こともある(例えばURLの表記).どのようにすればこのようなことができるか? なお,TeXを内部で使っているASCIIのEWBを使ったと明示されている書籍で,このような処理を行っていると思われるものは存在する(例:Petzold「プログラミングWindows第五版). // // “フォントの取り扱い”っぽく解いてみると…… // “実体のない文字”をもった適当なフォントを作成し(仮想フォントでよい), // そのフォントの“実体のない文字”を \hyphenchar に指定すればよい. // 例えば,ecrm1000 の文字コード 127 の文字(オリジナルでは,ハイフンのひとつ) // のプロパティを // (CHARACTER O 177 // (CHARWD R 0.0) // (CHARHT R 0.0) // (MAP // (MOVERIGHT R 0.0) // ) // ) // くらいに変更して幅・高さが 0 で実体をもたない文字に変更したフォントを // myecrm10 という名称にしたとする(ほかの文字には ecrm1000 の文字をそのまま // 使うような仮想フォントにする). // このようなフォントを用いつつ,下記の例のように \hyphenchar を // 切り替えればよい. // \documentclass{article} // \DeclareFontShape{T1}{cmr}{m}{n}{<-> myecrm10}{} // \def\hideautohyphen{\hyphenchar\font=127\relax} // %%% ↑127 は“実体のない文字”の文字コード // \def\useautohyphen{\hyphenchar\font=45\relax} // %%% ↑ 45 は通常のハイフンの文字コード // \def\text{% // This is a~meaningless sample text. This is a~meaningless sample text. // This is a~meaningless sample text. This is a~meaningless sample text. // This is a~meaningless sample text.\par} // \begin{document} // \fontencoding{T1}\selectfont // \text // \hideautohyphen \text // \useautohyphen \text // \end{document} // // もっと丁寧にやるなら,“45”のような値は決めうちにするのではなく, // 適宜退避・復元するとよい. // なお,フォントにこだわらなければ,適当なマクロを用いて // “ハイフンなしの分割”を行う文字列の個々の文字の間に // \hskip0pt あたりを挟み込むといった方法もなくはない // (が,ハイフネーションパターンをまったく考慮せずに分割してしまう). **相互参照 [#m146461f] +LaTeX2eでは節番号などを\label/\refによって参照することができる.だが書籍によっては番号だけではなく,例えば章のタイトルも参照したいことがある.例えば \chapter{イントロダクション}\label{intro} とすることによって, \ref{intro} では通常の参照,そして \titleref{intro} とすると,タイトル(ここでは「イントロダクション」という文字列)が出力されるようにしたいというようなことである. このような機構は一般にはクラスファイルに依存するが,ここではjsbook.clsを前提として\chapterのみを対象として実装してみよ. なお,実際は\section以下の階層に組み込むほうがクラスファイルに依存しない可能性は高いが,\@startsectionの実装そのものが比較的複雑なので,本質的ではない問題を避けるためにjsbook.clsの\chapterを前提とした. // \chapter の必須引数のほうは(参照時には余計なものとなる)強制改行などが // 含まれる可能性があるので,オプション引数のほうを参照するものとして処理. // また,番号なしの \chapter については参照しないものとみなしたが, // そちらも参照するには,(*) というコメントをつけた行を \@schapter の定義に // 追加すればよい. // なお,\@chapter の定義自体についても jsbook.cls のバージョンによって // 変わってくる可能性はあるが,\@chapter に対する変更は (*) の行の // 追加のみなので,個々のバージョンに追随させる(だけでなく,さらに // \@sect/\@ssect の定義にも同様の細工を仕込む)のは容易だろう. // \def\@chapter[#1]#2{% // \ifnum \c@secnumdepth >\m@ne // \if@mainmatter // \refstepcounter{chapter}% // \typeout{\@chapapp\thechapter\@chappos}% // \addcontentsline{toc}{chapter}% // {\protect\numberline{\@chapapp\thechapter\@chappos}#1}% // \else\addcontentsline{toc}{chapter}{#1}\fi // \else // \addcontentsline{toc}{chapter}{#1}% // \fi // \chaptermark{#1}% // \addtocontents{lof}{\protect\addvspace{10\p@}}% // \addtocontents{lot}{\protect\addvspace{10\p@}}% // \begingroup%%% (*) // \let\label\@gobble%%% ← \label が見出し文字列に混入した場合への対処 (*) // %%% なお,見出し文字列は \@makechapterhead の中で // %%% 組版されるので,ここでは \label を単に無視してよく, // %%% “\label を回収して置き直す”必要はない. // \let\index\@gobble \let\glossary\@gobble%%% (*) // %%% ついでに \index,\glossary も無視 // \protected@edef\@currenttitle{#1}%%% (*) // \expandafter\endgroup\expandafter\def%%% (*) // \expandafter\@currenttitle\expandafter{\@currenttitle}%%% (*) // \if@twocolumn // \@topnewpage[\@makechapterhead{#2}]% // \else // \@makechapterhead{#2}% // \@afterheading // \fi} // \let\@currenttitle\@empty // \def\label#1{\@bsphack // \protected@write\@auxout{}% // {\string\newlabel{#1}{{{\@currentlabel}{\@currenttitle}}{\thepage}}}% // \@esphack} // \def\ref#1{% // \expandafter\@setref\csname r@#1\endcsname\@firstoffirstoftwo{#1}} // \def\titleref#1{% // \expandafter\@setref\csname r@#1\endcsname\@secondoffirstoftwo{#1}} // \long\def\@firstoffirstoftwo#1#2{\@firstoftwo#1\@empty\@empty} // \long\def\@secondoffirstoftwo#1#2{\@secondoftwo#1\@empty\@empty} ----- - 「入門」の一項目にするのではなく,別途「マクロの作成・応用」のようなページにされた方が良いように思いますが,いかがでしょうか。 この内容は「入門」のレベルを逸脱しているように思いましたので -- トニイ 2006/06/16 - [北見 けん] まあ、もう少しコンテンツが充実したら構成を見直すということでよろしいかと思います。 まずプリミティブを把握しておけば LaTeX などのマクロは自分で読めるようになるという意味では、入門としてはプリミティブの範囲に収まるものとしてもよいような気もします。 -- 北見 けん 2006/06/16 10:43 - このページの構成自体についてもいろいろご意見があるようなので、コメント欄を追加してみました。ついでに勝手ながら過去のコメントを表に出してみました。-- 北見 けん 2006/06/23 - とりあえず「タネ」を充実させてからでどうでしょう.何問か書きましたが,一日も経たずに解答がついてますね. -- 本田 &new{2006-06-24 (土) 10:53:29}; #comment