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
# hongo
A Rust library that typesets documents written in LaTeX into PDF.
It uses neither TeX nor LaTeX. Reading the source, breaking lines, laying out pages and writing the PDF
are all done here. Math goes to [suki](https://rollpie.com/rust/suki), and chemical structures go to
[lina](https://rollpie.com/rust/lina). Apart from these two, it depends on no crates.
It can set text that mixes Japanese and Latin script. It handles the spacing between Japanese
characters, the space between Japanese and Latin text, and the characters that may not begin or end
a line.
## Usage
```
cargo build --release
./target/release/hongo doc.tex
```
The PDF is written next to the document, with the same name and `.pdf`. Change it with `-o`.
```
./target/release/hongo doc.tex -o out.pdf
```
Fonts go in `font/`. The math typeface is looked up under the name `latinmodern-math.otf`.
For Japanese, the first file in `font/` that is not the math typeface is used. To choose a font for
each role, pass `--font-roman`, `--font-japanese` and so on. `--help` lists them.
It also works as a library.
```
use std::path::Path;
let library = hongo::font::open(
Path::new("font/latinmodern-math.otf"),
None, None, None, None, None,
Some(Path::new("font/ipaexm.ttf")),
)?;
let source = std::fs::read_to_string("doc.tex")?;
let made = hongo::render(&source, &library, &hongo::Style::default(), Path::new("."))?;
std::fs::write("doc.pdf", made.data)?;
```
It also runs in a browser as wasm, as described under [Typesetting as you type](#typesetting-as-you-type).
## Trying it in a browser
A small server comes with it. Paste a source and press the button, and it becomes a PDF that shows on
the page and can be saved.
```
cargo run --release --example serve
```
Open `http://127.0.0.1:7830`. It does not accept connections from other machines.
If there is no Japanese typeface in `font/`, pass one with `--font-japanese`.
```
cargo run --release --example serve -- --font-japanese /path/to/mincho.otf
```
`test/report.tex` is loaded at the start. Edit it and press the button to typeset it again.
If the source has a mistake, the page says on which line and what could not be read.
## Typesetting as you type
`web/index.html` typesets in the browser while you type, with no server.
Write LaTeX on the left, and the pages appear on the right.
hongo runs inside the page as wasm.
The package comes with `web/hongo.js` and `web/font.js`, so `web/index.html` works as soon as it is opened by double clicking.
`web/font.js` holds only Latin Modern Math, under the GUST Font License in `web/GUST-FONT-LICENSE.txt`, so Japanese text does not appear.
To make them again, build the wasm, then write what the page needs next to it.
```
rustup target add wasm32-unknown-unknown
cargo build --lib --release --target wasm32-unknown-unknown
cargo run --example web
```
The build makes `target/wasm32-unknown-unknown/release/hongo.wasm`, about 830 KB with Rust 1.98.1.
A page opened by double clicking cannot read the files next to it, so `examples/web.rs` writes the wasm into `web/hongo.js` and the fonts into `web/font.js` as base64.
Then open `web/index.html`.
The directory can be copied anywhere, and `--output ~/Desktop/hongo` writes it there directly.
When `font/` has no Japanese typeface, pass one with `--font-japanese`.
If the `cargo` found first on the PATH is not rustup's (Homebrew's Rust, for example), it has no standard library for wasm32, and the build stops with "can't find crate for `std`".
Run rustup's cargo and rustc directly.
If rustup's `rust-lld` then cannot load `libLLVM.dylib`, add the toolchain's `lib` to the library path.
```
TOOL="$HOME/.rustup/toolchains/stable-aarch64-apple-darwin"
DYLD_FALLBACK_LIBRARY_PATH="$TOOL/lib:$HOME/lib:/usr/local/lib:/usr/lib" RUSTC="$TOOL/bin/rustc" "$TOOL/bin/cargo" build --lib --release --target wasm32-unknown-unknown
```
The right side shows the pages before they become a PDF, drawn as SVG with the glyph outlines from suki.
Typesetting runs in a worker, so typing never waits for it, and a document that takes more than five seconds is stopped.
The button writes the same pages as a PDF.
Images and `\input` cannot be used there, because the page cannot read files.
### Checking the page
The page was checked on macOS.
The package was unpacked as it is downloaded, and its `web/index.html` was opened in Chrome from `file://`, as a double click opens it.
The sample was typeset into one page in about 9 ms, with no character missing from the typeface.
Typesetting again after an edit, the message for a mistake, and making the PDF were checked in a browser, with the page served from 127.0.0.1.
To check it by hand, open `web/index.html` and see that
- the sample is typeset, and the bar at the top shows the number of pages and the milliseconds it took
- an edit is typeset again as soon as typing stops
- a mistake such as `\nosuchthing` shows its line and column, and the last pages stay
- the button saves a PDF
## Samples
They are in `test/`. `cargo run --example check` typesets all of them.
- `article.tex` - an article with math, tables, footnotes and references (3 pages)
- `report.tex` - a report with chapters, an appendix, floating figures, `p{}` columns and chemical structures (14 pages)
- `sample.tex` - one of each command (3 pages)
- `figure.tex` - images, floats and footnotes (2 pages)
- `feature.tex` - chapters, the appendix, alignment and nested lists (3 pages)
## What you need
Latin Modern Math (`latinmodern-math.otf`) is needed as the math typeface. It is the same file as in
suki's `font/`. Without it, neither math nor Latin text can be set.
To set Japanese, put a Japanese typeface in `font/`. IPAex Mincho and Noto Serif JP may be embedded
in PDF. Without one, Japanese characters do not appear, and hongo lists the missing characters at the end.
With the Latin Modern text faces (`lmroman10-regular.otf` and so on), body text is set in them.
Otherwise the Latin glyphs of the math typeface are used, and bold and italic are replaced by the math
alphanumeric characters (from U+1D400).
## Where it runs
It is written with the standard library only, so it runs wherever Rust does. It makes no OS-specific
calls. Sources with CRLF line endings are read too.
It has been checked on macOS only, not on Linux or Windows.
Only `examples/check.rs` looks for a macOS font when `font/` has no Japanese typeface. It exists for
local checks, and a font in `font/` is used first.
## Supported LaTeX
The document structure covers `\documentclass`, `\title`, `\author`, `\date`, `\maketitle`,
`\tableofcontents`, headings from `\part` to `\subparagraph`, and `\appendix`. `article` has no
chapters; `report` and `book` do. A chapter starts on a new page.
Font changes are `\textbf`, `\textit`, `\emph`, `\texttt`, `\textsf` and `\underline`, and
declarations such as `\bfseries`. Sizes run from `\small` to `\Huge`.
Environments are `itemize`, `enumerate`, `description`, `quote`, `quotation`, `center`, `flushleft`,
`flushright`, `verbatim`, `abstract`, `tabular`, `table`, `figure` and `thebibliography`.
Tables take `l`, `c`, `r` and `p{width}` columns, `|` rules, `\hline` and `\multicolumn`.
Math is `$…$`, `\(…\)`, `$$…$$`, `\[…\]`, `equation`, `align` and `gather`, with their starred forms.
suki sets the contents, including `aligned`, `split` and `gathered` inside a formula.
Chemical structures are `\chemfig` and `\chemname`, set by lina.
References are `\label`, `\ref`, `\pageref`, `\cite`, `\footnote` and `\caption`.
Images are `\includegraphics`, which reads `width`, `height` and `scale`. PNG and JPEG can be used.
You can define your own commands and environments. `\newcommand{\name}[number of arguments][default]{body}`
and `\newenvironment{name}[number of arguments]{begin}{end}` are read and expanded into the tokens.
plain TeX's `\def\name#1{body}` is read too. `\input` and `\include` are replaced by the contents of the file.
Furigana is added with `\ruby{漢字}{かんじ}`. The base text and the reading are centered on the wider
of the two. The reading is drawn in the space between lines, so the line spacing does not change.
The table of contents and the bookmarks show only the base text.
Some commands from common packages are read with a substitute. booktabs' `\toprule` and the like become
horizontal rules, and `\textcolor` drops the color and keeps the content.
`\fbox`, `\mbox`, `\rule`, `\eqref`, `\footnotemark`, `\footnotetext`, `minipage`, repeated columns
such as `*{3}{c}`, and accented letters such as `\'e` are read as well.
## How it works
Reading, layout and writing are kept apart.
`token` cuts the source into tokens, and `parse` builds a tree. The contents of math, chemical structures
and `verbatim` are kept as written, to be passed to suki and lina unchanged.
`number` numbers headings, figures, tables, equations and references, and ties each `\label` to its number.
`layout` turns the tree into a vertical list. `line` breaks body text into lines. When both edges are
justified, it chooses the breaks by looking at the whole paragraph; otherwise it fills each line as far
as it goes. Zero-width spaces sit between Japanese characters, so lines of Japanese alone are justified
the same way.
`page` lays the vertical list out on pages. A footnote is placed on the same page as the line that carries
it, and the room for body text shrinks accordingly. Floats go to the top of the first page where they fit,
and a float too large for that goes on a page by itself.
A page is broken where the penalty of the break and the space it leaves empty at the bottom cost least,
so the first or last line of a paragraph is rarely left alone on a page.
`pdf` writes the file. Every character is set in a Type0 font with Identity-H, so glyph IDs can be written
directly. Glyphs with no character assigned (such as stretched delimiters) and Japanese are handled the
same way. ToUnicode is added so that text can be selected and copied.
The table of contents and `\pageref` cannot be written until page numbers are known, so layout runs twice.
## Decisions
No outside crates. Fonts are read with suki's code, and the PDF writer is written here.
Content streams are not compressed, so that no compressor has to be written; the files are larger for it.
Japanese typefaces are cut down to the glyphs used before they are embedded, so even a 20 MB typeface
ends up as a few hundred kilobytes.
PNG and JPEG are passed to the PDF without being decoded. PNG's IDAT is zlib, and so is PDF's
FlateDecode, so the data can go in as it is once the predictor parameters are copied over.
PNGs with transparency are not read, because separating the transparency would mean decoding the image.
## Not supported
There is no hyphenation for Latin text. Without hyphenation tables, a long word that does not fit
simply runs past the end of the line.
Kerning and ligatures are not applied, because the font's `kern` and `GPOS` tables are not read.
Two-column layout, marginal notes, indexes and reading references from `.bib` files are not supported.
## License
Dual licensed under MIT and Apache-2.0. Take whichever you prefer.
- [LICENSE-MIT](./LICENSE-MIT)
- [LICENSE-APACHE](./LICENSE-APACHE)