Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
11
Help us understand the problem. What is going on with this article?
@VoD

TeXでドット入り罫線ノートを再現してみた

More than 3 years have passed since last update.

この記事は TeX & LaTeX Advent Calendar 2016 の20日目の記事です。
昨日は wtsnjp さんでした。明日も wtsnjp さんです。

最近流行りのドット入り罫線ノートを,LaTeX文書内で出力するようなマクロを制作した。TeXで書き込み式の教材プリントなどを制作する際に,役立つかもしれない。(あくまでアドベントネタであり,品質は保証できませんが…。)

1.jpg

(ご覧のとおり,本家に似せる努力は大してしていません。)

成果物

出力する行数を直接指定する\noteという制御綴と,ノート罫線で\vfillのように紙面を埋める\notefillという制御綴を用意した。

\noteは,出力行数(2以上の整数)を第一引数として指定する。(\note{5}\note{20}のように用いる。)\notefillは引数をとらない。

%%%依存するパッケージの読込
\usepackage{tikz}
\usepackage{zref}
\usepackage{zref-savepos}
\usepackage{fp}

%%%ユーザーが指定できるパラメーター
\newdimen\noteLineWidth
\newdimen\dotsRadius
\newdimen\noteLineDistance

\noteLineWidth.5truept\relax%    <- 罫線の太さ
\dotsRadius.8truept\relax%       <- ドットの半径
\noteLineDistance=6truemm\relax% <- 罫線間隔(A罫 : 7truemm,B罫 : 6truemm) 


%%%必要な内部レジスタの用意
\newdimen\VDNT@currentXPos
\newdimen\VDNT@currentYPos
\newdimen\VDNT@Xinterval
\newdimen\VDNT@Yinterval
\newdimen\VDNT@notegoal


%%% \notefillで用いる座標管理用カウンタの準備
\def\VDNT@pkgname{vodnote}
\global\newcount\VDNT@uniqe


%%% \notefill の定義
\newcommand{\notefill}{\par\bgroup
  \parindent\z@
  %%罫線間隔の算出
  \@tempcnta\linewidth
  \@tempcntb\noteLineDistance
  \FPeval\VDNT@dotsNum{round(round(((\the)\@tempcnta/(\the)\@tempcntb)/2:0)*2:0)}%
  \VDNT@Xinterval\dimexpr(\linewidth)/\VDNT@dotsNum\relax
  \VDNT@Yinterval\VDNT@Xinterval
  %%上端の座標取得
  \zsaveposy{\VDNT@pkgname.\the\VDNT@uniqe.TopPos}%
  %%下端の座標取得
  \leavevmode\vfill\leavevmode
  \zsaveposy{\VDNT@pkgname.\the\VDNT@uniqe.BottomPos}%
  %%ノート罫線描画幅の決定
  \VDNT@notegoal=\dimexpr
    \zposy{\VDNT@pkgname.\the\VDNT@uniqe.TopPos}sp
    -\zposy{\VDNT@pkgname.\the\VDNT@uniqe.BottomPos}sp
  \relax
  %%ノート罫線描画
  \noindent\smash{%
    \begin{tikzpicture}
      \VDNT@currentYPos\z@
      \fill[white!70!black] (\VDNT@Xinterval*\VDNT@dotsNum/2,\VDNT@currentYPos+\inv@mag*4pt) -- ++(\inv@mag*3pt,-\inv@mag*4pt) -- ++(-\inv@mag*6pt,0) -- cycle;
      \@whiledim\VDNT@currentYPos<\VDNT@notegoal\do{
        \VDNT@currentXPos\z@
        \draw[white!70!black,line width=\noteLineWidth] (0,\VDNT@currentYPos) -- (\linewidth,\VDNT@currentYPos);
        \foreach \k in{0,1,...,\VDNT@dotsNum}{%
          \VDNT@currentXPos=\dimexpr\VDNT@Xinterval*\k\relax
          \fill[white!70!black] (\VDNT@currentXPos,\VDNT@currentYPos) circle [radius=\dotsRadius];
        }
        \advance\VDNT@currentYPos\VDNT@Yinterval\relax
      }
      \fill[white!70!black] (\VDNT@Xinterval*\VDNT@dotsNum/2,\VDNT@currentYPos-\VDNT@Yinterval-\inv@mag*4pt) -- ++(\inv@mag*3pt,\inv@mag*4pt) -- ++(-\inv@mag*6pt,0) -- cycle;
    \end{tikzpicture}%
  }%
  \egroup
  %%座標管理用カウンタのインクリメント
  \global\advance\VDNT@uniqe\@ne
  \par
}


%%% \note の定義
\newcommand{\note}[1]{\par\bgroup
  %%罫線間隔の算出
  \@tempcnta\linewidth
  \@tempcntb\noteLineDistance
  \FPeval\VDNT@dotsNum{round(round(((\the)\@tempcnta/(\the)\@tempcntb)/2:0)*2:0)}%
  \VDNT@Xinterval\dimexpr\linewidth/\VDNT@dotsNum\relax
  \VDNT@Yinterval\VDNT@Xinterval
  %%ノート罫線描画
  \noindent
    \begin{tikzpicture}
      \VDNT@currentYPos\z@
      \fill[white!70!black] (\VDNT@Xinterval*\VDNT@dotsNum/2,\VDNT@currentYPos+\VDNT@Yinterval+\inv@mag*4pt) -- ++(\inv@mag*3pt,-\inv@mag*4pt) -- ++(-\inv@mag*6pt,0) -- cycle;
      \foreach \i in{1,2,...,#1}{
        \VDNT@currentXPos\z@
        \global\VDNT@currentYPos=\dimexpr\VDNT@Yinterval*\i\relax
        \draw[white!70!black,line width=\noteLineWidth] (0,\VDNT@currentYPos) -- (\linewidth,\VDNT@currentYPos);
        \foreach \k in{0,1,...,\VDNT@dotsNum}{
          \VDNT@currentXPos=\dimexpr\VDNT@Xinterval*\k\relax
          \fill[white!70!black] (\VDNT@currentXPos,\VDNT@currentYPos) circle [radius=\dotsRadius];
        }
      }
      \fill[white!70!black] (\VDNT@Xinterval*\VDNT@dotsNum/2,\VDNT@currentYPos-\inv@mag*4pt) -- ++(\inv@mag*3pt,\inv@mag*4pt) -- ++(-\inv@mag*6pt,0) -- cycle;
    \end{tikzpicture}%
  \egroup
  \par
}

これをスタイルファイルとして保存しusepackageで読み込むか,文書のプリアンブル部に貼り付ければ動作する(プリアンブル部に貼り付ける場合は,このソースの前に\makeatletterを書き込み,末尾に\makeatotherを追加して下さい)。

「ユーザーが指定できるパラメーター」としている部分の寸法を書き換えると,罫線の太さやドットの半径など,ノートの体裁が変更できる。デフォルトでは罫線の太さを 0.5truept,ドットの半径を 0.8truept,罫線間隔を 6truemm(B罫の間隔)としてある1

本当はパッケージとして制作するつもりだったのですが,アドベントに間に合わず大した分量のソースコードではないため,ネタとして提供することにしました。(そのため,名前空間を定義していたりと,やや仰々しい作りになっています。)

サンプル

サンプルソースを,(出力 PDF)ー(それを与えるソースコード)の順でいくつか示す。

\notefill のサンプルソース

\notefillで正しくノート罫線を出力させるためには,2回のコンパイルが必要である。以下のサンプルソースは,いずれも2回コンパイル後に DVI から出力される PDF を示している。

1.jpg

sample.tex
\documentclass[dvipdfmx,10pt,a4paper,usemag,papersize]{jsarticle}
\usepackage{lipsum}

%%%ここで上記のソースを(何らかの形で)読み込む

\begin{document}

\notefill

\newpage

\lipsum[1]
\notefill
\lipsum[1]
\notefill

\end{document}

なお,任意の文字サイズを指定しても,また JS 文書クラスのusemagオプションやtombowオプションを宣言しても罫線間隔を保ったまま正しく動作する。これは zref-savepos を用いて予めノート上端とノート下端の間の距離を計測し,そのつど出力行数を計算しながらTikZによる描画にはいっているためである(詳細は後述)。

3.jpg

sample.tex
\documentclass[dvipdfmx,21pt,a4paper,usemag,papersize,tombow]{jsarticle}

%%%ここで上記のソースを(何らかの形で)読み込む

\begin{document}

hogehoge (21pt)

\notefill

\end{document}

\note のサンプルソース

5.jpg

sample.tex
\documentclass[dvipdfmx,21pt,a4paper,usemag,papersize,tombow]{jsarticle}
\usepackage{lipsum}

%%%ここで上記のソースを(何らかの形で)読み込む

\begin{document}

{\LARGE 東大生}のノートは{\LARGE 必ず美しい}とは限らない。ここから5行のドット入りノート罫線。

\note{5}

\lipsum[1]

\end{document}

なお,\noteに与えるノート行数があまりにも大きいと,ページ内に収まらなくなってしまう。

5.jpg

sample.tex
\documentclass[dvipdfmx,21pt,a4paper,usemag,papersize,tombow]{jsarticle}
\usepackage{lipsum}

%%%ここで上記のソースを(何らかの形で)読み込む

\begin{document}

{\LARGE 東大生}のノートは{\LARGE 必ず美しい}とは限らない。ここから5行のドット入りノート罫線。

\note{5}

\lipsum[1]

\note{50}% <- 長すぎて次のページではみ出してしまった。

\end{document}

この場合,\notefillを用いれば,ページ下端までちょうど収まる行数だけノートが出力される(ただしその場合2回タイプセットが必要になる)。

5.jpg

sample.tex
\documentclass[dvipdfmx,21pt,a4paper,usemag,papersize,tombow]{jsarticle}
\usepackage{lipsum}

%%%ここで上記のソースを(何らかの形で)読み込む

\begin{document}

{\LARGE 東大生}のノートは{\LARGE 必ず美しい}とは限らない。ここから5行のドット入りノート罫線。

\note{5}

\lipsum[1]

\notefill

\end{document}

\notefill\noteはいずれも罫線を描画する前に,現在位置の\linewidthから出力すべき罫線の長さ,一行のドット数を計算する。したがってenumerate環境やitemize環境など,インデントが深く取られる環境内にこれらの制御綴を配置すると,自動でノート罫線が適当な長さで出力される。

5.jpg

sample.tex
\documentclass[dvipdfmx,21pt,a4paper,usemag,papersize,tombow]{jsarticle}
\usepackage{lipsum}

%%%ここで上記のソースを(何らかの形で)読み込む

\begin{document}

\lipsum[5]

7行のドット入りノート罫線。

\note{7}

\begin{enumerate}
\item \lipsum[6]

enumrate環境内にドット入りノート罫線を配置する。

\note{6}

\item \lipsum[7]

\notefill

\end{enumerate}
\end{document}

なお,ノート罫線部分と本文との距離が,\note\notefillで微妙に異なるのはその実装の違いにある。詳細は次で述べるが,\notefillは「その罫線がページ下端いっぱいまで到達するように用いたとき,見開きで左右のノート罫線の位置が一致する」ように工夫している。すなわち,ページの下端から罫線を積み上げていくように描画を行う。これにより,ページ上部の文章量がいくらでも,左右のノート罫線の位置にズレが無いように描画できる。

9.jpg

一方,\noteは指定された行数だけ罫線を上から下へと描画する。

実装について

以下では\note\notefillの2つの制御綴の実装について説明する。

\note の実装

罫線間隔の算出

成果物のソースを抜粋し以下に示す。

%%罫線間隔の算出
\@tempcnta\linewidth
\@tempcntb\noteLineDistance
\FPeval\VDNT@dotsNum{round(round(((\the)\@tempcnta/(\the)\@tempcntb)/2:0)*2:0)}%
\VDNT@Xinterval\dimexpr\linewidth/\VDNT@dotsNum\relax
\VDNT@Yinterval\VDNT@Xinterval

一行目で,現在時点の行長をsp単位で表した値を\@tempcntaに取得し,二行目で\noteLineDistanceをsp単位で表した値を\@tempcntbに取得する。\noteLineDistanceは,ソース冒頭で用意した寸法レジスタで,ユーザーが罫線間隔を与えるために用いる。

三行目において,一行に配置するドット数(\VDNT@dotsNum+1 )2を計算する。単純に考えると,このドット数は,$\rm\left[\frac{linewidth}{noteLineDistance}\right]$という式で与えられるかに思われるが3,以下の要求と衝突する可能性が排除できない。

「ノートを縦に二分割して二段組として用いるときに便利なよう,線引きのガイドマーク(上下端の罫線の中央にある小さな三角形)をドット上に配置したい。」

つまり,一行に配置するドット数は奇数でなければならず,\VDNT@dotsNumは偶数である必要がある。従って,\FPevalを用いて以下のような計算を実行している。

\mathrm{VDNT@dotsNum}=2\cdot\left[\frac{1}{2}\cdot\frac{\mathrm{linewidth}}{\mathrm{noteLineDistance}}\right]

ここではガウス記号($[\ ]$)を四捨五入を表す記号として導入しました。

さらに四・五行目においてドット間隔(\VDNT@Xinterval)と罫線間隔(\VDNT@Yinterval)を設定している。これらの値は以下のようにして与えられる。

\mathrm{VDNT@Xinterval}=\mathrm{VDNT@Yinterval}=\frac{\mathrm{linewidth}}{\mathrm{VDNT@dotsNum}}

注意すべきは,\VDNT@Xinterval\VDNT@Yinterval\noteLineDistanceと同値ではない点である。 ドット数は(当然ながら)整数値でなければならず,また上述の理由により奇数である必要がある。従って,この条件を満たしつつ,\noteLineDistanceに最も近い値が,ドット間隔および罫線間隔として採用される仕組みになっている。

ノート罫線の描画

%%ノート罫線描画
\noindent
  \begin{tikzpicture}
    \VDNT@currentYPos\z@
    %%下端の三角形マーク描画
    \fill[white!70!black] (\VDNT@Xinterval*\VDNT@dotsNum/2,\VDNT@currentYPos+\VDNT@Yinterval+\inv@mag*4pt) -- ++(\inv@mag*3pt,-\inv@mag*4pt) -- ++(-\inv@mag*6pt,0) -- cycle;
    \foreach \i in{1,2,...,#1}{
      \VDNT@currentXPos\z@
      \global\VDNT@currentYPos=\dimexpr\VDNT@Yinterval*\i\relax
     %%罫線描画
      \draw[white!70!black,line width=\noteLineWidth] (0,\VDNT@currentYPos) -- (\linewidth,\VDNT@currentYPos);
      \foreach \k in{0,1,...,\VDNT@dotsNum}{
        \VDNT@currentXPos=\dimexpr\VDNT@Xinterval*\k\relax
        %%ドット描画
        \fill[white!70!black] (\VDNT@currentXPos,\VDNT@currentYPos) circle [radius=\dotsRadius];       }
    }
    %%上端の三角形マーク描画
    \fill[white!70!black] (\VDNT@Xinterval*\VDNT@dotsNum/2,\VDNT@currentYPos-\inv@mag*4pt) -- ++(\inv@mag*3pt,\inv@mag*4pt) -- ++(-\inv@mag*6pt,0) -- cycle;
  \end{tikzpicture}%

ノート罫線の描画部分は TikZ で実装した。上のソースコードの概略を示す。

  • \magへの対策として,ソース内に直打ちしている寸法には全て\inv@magを乗じている4。これにより,例えば JS 文書クラスで10pt以外の文字サイズを指定したとしても,罫線類が予期せぬ大きさに拡大・縮小されることを防いでいる。
  • \foreachを用いて,縦横に繰り返し操作を行い罫線・ドットを出力している。縦方向の繰り返しのループ変数は\i,横方向の繰り返しのループ変数は\kである。
  • \VDNT@currentXPos\VDNT@currentYPosには,現在描画中の位置が格納されている。

各描画命令(\draw)が何を描画しているのか,詳細をソース中にコメントで示した。従って当該部分をコメントアウトまたは消去することにより,例えばドットや,上下端の三角形を削ったノート罫線を出力する制御綴も作成できる。また,罫線とドットの色が好みでない場合は,white!70!blackなどの色指定部分を操作すれば変更できる。

\notefill の実装

\notefillの特色は,以下のようにまとめられる。

  1. \vfillのような振る舞いをする」\noteとして機能する。すなわち,\notefillを配置した位置に\vfillを置いたと仮定した場合に,確保されるスペースに収まるだけ罫線を出力する。
  2. 罫線を「下から積み上げるように」描画する。これにより,見開きページで\notefillを用いた時,左右の罫線がズレることなく描画できる(前章末尾参照)。

これを実現するために,以下のようなソースで罫線を出力するスペースを測定し,罫線を描画する。なお,実装は\noteと共通の部分も多いが,そこの説明は省略する。

ノート描画幅の決定

%%上端の座標取得
\zsaveposy{\VDNT@pkgname.\the\VDNT@uniqe.TopPos}%
%%下端の座標取得
\leavevmode\vfill\leavevmode
\zsaveposy{\VDNT@pkgname.\the\VDNT@uniqe.BottomPos}%
%%ノート罫線描画幅の決定
\VDNT@notegoal=\dimexpr
  \zposy{\VDNT@pkgname.\the\VDNT@uniqe.TopPos}sp
  -\zposy{\VDNT@pkgname.\the\VDNT@uniqe.BottomPos}sp
\relax

\zsaveposyは,PDF 上における位置を取得する制御綴であり,zref パッケージの zref-savepos モジュールにより提供される。これは\pdfsaveposという pdfTeX のプリミティブを用いて実装されており,\pdfsaveposの挙動についてはこの記事で詳しく追究した。

はじめに,\zsaveposy{\VDNT@pkgname.\the\VDNT@uniqe.TopPos}でノート上端の座標を保存する。なお,他のパッケージで zref-savepos を用いていた場合に衝突することの無いよう,名前空間として\VDNT@pkgnameを保存名の冒頭に与えている。また\notefillを複数回用いる場合に備え,\notefill使用の度にインクリメントされるカウンタ(\VDNT@uniqe)の値を保存名に挟んでいる。これにより,\notefillを文書内で複数用いても,「1つ前や後の\notefillで用いるべき座標値が誤って参照される」といったことが防げる。

次に,\vfillを実行し,ノート描画スペースの下端まで移動する。ここで\zsaveposy{\VDNT@pkgname.\the\VDNT@uniqe.BottomPos}を用いてノート下端の座標を保存し,次の演算に移る。

\notefillでは,ノート描画スペースの幅を格納する寸法レジスタとして,\VDNT@notegoalを用いる。これは単純に,以下の式で与えられる。

\rm VDNT@notegoal=(VDNT@pkgname.VDNT@uniqe.TopPos)-(VDNT@pkgname.VDNT@uniqe.BottomPos)

二点間の座標の差を計算し,相対距離を用いる事により,「pdfTeX の pdf モードと dvi モードで PDF 原点位置がずれる」問題や,「e-(u)pTeX で mag を用いると PDF 原点位置がずれる」問題(やはりこの記事で詳しく解説)を回避している。

座標管理用カウンタのインクリメント

%%座標管理用カウンタのインクリメント
\global\advance\VDNT@uniqe\@ne

上で述べたように,\notefillの定義末尾で座標管理用のカウンタレジスタ(\VDNT@uniqe)をインクリメントする。レジスタのスコープの影響を受けないよう,\global付きで\advanceを実行している。


  1. すべてtrue付きの寸法として指定しているのは,mag による拡大縮小の影響を受けないようにするため。 

  2. 「0から\VDNT@dotsNumまで」繰り返し操作を行いドットを配置するので,ドット数は\VDNT@dotsNum+1 コとなる 

  3. ドットを正方形の格子として配置することを前提としている。したがって\noteLineDistanceがドット間の距離をも表すとみなしている。 

  4. 手っ取り早くtrueptなどを用いると,コンパイルが通らなくなる。TikZ の描画命令ではtrue付きの寸法を用いて座標を指定することができない(ようである)。 

11
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
VoD
TeXとLaTeXとMETAFONTが好き。あとLispも多少。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
11
Help us understand the problem. What is going on with this article?