Of the three modern engines, XeTeX is the only one that does not write PDF. When typesetting finishes it emits a stream of bytes in a format called .xdv (extended DVI), and a separate program, xdvipdfmx, turns that into PDF. From the outside a single xelatex document.tex does everything; inside, there are two stages. Knowing that changes how you read the log the day an image or an embedded font goes wrong. This page covers what XeTeX brought to LaTeX — reading UTF-8 as-is, and naming the fonts already installed on the operating system through fontspec — plus what .xdv really is, and what actually breaks when a pdfLaTeX manuscript is moved to XeLaTeX.
Who wrote XeTeX, and what for
It began as work Jonathan Kew did at SIL International, and the first public release was April 2004, for Mac OS X only. SIL works on the scripts and orthographies of minority languages worldwide, so the requirement was there from the start: typeset Unicode, using the actual fonts made for those languages. Early XeTeX was accordingly built on the Mac typesetting technology of the day, AAT (Apple Advanced Typography). A Linux port followed in 2006, Windows soon after, and from TeX Live 2007 it shipped on every platform. On TeX Live 2024, xetex --version prints a copyright line naming SIL International, Jonathan Kew and Khaled Hosny, then lists the libraries it links: ICU 74.2, HarfBuzz 8.3.0, Graphite2 1.3.14, FreeType2 2.13.2 — and, on macOS, the Core Text and Cocoa frameworks. XeTeX is best understood as the layer that wires TeX to what the operating system and the type industry actually ship.
The version number carries a small story too. xetex --version returns XeTeX 3.141592653-2.6-0.999996 (TeX Live 2024). The leading 3.141592653 is Knuth's TeX: he extends it by one digit at each bug-fix release so that it tends toward π, and he has written that the absolutely final change, to be made after his death, will set the version to π exactly, at which point every remaining bug becomes a feature. pdfTeX and LuaTeX carry the same digits, so all three engines share that prefix. The trailing 0.999996 is XeTeX's own, and the bundled NEWS file shows it advancing one step each February, from 0.999991 in 2019 to 0.999996 in February 2024 — walking up just short of 1 by adding nines. No document states an intention to converge on 1, so treat that as an observation rather than a rule.
Naming an operating-system font: fontspec and \setmainfont
Load \usepackage{fontspec}, write \setmainfont{Helvetica Neue} using the name the operating system knows, and that is the whole procedure. No TFM to build, no line to add to a map file. Compile that with xelatex and pdffonts confirms HelveticaNeue embedded as a CID TrueType. Body, sans-serif and monospaced faces are \setmainfont, \setsansfont and \setmonofont; to raise a separate family for headings, use \newfontfamily\headingfont{...}. The fonts bundled with TeX Live, however, are a different matter, and that is where most people first get stuck. The next section deals with them.
\documentclass{article}
\usepackage{fontspec} % no inputenc, no fontenc needed
% An OS font is named the way the system knows it, e.g.
% \setmainfont{Helvetica Neue}
% A font that ships with TeX Live is safest named by file name:
\setmainfont{texgyretermes-regular.otf}[
BoldFont = texgyretermes-bold.otf,
ItalicFont = texgyretermes-italic.otf,
BoldItalicFont = texgyretermes-bolditalic.otf,
]
\setsansfont{texgyreheros-regular.otf}
\setmonofont{texgyrecursor-regular.otf}
\newfontfamily\headingfont{texgyreadventor-regular.otf}
\begin{document}
Unicode goes in literally: naïve, Straße, ¿cómo?, œuvre.
\textbf{Bold} and \textit{italic} come from the files named above.
\[ E = mc^2 \]
\end{document}The important part is what you do not write: neither inputenc nor fontenc. UTF-8 input and Unicode fonts are the baseline, so the character-encoding incantations of the pdfLaTeX era disappear entirely. When a font cannot be located the message is ! Package fontspec Error: The font "..." cannot be found. — and the first thing to suspect is not TeX but name resolution on the operating system side. The finer controls — Ligatures, Numbers=OldStyle, SmallCapsFeatures, RawFeature for passing raw OpenType tags, and Renderer for choosing the shaper (HarfBuzz / AAT / Graphite) — belong to fontspec and are covered on its own page. To carry Unicode fonts into mathematics as well, pair it with unicode-math.
The font "..." cannot be found. — when a bundled font will not answer to its name
XeTeX asks the operating system's font database for a name, not TeX Live's directories. This is where most people first get stuck. On a macOS machine with TeX Live 2024 installed as-is, \setmainfont{Helvetica Neue} works, while \setmainfont{TeX Gyre Termes}, \setmainfont{Latin Modern Roman} and \setmainfont{TeX Gyre Pagella} all fail with ! Package fontspec Error: The font "..." cannot be found. — and all three of them work under LuaLaTeX. The difference is how each engine searches. LuaTeX's luaotfload scans the TeX directory tree itself and builds an index, whereas XeTeX queries the operating system's font machinery — Core Text on macOS — so a font shipped by the distribution and never registered with the system cannot be found by name.
The fix is simple: name bundled fonts by their file name. \setmainfont{texgyretermes-regular.otf} works — and it is case-sensitive, so TeXGyreTermes-Regular.otf does not. Naming a file this way gives up the automatic pairing of bold and italic, so declare BoldFont, ItalicFont and BoldItalicFont explicitly. The sample above is written in that form and compiles under both xelatex and lualatex with zero errors and zero missing characters. You can also register the distribution's fonts with the system instead (the texlive-fontconfig setup on Linux systems, for example), but that varies by machine and will not reproduce for a collaborator, so file names are the more dependable thing to commit to a manuscript. When someone reports that XeLaTeX cannot find a font, ask them to try the file name before anything else.
Japanese fonts show the same rule from the other side. A name the operating system knows will resolve; a name only TeX Live knows will not. On this macOS machine with TeX Live 2024, \setmainfont{Hiragino Mincho ProN}, \setmainfont{Hiragino Sans} and \setmainfont{YuMincho} all compile under XeLaTeX, because those are the names the system font panel shows. \setmainfont{Noto Sans JP}, by contrast, fails with cannot be found unless it has been installed at the OS level. So the test is easy: look at whether the font appears in your system font list. If it does, XeTeX will find it; if it does not, no amount of it being present in TeX Live will make the name work. For Japanese manuscripts needing vertical writing or strict line-breaking rules, luatexja or upLaTeX remains the smoother road than xeCJK.
What .xdv is, and why XeTeX does not write the PDF itself
.xdv is DVI with extensions. Run xelatex -no-pdf and you can hold one in your hand: its first bytes are f7 07 — the DVI pre command followed by a format id of 7 rather than the 2 of standard DVI. Look inside and, after a comment reading XeTeX output 2026.08.13:0452, the fonts it uses are written out as file paths (lmroman10-regular.otf and the like). So an .xdv already knows which glyph of which font file goes at which coordinate — but the font itself is not embedded yet. The remaining job is to open those font files, subset them and pack them into a PDF, and that is exactly xdvipdfmx's job.
A normal xelatex run leaves no .xdv on disk, because XeTeX pipes those bytes into the driver's standard input. The flag -output-driver=CMD lets you substitute a driver, so pointing it at a script that merely runs cat catches the stream as it passes — 820 bytes in the test above, ending with the traditional DVI trailer df df df df. Two practical consequences follow. First, when a package such as graphicx asks which driver you are using, the answer is xetex, meaning xdvipdfmx. Second, you have to work out which of the two stages an error came from. You typed xelatex, but a failure in image handling or font embedding surfaces as an xdvipdfmx line. Reading the log as two halves — the first TeX error, and the last driver line — gets you there fastest.
# Stop after the first stage and keep the intermediate XDV file.
$ xelatex -no-pdf document.tex
Output written on document.xdv (1 page, 820 bytes).
# The second byte is the DVI format id: 7 for XDV, 2 for plain DVI.
$ xxd document.xdv | head -2
00000000: f707 0183 92c0 1c3b 0000 0000 03e8 1d20 .......;.......
00000010: 5865 5465 5820 6f75 7470 7574 2032 3032 XeTeX output 202
# Run the second stage by hand. xdvipdfmx has been the default
# driver on every platform since XeTeX 0.997.
$ xdvipdfmx document.xdv
document.xdv -> document.pdf
# The driver is a replaceable external command.
$ xelatex -output-driver="/path/to/save-stdin.sh" document.texWhat breaks when you move a document from pdfLaTeX to XeLaTeX
The dangerous case is the one that does not raise an error. Feed an old preamble straight to xelatex and it will usually compile. \usepackage[utf8]{inputenc} is skipped with a single warning, Package inputenc Warning: inputenc package ignored with utf8 based engines., and on its own that is harmless. The trouble is the pairing of \usepackage[T1]{fontenc} with \usepackage{lmodern}. Leave those two lines in and XeLaTeX stays on the 8-bit NFSS path: what gets embedded is a TFM font called ec-lmr10, reported by pdffonts as Type 1C. That is a different object from the CID Type 0C you get through fontspec — which means almost nothing was gained by switching engines.
One log line tells the two apart. With [T1]{fontenc} still loaded, typing 日 produces Missing character: There is no 日 ("65E5) in font ec-lmr10! — the code point in TeX's hexadecimal form "65E5, and an 8-bit font name. In the same document switched to fontspec it reads Missing character: There is no 日 (U+65E5) in font [lmroman10-regular]... — the notation becomes U+65E5 and the font name becomes an OpenType file. If you are seeing the first form, you have fallen back onto the 8-bit path. The same thing happens with combining diacritics (ec-lmr10 has no U+0301; the OpenType route sets it without complaint). The right migration is to delete the whole family of legacy font declarations at once — inputenc, fontenc, lmodern, times and friends — and let fontspec be the single source of font selection.
| Line in the old preamble | What XeLaTeX does with it | What to do |
|---|---|---|
\usepackage[utf8]{inputenc} | ignored, with one warning line | delete it |
\usepackage[T1]{fontenc} | silently keeps you on the 8-bit NFSS path | replace with fontspec |
\usepackage{lmodern} | embeds ec-lmr10 (Type 1C) | use \setmainfont{lmroman10-regular.otf} |
microtype | protrusion only; expansion is unavailable | keep loading it as-is |
babel | works, but thin for RTL and complex scripts | consider polyglossia |
Why font expansion does not work under XeLaTeX
Because the engine does not have the feature. Of microtype's two pillars, XeTeX implements only protrusion. Probe the primitives one at a time and XeTeX turns out to have \lpcode and \rpcode, which set the per-character overhang, but not \efcode, which sets how far a character may stretch. Even the protrusion switch is not pdfTeX's \pdfprotrudechars but a separately named \XeTeXprotrudechars. Load \usepackage{microtype} under XeLaTeX and the log shows Character protrusion enabled (level 2). while the Automatic font expansion enabled line that would sit beside it under pdfLaTeX or LuaLaTeX quietly disappears. Ask for it explicitly and you get a clean stop: ! Package microtype Error: Font expansion does not work with xetex.
Complex scripts and right-to-left: HarfBuzz and the \XeTeX... primitives
The main reason XeTeX became so widely used is that it handles scripts whose letters change shape according to context — Arabic, the Indic family — properly. Shaping is done by HarfBuzz: version 0.9999 (May 2013) switched from the older ICU LayoutEngine to HarfBuzz, and the build in TeX Live 2024 links HarfBuzz 8.3.0. The engine also carries a set of its own primitives: \XeTeXinputencoding to change input encoding mid-document, \XeTeXcharclass and \XeTeXinterchartoks to give characters classes and act on what sits next to them, \XeTeXlinebreaklocale to pick line-breaking rules by language, \XeTeXgenerateactualtext to write real text into the PDF for extraction, and \XeTeXpicfile and \XeTeXpdffile for images. Language switching is handled by polyglossia, right-to-left typesetting by bidi (which polyglossia loads for you when you ask for Arabic and the like), and Chinese, Korean and Japanese by xeCJK — though for Japanese vertical writing and demanding line-break rules, luatexja or upLaTeX is the smoother road.
When to reach for XeLaTeX, and when not to
- When you want to use the fonts you already have. Put the OpenType or TrueType name in
\setmainfontand you are finished; no configuration file to touch. - When you want to write Unicode literally. UTF-8 input is the default and the entire encoding preamble disappears.
- For multilingual and right-to-left documents.
polyglossiawithbidihas a long record in Arabic and Hebrew, and HarfBuzz does the shaping. - Reason not to, one — you need font expansion. XeTeX has no
\efcode, somicrotype's expansion cannot work at all. If typographic quality is the priority, use pdfLaTeX or LuaLaTeX. - Reason not to, two — you want to program the typesetter. XeTeX embeds no scripting language. If you need low-level intervention, move to LuaLaTeX.
- Reason not to, three — Japanese vertical writing. It exceeds what
xeCJKis meant for; tryingluatexjaor upLaTeX early will save time.
The one-line rule of thumb: if you only want to use operating-system fonts by name, XeLaTeX; if you want to reach into the typesetting itself, LuaLaTeX. A full three-way comparison lives on the “Choosing an engine” page. One last note about collaboration: font names differ from system to system, and \setmainfont{Helvetica Neue} will simply fail on a colleague's Linux machine. For a shared manuscript, first get a build that works with fonts that ship with TeX Live, named by file as above, then substitute only the fonts you actually need. If you are submitting a PDF alone, run pdffonts and confirm that every font is embedded before you send it.