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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
\documentclass[a4paper,10pt]{report}
\title{組版エンジンの設計と実装\\ --- 依存のない Rust で LaTeX から PDF へ ---}
\author{本郷}
\date{2026年9月8日}
\newcommand{\code}[1]{\texttt{#1}}
\newcommand{\module}[2]{\code{#1}(#2)}
\newcommand{\unit}[2]{#1\,#2}
\begin{document}
\maketitle
\begin{abstract}
LaTeX で書いた文書を PDF に組み上げるエンジンを、外の crate に依存せずに書いた。
原稿の読み取り、行分割、ページ割り付け、PDF の書き出しまでを自前で行い、
数式と化学式だけを既にある 2 つのライブラリに渡している。
本報告では、日本語を組むために要る手当てと、それをどう実装したかを述べ、
組版の速さと出来上がりの大きさを測った結果を示す。
\end{abstract}
\tableofcontents
\chapter{序論}
\label{ch:start}
\section{何を作ったか}
\code{hongo} は、LaTeX の原稿を読んで PDF を書き出すライブラリである。
TeX も LaTeX も呼ばない。原稿を字句に切るところから、
PDF のオブジェクトを並べるところまでを自分で行う。
外の crate には依存していない。フォントの読み取りも、
字形の間引きも、PDF の組み立ても、標準ライブラリだけで書いてある\footnote{%
数式は \code{suki}、化学式は \code{lina} に渡している。どちらも同じ作者のもので、
やはり依存を持たない。}。
\section{なぜ書いたか}
TeX は 40 年動いてきた道具であり、組版の質では今も基準である。
ただ、次の 3 つが手元では重かった。
\begin{enumerate}
\item 配布が大きい。TeX Live は数ギガバイトになる。
\item 途中の結果を取り出しにくい。行がどこで切れたか、
どの字がどの座標に置かれたかを、外から読むのは容易ではない。
\item 日本語を組むには別の実装が要り、そこがまた別の系統になる。
\end{enumerate}
必要だったのは、\emph{文書を PDF にする}という一点だけを、
読める大きさのコードで行うものだった。
\section{この報告の構成}
第 \ref{ch:letter} 章で書体の扱いを、第 \ref{ch:line} 章で行分割を、
第 \ref{ch:page} 章でページ割り付けを述べる。
第 \ref{ch:formula} 章で数式と化学式の組み込みを説明し、
第 \ref{ch:measure} 章で測った結果を示す。
\chapter{文字と書体}
\label{ch:letter}
\section{OpenType から取るもの}
字を置くには、字形の番号と送り幅が要る。
これらは OpenType のテーブルから読む。使うのは次の表である。
\begin{table}[htbp]
\centering
\begin{tabular}{|l|l|p{6.2cm}|}
\hline
\multicolumn{3}{|c|}{読んでいる OpenType のテーブル} \\
\hline
名前 & 何が入っているか & 何に使うか \\
\hline
\code{cmap} & 文字から字形番号への対応 & 原稿の文字を字形に直す \\
\code{hmtx} & 字形ごとの送り幅 & 行の幅を測り、PDF の \code{/W} を書く \\
\code{head} & 単位と字形を囲む箱 & \code{/FontBBox} を書く \\
\code{OS/2} & 上下の伸びと大文字の高さ & \code{/Ascent} などを書く \\
\code{glyf} & 輪郭(TrueType) & 使った字形だけを残して埋め込む \\
\code{CFF } & 輪郭(PostScript) & そのまま埋め込む \\
\hline
\end{tabular}
\caption{読んでいるテーブルとその使い道}
\label{tab:table}
\end{table}
表 \ref{tab:table} のうち \code{glyf} と \code{CFF } だけは扱いが分かれる。
理由は第 \ref{sec:embed} 節で述べる。
\section{役ごとの割り当て}
\label{sec:role}
書体は役ごとに持つ。地の文、斜体、太字、太斜体、等幅、サンセリフ、和文の 7 つである。
字を置くときは、次の順に探す。
\begin{itemize}
\item その役に割り当てた書体。
\item 和文の書体。
\item 地の文の書体。
\item 数式の書体。
\end{itemize}
どれにもなければ、その字を覚えておいて、終わりにまとめて知らせる。
組めなかった字を黙って落とさないためである。
\section{数式の書体で太字と斜体を代える}
太字や斜体の書体を用意していないときは、数式の書体の英字で代える。
Unicode の \unit{U+1D400}{から}の並びには、数式用の太字と斜体が入っている。
\code{a} を太字にしたければ \unit{U+1D41A}{を}引けばよい。
\begin{quote}
この代えは、和文には効かない。和文の明朝に太字がなければ、太字は出ない。
そこは書体を足してもらうほかない。
\end{quote}
\section{字形の間引き}
\label{sec:embed}
和文の書体は 20 メガバイトを超えることがある。
そのまま埋めると、3 ページの文書でも PDF がそれだけで重くなる。
そこで \code{glyf} を持つ書体では、使った字形だけを残して詰める。
字形の番号は変えない。番号を変えると、PDF 側の指し方も変える必要があり、
合成字形の部品の番号まで書き換えることになるからである。
\begin{verbatim}
for index in 0..count {
loca.extend_from_slice(&(glyf.len() as u32).to_be_bytes());
if want.contains(&(index as u16)) {
glyf.extend_from_slice(&data[start..end]);
}
}
\end{verbatim}
使わない字形は長さ 0 にする。\code{loca} は字形の数だけ残るので、
5 万字の書体では \unit{200}{キロバイト}ほどがそこに残る。
それでも元の 20 メガバイトよりはずっと小さい。
\chapter{行分割}
\label{ch:line}
\section{行の悪さ}
行に入れたものの自然な幅を $w$、その行に使える幅を $W$、
伸ばせる量の合計を $y$、詰められる量の合計を $z$ とする。
伸ばす割合 $r$ は次のように決まる。
\begin{equation}
\label{eq:ratio}
r =
\begin{cases}
(W - w) / y & (w < W) \\
0 & (w = W) \\
(W - w) / z & (w > W)
\end{cases}
\end{equation}
$r$ から行の悪さを測る。
\begin{equation}
\label{eq:badness}
b = 100 \, |r|^{3}
\end{equation}
3 乗にしてあるので、少し伸びたぶんには目をつぶり、伸びすぎた行だけを強く嫌う。
行 1 本ごとの悪さは、式 \ref{eq:badness} に切れ目の罰点 $p$ を足して 2 乗する。
\begin{align}
\label{eq:demerits}
d &= (10 + b)^{2} + p^{2} & (p \geq 0) \\
d &= (10 + b)^{2} - p^{2} & (p < 0)
\end{align}
段落全体では、この $d$ の合計がいちばん小さくなる切り方を選ぶ。
\begin{displaymath}
D = \min_{\text{切り方}} \sum_{i=1}^{n} d_i
\end{displaymath}
一行ずつ貪欲に決めるのではなく、段落を最後まで見てから決める。
前のほうの行を少し詰めることで後ろの行が楽になる、という選び方ができる。
\section{和文の字間}
和文には語間の空白がない。かわりに、ほとんどの字と字のあいだで切ってよい。
そこで、字と字のあいだに幅 0 の空きを置く。
この空きは伸び縮みするので、欧文の語間と同じ仕組みのまま両端がそろう。
\begin{table}[htbp]
\centering
\begin{tabular}{lrl}
\hline
名前 & 値 & 何のための値か \\
\hline
和欧間 & \unit{0.25}{em} & 和文と欧文のあいだ \\
字間の伸び & \unit{0.08}{em} & 和文の字間で伸ばせる量 \\
字間の詰め & \unit{0.04}{em} & 和文の字間で詰められる量 \\
語間 & \unit{0.333}{em} & 欧文の語と語のあいだ \\
語間の伸び & \unit{0.167}{em} & 欧文の語間で伸ばせる量 \\
\hline
\end{tabular}
\caption{空きの量。すべて字の大きさに対する割合}
\label{tab:gap}
\end{table}
表 \ref{tab:gap} の値は \code{Style} に持たせてあり、外から変えられる。
\section{禁則}
切ってはいけない場所には、切れない印を先に置く。次の 3 つを見ている。
\begin{description}
\item[行頭に置けない字] 「、」「。」「」」「)」と、小書きの仮名。
\item[行末に置けない字] 「「」「(」のような、開く側の括弧。
\item[和欧間] 括弧や句読点の隣では、空きそのものを入れない。
\end{description}
\chapter{ページ割り付け}
\label{ch:page}
\section{脚注}
脚注は、それを載せる行と同じページに置く。
行をページに積むときに、その行が脚注を連れているかを見て、
連れていれば脚注の欄のぶんだけ本文に使える高さを減らす。
減らした結果その行が入らなくなれば、行ごと次のページへ送る。
脚注だけが先のページに残ることはない\footnote{%
ただし、1 つの脚注がページより高いときは、そのページから溢れる。
そこは元の TeX でも同じである。}。
\section{浮かせるもの}
図と表は、書いた場所ではなく、収まる最初のページの先頭に置く。
\code{[h]} と書いてあって、その場に入るときだけはそこに置く。
1 ページのうち浮かせるものに使ってよい割合は 0.7 とした。
これを超えるものは次のページへ回す。1 つでページに入らないものは、
そのページを丸ごと使う。
\begin{figure}
\centering
\includegraphics[width=0.42\textwidth]{color.png}
\caption{ページの先頭へ回された図。書いた場所は第 \ref{ch:page} 章の途中である}
\label{fig:float}
\end{figure}
図 \ref{fig:float} は、この文の場所に書いてあるが、\pageref{fig:float} ページの
先頭に置かれている。
\chapter{数式と化学式}
\label{ch:formula}
\section{数式}
数式は \code{suki} に渡す。返ってくるのは、位置と寸法の決まった木である。
座標は em で、ベースラインが $y = 0$、下向きが正である。
これをそのまま PDF の座標に写す。
\begin{gather}
\int_{0}^{1} x^{2} \, dx = \frac{1}{3} \\
\lim_{x \to \infty} \left( 1 + \frac{1}{x} \right)^{x} = e \\
\sum_{k=1}^{n} k = \frac{n(n+1)}{2}
\end{gather}
行列や場合分けも同じ道で通る。
\[
A = \begin{pmatrix} a & b \\ c & d \end{pmatrix}, \quad
\det A = \begin{vmatrix} a & b \\ c & d \end{vmatrix} = ad - bc
\]
伸びる括弧は、引き伸ばすのではなく、書体が持つ大きさ違いの字形を選んで作る。
書体を替えれば組み方もそれに従う。
\section{化学式}
化学式は \code{lina} に渡す。記法は \code{chemfig} と同じである。
文中に \chemfig{H_2O} や \chemfig{O=C=O} のように置ける。
環も書ける。ベンゼンは \chemfig{*6(-=-=-=)} で、
芳香環の丸を使えば \chemfig{**6(------)} になる。
\chemname{\chemfig{H_2N-CH_2-C(=[:90]O)-OH}}{グリシン}
くさびで立体を書くこともできる。
\chemname{\chemfig{H_3C-C(>[:90]OH)(<:[:-90]H)-CH_2-CH_3}}{2-ブタノール}
結合は原子の墨の縁で止まる。止める位置は書体から測った字形の箱で決まるので、
ラベルが長くなれば結合はそのぶん短くなる。
\chapter{測った結果}
\label{ch:measure}
\section{速さと大きさ}
38 ページの文書を組んで測った。
\unit{23}{メガバイト}の和文の書体を読み込むところを含めて、
実際にかかったのは \unit{0.12}{秒}である。
\begin{table}[htbp]
\centering
\begin{tabular}{|l|r|r|r|}
\hline
文書 & ページ & 大きさ & 何が入っているか \\
\hline
\code{article.tex} & 3 & \unit{1248}{KB} & 数式と表 \\
\code{sample.tex} & 3 & \unit{1215}{KB} & 数式、化学式、表 \\
\code{figure.tex} & 2 & \unit{1187}{KB} & 画像と浮かせるもの \\
\code{feature.tex} & 3 & \unit{1175}{KB} & 章、付録、揃え \\
\hline
\multicolumn{2}{|r|}{書体だけで} & \unit{1130}{KB} & 数式と和文の 2 つぶん \\
\hline
\end{tabular}
\caption{組んだ文書とその大きさ}
\label{tab:size}
\end{table}
表 \ref{tab:size} のとおり、出来上がりのほとんどは書体である。
中身の流れは圧縮していないので、そのぶんも乗っている。
\section{何が残っているか}
欧文の分綴をしていない。語を切ってよい場所の表がないと決められず、
その表はそれだけで数万行になる\cite{liang}。
カーニングと合字も見ていない。\code{kern} と \code{GPOS} を読んでいないためである。
\chapter{結論}
日本語と欧文の混じった文書を、依存なしで PDF に組めるところまで来た。
行分割は段落全体を見て決め、禁則と和欧間の空きを見ている。
数式と化学式は既にあるものに任せ、こちらはページと PDF に集中した。
\appendix
\chapter{記法の一覧}
\label{ch:notation}
書ける命令のうち、よく使うものを挙げる。
\begin{verbatim}
\section{見出し} 節
\textbf{太字} 字の装い
\begin{itemize} 箇条書き
\begin{tabular}{|l|r|} 表
$x^2$ 文中の数式
\[ x^2 \] 独立した行の数式
\chemfig{H_2O} 化学式
\footnote{注} 脚注
\label{名前} \ref{名前} 参照
\end{verbatim}
すべての一覧は \code{readme.md} にある。
\chapter{使った書体}
数式は Latin Modern Math を使った。GUST が配っているもので、
数式用の英字と、大きさ違いの括弧を持っている。
和文は明朝体を 1 つ置く。IPAex明朝 と Noto Serif JP は、
どちらも PDF への埋め込みが許されている。
\begin{thebibliography}{9}
\bibitem{knuth} Donald E. Knuth and Michael F. Plass.
\textit{Breaking Paragraphs into Lines}. Software: Practice and Experience, 11(11), 1981.
\bibitem{liang} Franklin M. Liang.
\textit{Word Hy-phen-a-tion by Com-put-er}. Stanford University, 1983.
\bibitem{jis} 日本産業規格. \textit{JIS X 4051 日本語文書の組版方法}. 2004.
\bibitem{pdf} Adobe Systems. \textit{PDF Reference, sixth edition}. 2006.
\end{thebibliography}
\end{document}