これは「TeX & LaTeX Advent Caleandar 2014」の18日目の記事です。
(17日目は ワトソン さん です。19日目は y_minoda さん です。)
TikZ、スゴイですね! (色んな意味で)
というわけで、「完全攻略! TikZ」というネタをやろうかなと思ったのですが、冷静に考えてみると来年のクリスマスに間に合いそうにない。というわけで、方針転換して、TikZをチョットだけ(マニュアルの1ページ分くらい)解説することにします。トピックは「TikZの図の外見のサイズ」です。
外見のサイズ is 何
よくLaTeXの解説では、図は(\includegraphics
したものでもpicture
環境でも)「1つの大きな文字のように扱われる」とされています。この説明に沿うと「図の外見のサイズ」とはその“文字”の幅・高さ・深さのことを指します。ほとんどの場合、“図の内容”はその“文字の領域”の内側にありますが、外側にはみ出すことも可能で、(特に指示がない限りは)外側の部分はそのまま“領域からはみ出して”(場合によっては周りの文字に重なって)描画されることになります。
図の外見のサイズを正しく把握することは、特に図を行内で(“インライン”で)文字と混在させる場合に問題になります。いくつか例となる問題を挙げてみます。
【例題1】 次のコードを組版した場合、文字「ABC」「XYZ」とTikZの図(線分1つ)の位置関係はどうなるか?
ABC\tikz\draw (1em,-2em)--(2em,-1em);XYZ
【例題2】 下図の中の右側の“変な矢印”をTikZで描画したい。その際に左側の矢印(これはcmsyの\longrightarrow
)と“位置が揃う”ようにしたい。cmsy
(LaTeXの既定の数式記号フォント)の矢印(の横棒)の垂直位置が 0.25em、線幅が 0.04em であることが解っている。この情報を基にして“位置が揃う”ように“変な矢印”を描画するコードを書け。
「なんだ、こんなの簡単じゃん!」という人は、今日のカレンダーの記事は残念だったということで、明日の記事に期待しましょう。
ここからは残りの人のための解説です。
picture環境の外見のサイズ
TikZの話の前に、LaTeX標準のpicture
環境では「外見のサイズ」がどう決まっていたかを復習しておきましょう。
(\unitlength
の値は 1pt(LaTeX既定値)とします。)
\begin{picture}(60,48)
%...中身
\end{picture}
このように、picture
環境ではその引数に「外見のサイズ」を明示指定するという約束でした。なので、環境の中に何が書いてあっても、この図の「外見のサイズ」は「幅=60pt、高さ=48pt、深さ=0pt」(深さは常にゼロになる)となります。
ところで、picture
環境では、2つ目の引数を与えることで、座標の値をずらすことができます。
Hello
\begin{picture}(40,40)(-20,-20)
\put(-20,-20){\framebox(40,40){}}% 外見の領域全体を囲む枠
\put(0,0){\circle*{4}}% 原点の位置
\end{picture}
\TeX
注意すべきことは、この「座標をずらす設定」は外見のサイズに影響を与えない、ということで、上掲のpicture
環境の図の縦のサイズはやはり「高さ=40pt、深さ=0pt」となります。環境内でY座標が負である領域も(外見の領域内であれば)外側の世界のベースラインより上にあるわけです。当然ですが、このような場合は、環境内での原点((0,0)
)は外側のベースラインの上にはありません。
ここで述べたpicture
環境の事情は、epic+eepic パッケージおよび pict2e パッケージでpicture
を拡張した場合、あるいはPSTricksパッケージのpspicture
環境にも当てはまります。
TikZの図の外見の大きさ
さて、話をTikZに戻しましょう。
(TikZパラメタ値x=1cm
, y=1cm
(既定値)を仮定します。)
\begin{tikzpicture}[thick]
\fill (-1,2) circle [radius=0.5];% まる
\draw (-1,-1)--(-0.5,0)--(0,-1)--cycle;% さんかく
\draw (1,0.5) rectangle (2,1.5);% しかく
\end{tikzpicture}
TikZではpicture
環境と異なり「外見の領域」(TikZではこれを“バウンディングボックス(bounding box)”と呼ぶ)を明示的に指定する必要がありません。これは、TikZが(PGFが)「バウンディングボックスを自動的に算出する」機能をもっているからです。そこでは、「描画されたもの全てを含む最小の矩形領域」(を近似的に求めたもの)をバウンディングボックスと見なしています。
バウンディングボックスの様子が判るように、その領域を半透明で塗りつぶしてみます。(現在のバウンディングボックスがcurrent bounding box
という名前のノードとして取得できることを利用している。)
\huge Hello % 字を大きく
\begin{tikzpicture}[thick]
\fill (-1,2) circle [radius=0.5];% まる
\draw (-1,-1)--(-0.5,0)--(0,-1)--cycle;% さんかく
\draw (1,0.5) rectangle (2,1.5);% しかく
% 以下は"現在のbounding boxの矩形を描く"命令
\fill[green, opacity=0.2]
(current bounding box.south west)
rectangle (current bounding box.north east);
\end{tikzpicture}
\TeX
これを見ると、図のバウンディングボックスと外側のベースラインとの関係は、picture
環境の時と同じであることが解ります。すなわち、バウンディングボックスの矩形の座標は (‐1.5cm, ‐1cm)―(2cm, 2.5cm) です1(実際は線幅があるので若干外側に広い)が、これはベースラインのすぐ上に載っていると見なされるので、「外見のサイズ」は「幅=3.5cm、高さ=3.5cm、深さ=0pt」となるわけです。
ここまでの説明を理解したならば、【例題1】のコードの実際の組版結果が次のようになることは容易に予想できることでしょう。線分はY座標が負の位置にありますが、実際にはそれはベースラインの上に配置されます。
外見のサイズの調整
このままでは、【例題2】のような「周りのテキストと位置を揃えた」描画を行うことは困難です。環境内のY座標のどの値が外の世界のベースラインと一致するかが予測できないからです。
これを解決する一つの手段として、「picture
環境と同様にバウンディングボックスを明示する」という方法が考えられます。TikZでは、use as bounding box
オプション付の\path
命令(またはその省略形である\useasboundingbox
命令)でバウンディングボックスの明示指定ができます。
\huge Hello
\begin{tikzpicture}[thick]
% 下端のY座標が0になるようにバウンディングボックス明示指定.
% これでY座標の0がベースラインと一致する.
\useasboundingbox (-1.5,0) rectangle (2,2.5);
\fill (-1,2) circle [radius=0.5];% まる
\draw (-1,-1)--(-0.5,0)--(0,-1)--cycle;% さんかく
\draw (1,0.5) rectangle (2,1.5);% しかく
\fill[red] (0,0) circle [radius=3pt];% 原点
\end{tikzpicture}
\TeX
ただこの方法だと、Y座標が負の領域は「バウンディングボックスをはみ出している」(「外見の深さ」は相変わらずゼロである)ことになるので、段落内で用いるには不都合です。(下の行と重なってしまいます。)
実は、TikZには、環境内の座標と外側のベースラインの関係を調節するための機構を持っています。それは baseline パラメタです。
baselineパラメタの利用
baseline
パラメタは次のようにtikzpicture
環境(または\tikz
命令)に対して与えられます。パラメタの値には単位付きな数値(つまり長さ、1cm
等)を指定します。
\begin{tikzpicture}[baseline=値]
この指定により、環境内でのY座標が値
である垂直位置が外側のベースラインと一致するようになります2。
\huge Hello
% Y座標が0ptの垂直位置をベースラインと一致させる
\begin{tikzpicture}[thick, baseline=0pt]
\fill (-1,2) circle [radius=0.5];% まる
\draw (-1,-1)--(-0.5,0)--(0,-1)--cycle;% さんかく
\draw (1,0.5) rectangle (2,1.5);% しかく
\fill[red] (0,0) circle [radius=3pt];% 原点
\end{tikzpicture}
\TeX
活用事例: 変な矢印
baseline
パラメタを利用すると【例題2】の“変な矢印”が実現できますね。早速やってみましょう。baseline=0pt
でベースライン位置をY座標の 0pt に固定します。その上で、Y座標 0.25em の位置に矢印を描けばよいわけです。コイル状のアレはパスに対する装飾(decoration)として指定します。(よくTikZ屋さんが嬉しがってやっているアレです。)矢印の“矢じり”は\longrightarrow
と合わせたいので、形状to
を選択します。
%% (プレアンブルで)
\usetikzlibrary{decorations.pathmorphing}% coil 装飾
%% \myCoilArrow{長さ} : 変な矢印
\newcommand{\myCoilArrow}[1]{%
\tikz[baseline=0pt, x=1em, y=1em]
\draw[-to, line width=0.04em, decorate,
decoration={pre length=0.4em, post length=0.4em,
coil, segment length=0.2em, amplitude=0.2em}]
(0,0.25)--(#1,0.25);
}
%% (文書本体で)
.tex $\longrightarrow$ .dvi $\myCoilArrow{3em}$ .pdf
はいできあがり! ……えっ、“矢じり”が小さい? 確かにto
は\to
の矢印の矢じりを模した形なのですが、どうやら作者は大きさまで\to
に合わせようとは考えなかったようです。大きさまで揃える(冒頭の例題の図のようにする)には、新たに矢じり形状を(下位レイヤーで)定義する必要があるようです。例題の図は実際に形状定義を行って作りました。この記事の話ではそこは本質でないので勘弁してください……。
baseline の別の指定方法
ところで、このbaseline
パラメタですが、値として座標を指定することもできて、この場合はその点のY座標(長さ値)を指定したのと等価です。
% これは baseline=1cm と同値.
\begin{tikzpicture}[baseline={(3,1)}]
もちろん、このように具体的な数値の座標を指定したのでは余り利点がないですが、これにはもっと面白い使い方があります。
“気づかれない”TikZ
(単一行のテキストを含む)ノードにT
という名前が付いている時、座標(T.base)
のY成分はノード内のテキストのベースラインの位置を表します。これと、さっきのbaseline
指定を組み合わせると、“図の外側と内側”のベースラインを合わせることが可能になります。
Hello,
\tikz[baseline=(T.base)]
\node[draw=green, fill=green!20, text=blue]
(T) at (0,0) {Ti\emph{k}Z};
world!
この例で、“現在座標”の初期値は (0,0) なので、at (0,0)
の指定は不要です。まあこの場合、座標は何でもいいわけですが。
ここで、ノードの“存在”を示している枠線や塗り潰しなどの要素を全部除いてしまうと、まるで、「テキストをそのまま置いた」ように見えるはずです。
Hello,
\tikz[baseline=(T.base)]
% draw も fill も無しにする
\node (T) {Ti\emph{k}Z};
world!
Hello, Ti\emph{k}Z world! % 比較用
いや、まだ余計な隙間が空いていますね……。これも取り除きましょう。
Hello,
\tikz[baseline=(T.base)]
% inner sep (パディング), outer sep (マージン) をゼロに
\node[inner sep=0pt, outer sep=0pt] (T) {Ti\emph{k}Z};
world!
Hello, Ti\emph{k}Z world! % 比較用
これで見事に「TikZに見えない」ようになりましたね!
活用事例: 和文の斜体
これに対して、「別にそんなことをしても役に立たないじゃん」と思ったかもしれません。確かにこのままでは全く有用ではありません。しかし、この状態でもう一度TikZの機能を“足して”いくことができます。ということは、“TikZでテキスト装飾!” というネタに発展させることができます。例えば、直前の例で\node
にscale
オプション(あるいはxscale
/yscale
)を付けることで文字を拡大縮小させる、つまり“\scalebox
の代用”ができます。
「既存の機能の代用なんか要らない」ですか? では「和文を斜体にする」というのはどうでしょう? graphicxパッケージではテキストの回転や拡大縮小はできますが、斜体変形はサポートしていません。一応、「回転と拡大縮小を組み合わせると斜体変形ができる」というトリックは知られていますが、そこで使うパラメタの値の計算が自明ではありません。一方で、“TikZでテキスト装飾”を使うと、slant
を素直に指定するだけで済みます。
Ti\emph{k}Zで
\tikz[baseline=(T.base)]
\node[inner sep=0pt, outer sep=0pt, xslant=0.25]
(T) {とっても素敵な};% ひどい……
テキスト装飾!
活用事例: テキストにグラデーション
TikZはスゴいパッケージです。なので、“TikZで装飾”を応用すると、“スゴい装飾” ができてしまいます。例として、「文字の色をグラデーションさせる」ことにしましょう。
\documentclass[a4paper]{article}
\usepackage{tikz}
\usetikzlibrary{fadings}% フェーディング利用
%% \myFading{<先頭の色>}{<末尾の色>}{<テキスト>} :
% テキストにグラデーションをかける.
\newcommand*{\myFading}[3]{{%
% 文字の部分だけを透明にしたフェーディング
% 'mytext'を作る.
\begin{tikzfadingfrompicture}[name=mytext]
\node[rectangle, inner sep=1pt, outer sep=0pt,
fill=transparent, text=transparent!0] {#3};
\end{tikzfadingfrompicture}%
% 描画開始
\begin{tikzpicture}[baseline=(T.base)]
% テキストの全体の大きさを持つノード 'T' を
% 先に作る必要があるので, 全透明でテキストを
% 書いたノードを作る.
% (これでPDFにテキスト情報が入る.)
\node[rectangle, inner sep=0pt, outer sep=0pt,
opacity=0] (T) {#3};
% 線形グラデーションで塗り潰したTと同じ大きさの
% 長方形を描き, その際に先に作ったフェーディング
% 'mytext' を適用する.
\shade[left color=#1, right color=#2,
% フェーディングのスケールを抑止する.
% (mytextの定義では"念のため"1ptの隙間を入れた)
path fading=mytext, fit fading=false,
% この長方形を外見の領域とする.
use as bounding box]
(T.south west) rectangle (T.north east);
\end{tikzpicture}%
}}
\begin{document}
\Large\sffamily\fontseries{sbc}\selectfont
Thanks {\TeX}, \myFading{black}{white}{and Good-bye, Good-bye, Good-bye.}
\end{document}
やっぱりTikZはスゴイですね!
最後に、復習のための練習問題を出しておきます。
練習問題
下の図のように、テキストにバツ印を付けて出力するための命令\myCrossedOut
を定義しなさい。
命令の書式は、\myCrossedOut{色名}{テキスト}
とします。つまり、先の出力は以下のようなソースによって出力されたものです。
ここで早速\myCrossedOut{red}{dvipdfm}%
\textcolor{red}{dvipdfmx}を起動する。