A LaTeX document containing the single word “Hello” is 252 bytes as a DVI file. The same one page turned into a PDF by pdflatex is 11,287 bytes. Almost all of those extra eleven kilobytes are not text but the font itself: a Type 1 program cut down to the five glyphs actually used, occupying 9,002 bytes. PDF generation in LaTeX is, at bottom, a chain of decisions about what to carry inside the file. This page follows that chain — the two routes to a PDF, how to set the PDF version and compression, how the paper size really gets recorded, and how to check font embedding with pdffonts — with measured numbers throughout.
Two routes to a PDF: direct output and via DVI
Either the engine writes the PDF itself, or a separate program converts a DVI file into one. On the direct route, pdflatex (pdfTeX) and lualatex (LuaTeX) emit PDF operators as they set the page. On the DVI route, latex or the Japanese (u)platex write a DVI file first and dvipdfmx translates it. The interesting case is xelatex: it looks like one command but really belongs to the second family — XeTeX writes an extended DVI called .xdv and hands it to xdvipdfmx. Looking at the actual TeX Live 2024 installation makes this plain: dvipdfmx is a symbolic link to xdvipdfmx, so the Japanese DVI route and XeTeX’s .xdv route are served by one and the same binary. The evidence survives in the output — a PDF built with xelatex reports its Producer as xdvipdfmx (20240305).
# TeX Live 2024: one binary serves both DVI routes
$ ls -l $(which dvipdfmx)
lrwxr-xr-x 1 root wheel 9 May 4 2024 .../dvipdfmx -> xdvipdfmx
# direct
$ pdflatex paper.tex
# via DVI
$ latex paper.tex && dvipdfmx paper.dvi
# via DVI, the Japanese way
$ uplatex paper.tex && dvipdfmx paper.dviPush the same manuscript through five routes and every result is “one A4 page”, yet the insides differ. The Producer string differs, of course, but the two things worth watching are the default PDF version and the MediaBox numbers. A4’s width of 210 mm is exactly 595.2755905… points, and each converter rounds it its own way: pdfTeX and LuaTeX follow \pdfdecimaldigits=3 and write 595.276, dvipdfmx writes 595.28, and Ghostscript (via ps2pdf) rounds all the way to the integer 595. The difference will almost never matter in practice, but it is the reason two supposedly identical A4 files can refuse to compare equal.
| Command | Program that writes the PDF | Default PDF version | A4 MediaBox width |
|---|---|---|---|
pdflatex | pdfTeX itself | 1.5 | 595.276 |
lualatex | LuaTeX itself | 1.5 | 595.276 |
xelatex | xdvipdfmx, via an .xdv | 1.5 | 595.28 |
latex + dvipdfmx | dvipdfmx | 1.5 | 595.28 |
uplatex + dvipdfmx | dvipdfmx; the standard Japanese route | 1.5 | 595.28 |
latex + dvips + ps2pdf | Ghostscript | 1.4 | 595 |
“pdflatex cannot use EPS” is out of date
On TeX Live 2024, writing \usepackage{graphicx} and \includegraphics{fig.eps} and running pdflatex brings the figure in with no further configuration. It is not that pdfTeX learned to read EPS. The driver file pdftex.def loads epstopdf-base on its own, provided LaTeX is running, shell escape is enabled (TeX Live’s default restricted mode is enough), and \DoNotLoadEpstopdf has not been defined. Run it and an intermediate file named fig-eps-converted-to.pdf appears beside your source; that is what gets included. You no longer need to write \usepackage{epstopdf} yourself. Conversely, adding -no-shell-escape stops the conversion and the figure simply vanishes, with no error and no warning. That silent failure is the dangerous one.
The automatic conversion has a trap, and the comment in pdftex.def itself flags it: “This can be wrong!” If both fig.pdf and fig.eps exist and the PDF is the real source artwork, it will be overwritten by one regenerated from the EPS. The remedy is to put \newcommand{\DoNotLoadEpstopdf}{} before even the \documentclass line. The DVI route (dvipdfmx) has always handled EPS, by calling Ghostscript behind the scenes to convert it, and it leaves no intermediate file lying around. That is why a legacy workflow full of EPS still sits comfortably on the DVI route.
% keep a hand-made fig.pdf from being overwritten by fig.eps
\newcommand{\DoNotLoadEpstopdf}{}
\documentclass{article}
\usepackage{graphicx}
\begin{document}
\includegraphics{fig} % extension omitted: pdf, png, jpg are tried first
\end{document}Is the output driver really auto-detected?
For graphicx and color the answer is yes. Both need to know the output driver (pdftex, luatex, xetex, dvipdfmx, dvips, dvisvgm) in order to emit the right low-level instructions, and the configuration file graphics.cfg works out the engine by probing for \pdfoutput, \XeTeXversion and \luatexversion, then decides whether to read pdftex.def, luatex.def, xetex.def or dvips.def. So do not write a driver option by hand — a hard-coded one becomes a lie the moment you change how you compile.
But hyperref is the exception. Produce DVI with (u)platex while writing only \usepackage{hyperref} and the log reports Package hyperref Info: Driver (default): hdvips. — it emits \specials meant for dvips. Hand that DVI to dvipdfmx and you get a run of dvipdfmx:warning: Unknown token "SDict" and a PDF with neither links nor bookmarks. On this route, state it explicitly: \usepackage[dvipdfmx]{hyperref}. “Never name the driver” is advice about graphicx; it does not extend to hyperref on the DVI route — see “Bookmarks and metadata” for the full story.
Setting the PDF version: where \pdfminorversion actually works
\pdfminorversion is pdfTeX only. Write it under LuaTeX and you get ! Undefined control sequence.; XeTeX gives the same error. LuaTeX regrouped its primitives into a namespace, so the spelling there is \pdfvariable minorversion=4. XeTeX has no corresponding primitive at all — the program writing the PDF is xdvipdfmx — so you pass it to the driver: -output-driver="xdvipdfmx -V 4". On the DVI route it is simplest of all: dvipdfmx -V 4 paper.dvi. You can even trace where the default comes from: TeX Live’s pdftexconfig.tex contains \pdfminorversion = 5, and that is the whole story behind “the default is PDF 1.5”.
By 2024, though, there is much less need to keep those three spellings straight. Put the LaTeX kernel’s newer entry point \DocumentMetadata{pdfversion=2.0} before \documentclass and you get PDF 2.0 from pdflatex, lualatex and xelatex alike — measured, all three report PDF version: 2.0. Where \pdfminorversion only touches the minor number, pdfversion sets the major one too: \pdfminorversion=0 merely yields PDF 1.0 and can never reach 2.0.
| Engine or route | How to set the PDF version | Note |
|---|---|---|
\pdfminorversion=5 | pdfTeX (pdflatex) | LuaTeX/XeTeX give ! Undefined control sequence. |
\pdfvariable minorversion=5 | LuaTeX (lualatex) | the same spelling reaches compresslevel and friends |
dvipdfmx -V 4 | the DVI route and XeTeX | for XeTeX: -output-driver="xdvipdfmx -V 4" |
\DocumentMetadata{pdfversion=2.0} | all three engines | sets the major number too; goes before \documentclass |
PDF compression: \pdfcompresslevel and \pdfobjcompresslevel, measured
Pushing the compression level up to 9 buys you two bytes over 6. Measured on an eight-page \lipsum[1-40] document: \pdfcompresslevel=0 gives 79,508 bytes, =1 gives 45,730, =6 gives 43,323 and =9 gives 43,321. Only the step from 0 to 1 matters; everything beyond it is noise. The other knob, \pdfobjcompresslevel, is a different mechanism altogether: it compresses not the page content but the object definitions of the PDF itself, bundled into object streams. On the same document that takes 43,321 bytes down to 41,019 — roughly five per cent.
Here is the trap that links the two. Object streams were introduced in PDF 1.5, so dropping to \pdfminorversion=4 for an old viewer silently disables \pdfobjcompresslevel. Not quite silently, in fact — the log says pdfTeX warning (Object streams): \pdfobjcompresslevel > 0 requires PDF-1.5 or greater. Object streams disabled now. And the measurement agrees: output at \pdfminorversion=4 matches output at \pdfobjcompresslevel=0 down to the byte (43,321 either way). When a file grows slightly after you lower its version, this is almost always why. dvipdfmx has the same relationship — adding -V 4 turns an 8,310-byte PDF into a 10,055-byte one. Its compression strength is -z 0 to -z 9, and there too the gap between -z 6 and -z 9 came to six bytes.
% pdfTeX defaults, as set by TeX Live in pdftexconfig.tex
\pdfminorversion = 5
\pdfcompresslevel = 9 % 0 = off; 1 already captures most of the gain
\pdfobjcompresslevel = 2 % object streams; needs PDF 1.5 or later
% LuaTeX spells the same knobs differently
\pdfvariable minorversion = 5
\pdfvariable compresslevel = 9
\pdfvariable objcompresslevel = 2When you want to read the generated PDF with your own eyes, the quickest move is to turn compression off entirely. Rather than writing engine-specific primitives, one line does it: \DocumentMetadata{uncompress} — measured, a 52,622-byte PDF becomes a 91,347-byte one you can open in a text editor. Reach for it when you need to inspect the operators the run produced, or to trace which package injected which object.
You wrote [letterpaper] and got A4: where the paper size is really decided
The class option does not decide the PDF’s paper size. On TeX Live 2024, run \documentclass[letterpaper]{article} through pdflatex and pdfinfo reports Page size: 595.276 x 841.89 pts (A4). The reason is plain: the PDF’s MediaBox is written from \pdfpagewidth and \pdfpageheight, and letterpaper never touches those — it only sets the text block (\textwidth and friends). Meanwhile TeX Live’s pdftexconfig.tex has already set \pdfpageheight = 297 true mm and \pdfpagewidth = 210 true mm at startup. The class ends up laying out a letter-sized text block on a sheet of A4.
There are three reliable fixes. Load geometry (\usepackage[letterpaper]{geometry} takes care of \pdfpagewidth as well); write the primitive yourself (\pdfpagewidth=8.5in, which XeTeX also accepts); or, on the DVI route, say it at conversion time (dvipdfmx -p letter paper.dvi). All three measured out as 612 x 792 pts (letter). And there is a fourth, more modern answer: add \DocumentMetadata{} and the MediaBox is written from \paperwidth and \paperheight instead of \pdfpagewidth — so the class option finally wins. The same source file yields A4 or letter depending on nothing but the presence of that one line. Miss it during a migration and you get more than a shifted page number.
# TeX Live 2024, same source, four ways of asking for US letter
$ pdflatex letter.tex && pdfinfo letter.pdf | grep "Page size"
Page size: 595.276 x 841.89 pts (A4) # [letterpaper] alone: ignored
$ pdflatex geom.tex && pdfinfo geom.pdf | grep "Page size"
Page size: 612 x 792 pts (letter) # \usepackage[letterpaper]{geometry}
$ latex letter.tex && dvipdfmx -p letter letter.dvi
Page size: 612 x 792 pts (letter) # decided by the converter
$ pdflatex dm.tex && pdfinfo dm.pdf | grep "Page size"
Page size: 612 x 792 pts (letter) # \DocumentMetadata{} presentAre the fonts embedded? How to read pdffonts
If every row of the emb column in pdffonts paper.pdf says yes, the fonts are embedded. There is a second tell in the names themselves: an embedded font is called something like OREBYP+CMR10, with a six-letter uppercase prefix and a +. That marks a subset — only the glyphs the document actually used were cut out — so a bare CMR10 with no prefix is a strong hint that the font was not embedded at all. Look inside the “Hello” PDF from the opening and the embedded font descriptor says /CharSet (/H/e/l/o/one): H, e, l, o, and the digit one from the page number. That is what subsetting means.
$ pdffonts paper.pdf
name type encoding emb sub uni object ID
----------------------------- ---------- --------- --- --- --- ---------
OREBYP+CMR10 Type 1 Builtin yes yes yes 4 0
PTKKKD+CMTI10 Type 1 Builtin yes yes yes 5 0
# the same source through latex + dvipdfmx: Type 1C instead of Type 1
$ pdffonts paper-dvipdfmx.pdf
FUYUFD+CMR10 Type 1C Builtin yes yes yes 4 0
# a font that was NOT embedded: bare name, emb = no
Helvetica Type 1 Standard no no no 7 0The type column carries information too. Comparing the same one-page “Hello”, the pdflatex output says Type 1 and the dvipdfmx output says Type 1C. Both are the very same Computer Modern subset of the very same five glyphs, yet the embedded font programs measure 9,002 bytes against 720 — a factor of more than twelve. dvipdfmx re-encodes Type 1 into CFF (Compact Font Format, called Type 1C in a PDF) before embedding it, and that single fact accounts for most of the total-size gap noted at the start: 2,145 bytes via DVI against 11,287 from pdflatex. LuaTeX and XeTeX default to the OpenType cut of Latin Modern, which lands as CID Type 0C. All of these are genuinely embedded and all are fine for print and for submission.
What to check before you send the file to a printer or a journal
Two commands settle it. Run pdffonts to confirm every font is embedded, and pdfinfo to read the page dimensions and the PDF version. That is the whole check. The failures are predictable: a stray no in the emb column (easiest to acquire by including an externally produced PDF figure), a file that is letter when it should be A4, or a PDF version newer than the submission rules allow. Since pdfinfo’s Page size line does not survey every page, you can also give it a range — pdfinfo -f 1 -l 99 paper.pdf — to check that the dimensions do not change partway through.
pdffonts paper.pdf— is every row ofembayes, and does every name carry anABCDEF+prefix?pdfinfo paper.pdf— isPage sizewhat you intended, and isPDF versionwithin what the submission rules allow?- Mostly English, and quickly → the direct route (
pdflatex). The defaults need no thought. - System fonts, Unicode-heavy text →
lualatexorxelatex(direct route). - Japanese with
(u)platex→ the DVI route (dvipdfmx); set paper with-pand the PDF version with-V. - A large stock of EPS → the DVI route embeds it without leaving intermediate files; the direct route converts automatically but litters the directory with
*-eps-converted-to.pdf.