DVI / PS / PDF workflow

Compile the same one-page LaTeX document twice and the DVI comes out at 956 bytes while the PostScript that dvips makes from it comes out at 169,769 — a factor of 178. Not one byte of that growth is new information. The only difference is that the DVI names nine fonts while the PostScript carries all nine. That single fact explains most of what a DVI is, why PostScript sat in the middle of the road for two decades, and how the three routes to PDF differ today.

What is actually inside a DVI file: boxes and font names

A DVI holds positions and font references, and nothing else — not one letterform. Run it through dvitype, which ships with TeX Live, and you can read this off the page. fntdef1 27: cmr10 is only a declaration that font number 27 shall be cmr10; setchar49 is only an instruction to put character 49 of the currently selected font here. Character 49 is the digit 1. The push, down4 and right3 between them move the current point. A DVI is a list of instructions saying where to put which numbered character from which named font — and it has no idea what any of those characters look like.

terminal
dvitype -output-level=4 -page-start='*' doc.dvi

# numerator/denominator=25400000/473628672
# magnification=1000;       0.00006334 pixels per DVI unit
# Postamble starts at byte 723.
# Font 27: cmr10---loaded at size 655360 DVI units
# Font 37: cmbx12 scaled 1200---loaded at size 943718 DVI units
#  (this font is magnified 120%)
# 145: fntdef1 37: cmbx12
# 167: fntnum37 current font is cmbx12
# 168: setchar49 h:=4063232+530841=4594073, hh:=291

This is at once why a DVI is small and why a DVI cannot be viewed on its own. Run strings over those 956 bytes and all you can read is the words of the text plus a list of font names — cmr10, cmmi7, cmsy10, cmex10 and the rest. Hand someone a DVI and they cannot reproduce the page unless their machine can find fonts of those names. That division of labour was exactly the point in the 1980s: stop at an intermediate form that is device-independent, and leave the business of matching real paper or a real screen to a driver further down the line. The name DVI is the design decision.

In a DVI, or notWhat it means
setchar / puta character number; never the letterform itself
fntdef / fntnuma font referenced by name, design size and checksum only
push / pop / down / rightthe box positioning — this is the typeset result itself
xxx (\special)the escape hatch for anything DVI does not model; the driver decides
glyph outlinesabsent. This is why a DVI will not display without the fonts
color, paper sizenot in DVI proper; both travel as \special instructions

Why a DVI measures everything in 1/65536 of a point

The answer is printed at the top of the dvitype output. In numerator/denominator=25400000/473628672 the denominator 473628672 is 65536 × 7227, and 7227 points is exactly 100 inches. So one DVI unit is 1/65536 of a point, which is the same integer unit TeX uses internally: the scaled point, sp. That is why cmr10---loaded at size 655360 DVI units above is nothing but 10 × 65536, i.e. 10pt, and why cmbx12 scaled 1200 reports 943718 — that is 12pt × 1.2 = 14.4pt multiplied by 65536. TeX sets the page using these integers and no floating point at all, which is precisely why the same source gives the same result down to one sp on any machine.

The format was designed by David R. Fuchs, a student of Knuth’s, in 1979; the specification appeared as “The format of TeX’s DVI files” in TUGboat volume 3, number 2, in October 1982. More than forty years later, the first byte of doc.dvi is still 247 — the pre opcode — and the second is still 2, the DVI identification number. Compatibility survived because the format assumed nothing about the device: the output machines it was written for are long gone, and only the part that assumed nothing is still standing.

Why PostScript sat in the middle: look inside a \special

The price of being device-independent is that DVI cannot express colour, or images, or rotation. So Fuchs left exactly one escape hatch: \special{...}, whose contents are never interpreted but handed straight to the driver. And the first thing to fill that hatch was PostScript. Load \usepackage{graphicx}, write \rotatebox{30}{rotated}, build a DVI with latex and look inside: there is raw PostScript sitting in the file. The DVI does not even know that it is PostScript. It is only carrying a string.

terminal
# what latex actually wrote into the DVI for \rotatebox and \textcolor
dvitype -output-level=4 -page-start='*' spec.dvi | grep xxx

#  88: xxx 'header=l3backend-dvips.pro'
# 116: xxx 'papersize=614.295pt,794.96999pt'
# 247: xxx 'color push rgb 1 0 0'
# 347: xxx 'ps: gsave currentpoint currentpoint translate
#            30 neg rotate neg exch neg exch translate'
# 449: xxx 'ps: currentpoint grestore moveto'

From there it was a one-way street. Once a DVI’s escape hatch is full of PostScript, the only machine that can finish interpreting it is one that understands PostScript. When laser printers carrying Adobe’s PostScript became the de facto standard in the mid-1980s, the route through dvips — DVI translated into PostScript — settled into place. And in translating, dvips embeds the fonts: the generated .ps opens with the line %%DocumentFonts: CMBX12 CMR10 CMEX10 CMSY7 CMR7 CMMI10 CMMI7 CMR5 CMSY10, and the body then carries a %%BeginFont: block for each of those nine. The 956 bytes and the 169,769 bytes at the top of this page differ by exactly those nine fonts.

PostScript has left the stage, but the fingerprints are everywhere. The dvipdfmx executable — which goes from DVI straight to PDF — contains a small interpreter for running inline PostScript, complete with the diagnostic Stack not empty after execution of inline PostScript code. That is why the ps: special above is still honoured as a real rotation on this route. Its --help also lists -D template PS->PDF conversion command line template [none], the hook for handing PostScript it cannot manage to an outside program such as Ghostscript. And the .xbb file that records an image’s size reads %%BoundingBox: 0 0 8 8 — PostScript’s own comment convention, still doing the job.

The three routes today, and what each one costs

Western text: straight to PDF with pdflatex and friends. Japanese: (u)platex then dvipdfmx. A pile of PSTricks or legacy EPS: latex then dvips then ps2pdf. That is the whole of the practical decision. What is not true is that the same document yields the same PDF. Sending the document from the top of this page down all three routes produced direct.pdf at 85,509 bytes, viadvi.pdf at 14,693 and viaps.pdf at 17,851. A look with pdffonts explains it at once: pdfTeX embeds the fonts as Type 1, while dvipdfmx and Ghostscript convert them to the compact Type 1C form first. The pages look identical; the files differ sixfold.

RouteFormats it passes throughWhere it wins
pdflatex / lualatex.tex → PDFone step; the default for Western text
xelatex.tex → XDV → PDFsystem fonts; the XDV step is merely hidden
dvipdfmx.tex → DVI → PDFthe standard for Japanese ((u)pLaTeX); places PNG and JPEG directly
dvips + ps2pdf.tex → DVI → PS → PDFPSTricks, a library of EPS, submission that assumes PostScript
terminal
# 1. straight to PDF
pdflatex doc.tex

# 2. via DVI (the Japanese route)
uplatex doc.tex && dvipdfmx doc
#   doc.dvi -> doc.pdf
#   [1]
#   14692 bytes written

# 3. via PostScript
latex doc.tex && dvips doc -o doc.ps && ps2pdf doc.ps

# in practice latexmk drives all three for you

Does XeLaTeX really skip DVI?

It does not. Run xelatex -no-pdf doc.tex and you get Output written on doc.xdv (1 page, 2476 bytes). — with doc.xdv left behind. Look at its first byte and it is 247, the very same pre opcode as in doc.dvi. Only the next byte differs: 2 for DVI, 7 for XDV. What XeTeX writes is extended DVI, and what turns it into PDF is xdvipdfmx. The README shipped with TeX Live says as much — “In the installation, dvipdfmx is a symlink to xdvipdfmx.” — and indeed dvipdfmx and extractbb resolve to that same executable. Saying that XeLaTeX “produces PDF directly” means the DVI stage is hidden, not that it is gone.

terminal
xelatex -no-pdf doc.tex
# Output written on doc.xdv (1 page, 2476 bytes).

# same container, different id byte:
#   doc.dvi   byte 0 = 247 (pre)   byte 1 = 2
#   doc.xdv   byte 0 = 247 (pre)   byte 1 = 7

xdvipdfmx doc.xdv          # this is what xelatex runs for you

When you hit Cannot determine size of graphic ... (no BoundingBox)

This is the first wall anyone hits when placing a PNG or a JPEG on the DVI route. The message in full is ! LaTeX Error: Cannot determine size of graphic in sample.png (no BoundingBox)., and the cause is neither the image nor dvipdfmx but the driver option on graphicx. latex makes no PDF, so it has no ability to read image dimensions itself; the default dvips driver can only read a PostScript %%BoundingBox line, and a PNG header is not one. There are two fixes: name the driver, \usepackage[dvipdfmx]{graphicx}, or run extractbb sample.png first so that an .xbb file carrying %%BoundingBox: 0 0 8 8 is waiting. The reason pdflatex never shows this error is simply that pdfTeX itself can read a PNG header.

terminal
# ! LaTeX Error: Cannot determine size of graphic in sample.png (no BoundingBox).

# fix 1 - name the driver in the preamble:
#   \usepackage[dvipdfmx]{graphicx}

# fix 2 - write the bounding box out first:
extractbb sample.png
cat sample.xbb
#   %%Title: sample.png
#   %%Creator: extractbb 20240305
#   %%BoundingBox: 0 0 8 8
#   %%HiResBoundingBox: 0.000000 0.000000 8.000000 8.000000

The order of decision, then. Choose by language and packages, not by output format: Japanese means (u)platex and dvipdfmx, PSTricks means the PostScript route, everything else means straight to PDF. Then make the image formats match: on the DVI route, PDF or EPS, or PNG and JPEG that have been through extractbb. Then name the driver explicitly — write [dvipdfmx] on graphicx and on hyperref alike, and whoever builds the document on a different route later will not fall into the same hole. The DVI itself, meanwhile, still has no idea what colour or what picture it is carrying.