Take a one-line LaTeX document — \documentclass{article}\begin{document}Hi\end{document} — and run it through pdflatex: you get an 11,529-byte PDF. Run the identical source through latex and the DVI file that comes out is 248 bytes. A factor of forty-six. Both describe the same single page carrying the same single word “Hi”, so where does the difference go? The answer explains the whole of TeX's processing pipeline. This page follows the road from source through engine to output, shows what a .dvi really contains, and explains why the older DVI route has not gone away — all with measured numbers.
Why it is not WYSIWYG: the case for batch processing
TeX does not repaint the screen as you type because it optimises line breaking a paragraph at a time. It does not fill one line after another; it evaluates the whole paragraph, asking which combination of break points is least ugly overall, and takes the solution with the lowest total cost. A consequence is that adding a single character at the end of a paragraph can change where its very first line breaks. Recomputing every paragraph on every keystroke does not pay.
So TeX chose batch processing: read the manuscript to the end, then set the whole thing in one go. What you give up is the comfort of watching the page change as you type. What you get is globally optimal typesetting, formatting that stays consistent across a long document, and the automation that plain text allows — generating source from a script, diffing it in Git, building it unattended on a server. The working rhythm becomes write, compile, look at the PDF, repeat. Making that loop fast is what almost everything below is about.
The two routes from source to output: direct PDF and via DVI
An engine either writes PDF directly or writes an intermediate file called DVI. pdflatex, xelatex and lualatex — the pdfTeX, XeTeX and LuaTeX engines respectively — write PDF; latex, and the Japanese platex and uplatex, write DVI. Writing DVI is not the end of the job: the file is handed to a separate program called a dvi driver, which converts it to the format you actually want — dvipdfmx to PDF, dvips to PostScript, dvisvgm to SVG. A driver that paints to a screen is called a dvi viewer.
# Direct to PDF, one step
lualatex document.tex # -> document.pdf
pdflatex document.tex # -> document.pdf
# Via DVI, two steps (the Japanese route)
uplatex document.tex # -> document.dvi
dvipdfmx document.dvi # -> document.pdf
# Other dvi drivers, same input
dvips document.dvi # -> document.ps
dvisvgm document.dvi # -> document.svgThe thing people confuse here is that “does it go through DVI?” and “can it use Unicode and system fonts?” are separate axes. pdflatex skips DVI and writes PDF directly, yet its handling of input is the old kind and it cannot simply be pointed at an OpenType font installed on your machine. xelatex and lualatex, conversely, read Unicode as it comes and let fontspec name a font directly. Keep the two axes apart and the crowd of names sorts itself out.
| Command | Output | Input and fonts | Image formats |
|---|---|---|---|
latex | DVI (a dvi driver is required) | Mostly ASCII; TeX's own font formats | EPS, PS |
pdflatex | PDF (direct) | Limited Unicode; no system fonts | PNG, JPEG, PDF (EPS auto-converted) |
uplatex | DVI (hand it to dvipdfmx) | A Unicode-aware Japanese engine; Japanese font embedding is configurable | EPS, PDF, PNG, JPEG |
xelatex | PDF (internally via an .xdv) | Unicode; system OpenType fonts named through fontspec | PNG, JPEG, PDF, EPS |
lualatex | PDF (direct) | Unicode; system fonts; the internals are extensible in Lua | PNG, JPEG, PDF, EPS |
What is actually inside a .dvi? Opening the 248 bytes
DVI stands for device independent, and what it contains is nothing but a stream of instructions saying which character goes at which position. dvitype, which ships with TeX Live, unpacks it into something a human can read. Here is what was actually inside the 248-byte DVI made from the one-line document above.
$ dvitype -output-level=4 one.dvi
numerator/denominator=25400000/473628672
magnification=1000
' TeX output 2026.08.13:1233'
maxv=41484288, maxh=26673152, maxstackdepth=3, totalpages=1
Font 27: cmr10---loaded at size 655360 DVI units
42: beginning of page 1
117: down4 41484288 v:=0+41484288=41484288
140: right3 5046272 h:=0+5046272=5046272
144: fntdef1 27: cmr10
165: fntnum27 current font is cmr10
166: setchar72 h:=5046272+491521=5537793
167: setchar105 h:=5537793+182045=5719838
[Hi]
181: setchar49 h:=15204352+327681=15532033
[ 1]
185: eopThat is the entire file. setchar72 says “put character code 72 (H) here”, setchar105 says “put 105 (i)”, and setchar49 is the 1 of the page number printed at the foot. right3 and down4 move the current point, and the unit — the DVI unit — is one sp, that is 1/65536 pt, which is why cmr10 at “655360 DVI units” is exactly 10 pt. The decisive line is fntdef1 27: cmr10: the DVI only calls the font by name. Nowhere in the file is there any statement of what cmr10 looks like.
A PDF is the exact opposite. Look inside the 11,529-byte PDF that pdflatex produced and the font is embedded, under the name UNYBJV+CMR10. The six-letter prefix marks it as a subset — proof that only the glyphs actually used were extracted. The object holding that font reads /Length1 1394 /Length2 8300 /Length3 0 /Length 9259: 9,259 bytes even after compression, four-fifths of the whole 11,529-byte file. That is essentially where the factor of forty-six went. Where the DVI can write “the H of cmr10”, the PDF has to carry the outline of that H around with it. It is the price of the PDF's promise that the file looks the same wherever it goes.
One caveat, though. “The DVI is small because it holds no font” is correct; “the PDF is 11,529 bytes because it holds one” is not the whole story. Push that same 248-byte DVI through dvipdfmx and the PDF that comes out is 1,950 bytes. It embeds the font too, but converts it to the far more compact /Subtype/Type1C form, in which the font itself takes only 546 bytes. So one page carrying one word comes out at 248, 1,950 or 11,529 bytes depending purely on how it is described. When file size matters, the choice of route really does show up.
Why the DVI route is still alive: Japanese and .xdv
The strongest practical reason DVI survives is that it is the standard route for Japanese typesetting. platex and uplatex emit DVI, and dvipdfmx turns it into PDF at the end. That program began as Mark A. Wicks's dvipdfm and was extended to meet Japanese requirements; the copy in TeX Live 2024 is dated March 2024. Its job is to translate the DVI's positioning commands into PDF drawing operations and to find and embed the fonts the DVI refers to. Run a one-line Japanese document through upLaTeX and a 396-byte DVI becomes a 5,998-byte PDF, with the Japanese font HaranoAjiMincho embedded inside it as a CID font.
And the mechanism is more current than you would think. Check what dvipdfmx actually is in TeX Live 2024 and it turns out to be a symlink to xdvipdfmx — the program that reads the .xdv files XeTeX produces. In other words, XeTeX internally emits a slightly extended DVI and hands it to a driver to convert to PDF: the DVI route exactly. You can prove it with xelatex -no-pdf, which stops before the conversion and leaves the .xdv behind. For the one-line document the .xdv is 436 bytes; feeding it to xdvipdfmx gives a 2,329-byte PDF — the same size as running plain xelatex on the same source. Splitting the job in two by hand simply made visible what xelatex was already doing inside.
$ readlink $(which dvipdfmx)
xdvipdfmx
$ xelatex -no-pdf one.tex # stop before the driver stage
$ ls -l one.xdv
-rw-r--r-- 1 user staff 436 one.xdv
$ xdvipdfmx one.xdv # run the driver by hand
$ ls -l one.pdf
-rw-r--r-- 1 user staff 2329 one.pdfThe practical rule is simple. Starting fresh in English or another Latin-script language, take the direct-PDF route (pdflatex or lualatex): one fewer stage means one fewer thing to go wrong. Working in Japanese with existing material or a lab convention behind you, the DVI route (uplatex plus dvipdfmx) is still the solid choice, with a long record in vertical writing and Japanese line-breaking rules. The full argument belongs to the page on choosing an engine, but at minimum there is no need to treat DVI as something to be avoided for being old.
Helper files and how many passes: the picture including BibTeX and makeindex
Running the engine once and being done only holds for a document with no references, no contents list, no bibliography and no index. During a run the engine writes .aux (numbers and pages) and .toc (the contents), and the next run reads them back. Repeating until that settles is the basic shape, and the moment a single \ref appears it takes at least two passes. Bibliographies and indexes insert programs other than the engine into the loop: BibTeX reads the .aux and writes a .bbl (or biber reads a .bcf), and makeindex reads the .idx and writes an .ind. The engine then has to read those outputs, which gives the running order below.
# A document with cross-references, a bibliography and an index
pdflatex thesis # writes .aux, .idx; references still print as ??
bibtex thesis # reads .aux -> writes .bbl
makeindex thesis # reads .idx -> writes .ind
pdflatex thesis # pulls .bbl and .ind in; numbering shifts again
pdflatex thesis # everything settles
# Or simply
latexmk -pdf thesis # figures out the order and the count on its ownStop counting passes: latexmk, editors and SyncTeX
latexmk watches the helper files and reruns the engine exactly as many times as the document needs. When the contents of .aux and friends come out the same as last time it decides the build has settled and stops, calling BibTeX/biber or makeindex along the way if they are needed. By default it will repeat at most five times ($max_repeat = 5) before deciding it is looping. Real documents essentially never reach that ceiling. If you want the DVI route, put it in a latexmk configuration file and the same single command still does the job.
latexmk -pdf document.tex # pdfLaTeX, as many passes as needed
latexmk -lualatex document.tex # LuaLaTeX
latexmk -pv document.tex # build, then open the viewer
latexmk -c # remove .aux, .log, .toc and friends
latexmk -C # the same, and remove the PDF tooLaTeX Workshop in VS Code, TeXShop, TeXstudio and Overleaf are all usually calling latexmk behind the scenes, so pressing “compile” has already solved the counting problem for you. The other thing worth turning on is SyncTeX, which records in a .synctex.gz file which source line each spot in the PDF came from. With it enabled you can click a point in the PDF and land on the matching line of source, and jump the other way as well. The longer the document, the more it earns its keep.
When a build stops, which stage to suspect
Which stage a message came from tells you where to look. An engine error in the .log means the problem is in your source; a complaint from the dvi driver points at an image or a font; output from BibTeX or biber points at the .bib. The most confusing symptom is “my image does not appear”, and that is the route difference showing through directly: the DVI route takes EPS comfortably, the direct-PDF route takes PDF, PNG and JPEG, so trying to include a PNG in a document compiled with latex is a very common way in. Reading the log itself is covered properly on the page about chasing errors.
- When only the cross-references are wrong, do not delete helper files: let
latexmkrerun as many times as it needs. - When only images fail to appear, check whether the route in use is direct PDF or DVI and convert the file to a format that route accepts.
- When a symptom persists with no explanation, and only then, delete
.aux,.tocand.outand rebuild (latexmk -c). If stale helper files were the cause, that fixes it. - When fonts change in a Japanese PDF, suspect the embedding configuration of
dvipdfmx. That is a driver question, not an engine question.