Is a LaTeX resume ATS‑friendly?

What a text extractor reads from 15 test PDFs

latexci.com · experiments run on 2026-10-18

Abstract

An applicant tracking system can only parse the text it gets out of your PDF. We did not test any commercial ATS. We compiled 15 small resume documents with pdfLaTeX, each pair differing in one detail, and read them with two open-source extractors. A LaTeX resume extracts cleanly when it uses the T1 font encoding, a single column, plain-text labels and dates written on the title line. Accents in the default encoding, icon fonts and dates set in their own column produced broken or misplaced text in at least one extractor.

1Method

Every document was compiled by the YToTech service, the one latexci uses for PDF export, then read by the tools in Table 1. pdftotext ran in two modes: its default reading order and -layout, which keeps the physical position of the text. Different parsers make different choices, so a resume is only safe when all three outputs make sense.

StepTool and version
CompilepdfTeX 1.40.29, TeX Live 2026, LaTeX 2025-11-01
Extractor 1poppler pdftotext 26.07.0, default and -layout modes
Extractor 2pypdf 6.14.2, extract_text()

Table 1. The test bench. The scripts are in the latexci repository under scripts/seo-cv/.

Remark. These are two extractors among many. The point is not to predict one product but to see which LaTeX constructs give unstable text, the raw material every parser starts from.

2Accents, ligatures and glyphtounicode

The usual advice is to add \input{glyphtounicode}\pdfgentounicode=1 so that ligatures such as fi extract as two letters. We crossed that line with the font encoding, on the text of Figure 1.

Rendered test line with accented names, fi and ffi ligatures, an en dash, a bullet and an arrow
Source (body)
Zoë Müller, École Polytechnique, Zürich\par
Office efficiency: financial workflows, affine fitting\par
2019--2021 \textbullet\ Python $\to$ SQL \& C++\par
Figure 1. The accent and ligature test, rendered. The four variants of Table 2 look identical on paper.
Encodingglyphto­unicode lineToUnicodeü in pdftotextü in pypdffi, ffi
OT1no3 of 3 fontsu + U+0308U+00A8 + spaceletters
OT1yes3 of 3 fontsu + U+0308U+00A8 + spaceletters
T1no3 of 3 fontsU+00FCU+00FCletters
T1yes3 of 3 fontsU+00FCU+00FCletters

Table 2. Font encoding (OT1 is LaTeX’s default; T1 here with lmodern) against the glyphtounicode line. ToUnicode: fonts carrying a Unicode map. U+0308 is a combining diaeresis, U+00A8 a spacing one, U+00FC the letter ü. “Letters”: both extractors returned efficiency, financial and affine as plain words.

Two results stand out. First, the encoding matters and the glyphtounicode line did not. With the default OT1 encoding, LaTeX builds ü from a u and a separate accent, and that is what comes out: pypdf returned the first line as

Zo¨ e M¨ uller,´Ecole Polytechnique, Z¨ urich

and pdftotext returned a u followed by a combining diaeresis, which a search for the precomposed ü does not match unless the software normalises Unicode. With \usepackage[T1]{fontenc} each accented letter is one glyph and both tools returned it as one character. Second, the ligatures were fine everywhere: on this installation the format already sets \pdfgentounicode to 1 (we printed its value), and every font of every test PDF carried a ToUnicode map with or without the line. Keep the line anyway: it costs nothing and makes the setting explicit for anyone who compiles your source on an installation that does not set it.

3Two columns against one

Figure 2 is the classic resume sidebar: skills and languages on the left, experience on the right. We built it twice, with two minipage boxes and with the paracol package. Both PDFs gave exactly the same text in all three readings, so only one is shown.

Two-column resume excerpt: skills and languages in a left sidebar, experience on the right
Source, minipage
\begin{minipage}[t]{0.3\linewidth}
  \textbf{Skills}\par
  Python, SQL\par
  Spark, Airflow\par
  \medskip
  \textbf{Languages}\par
  English (native)\par
  French (C1)
\end{minipage}\hfill
\begin{minipage}[t]{0.66\linewidth}
  \textbf{Experience}\par
  \textbf{Data Engineer}, Acme Analytics\par
  Built the nightly billing pipeline\par
  Cut report latency from 6 h to 40 min\par
  \medskip
  \textbf{Data Analyst}, Northwind Bank\par
  Automated the monthly risk dashboard
\end{minipage}
Source, paracol
\columnratio{0.3}
\begin{paracol}{2}
  \textbf{Skills}\par
  Python, SQL\par
  Spark, Airflow\par
  \medskip
  \textbf{Languages}\par
  English (native)\par
  French (C1)
\switchcolumn
  \textbf{Experience}\par
  \textbf{Data Engineer}, Acme Analytics\par
  Built the nightly billing pipeline\par
  Cut report latency from 6 h to 40 min\par
  \medskip
  \textbf{Data Analyst}, Northwind Bank\par
  Automated the monthly risk dashboard
\end{paracol}
pdftotext
Skills
Python, SQL
Spark, Airflow
Languages
English (native)
French (C1)

Experience
Data Engineer, Acme Analytics
Built the nightly billing pipeline
Cut report latency from 6 h to 40 min
Data Analyst, Northwind Bank
Automated the monthly risk dashboard
pypdf
Skills
Python, SQL
Spark, Airflow
Languages
English (native)
French (C1)
Experience
Data Engineer, Acme Analytics
Built the nightly billing pipeline
Cut report latency from 6 h to 40 min
Data Analyst, Northwind Bank
Automated the monthly risk dashboard
pdftotext -layout
Skills             Experience
Python, SQL        Data Engineer, Acme Analytics
Spark, Airflow     Built the nightly billing pipeline
                   Cut report latency from 6 h to 40 min
Languages
English (native)   Data Analyst, Northwind Bank
French (C1)        Automated the monthly risk dashboard
Figure 2. A two-column resume and what three readings return. In layout mode the columns are merged line by line.

The reading-order extractors kept each column as a block, so the sidebar came first and the experience second. Layout mode did what its name says: the line “Skills” and the line “Experience” became one line, and “Python, SQL” now sits in front of a job title. A parser that reads line by line sees a skill list fused with your work history. The single flow in Figure 3 holds the same words and gave identical text in both pdftotext modes.

Source, one column
\textbf{Experience}\par
\textbf{Data Engineer}, Acme Analytics\par
Built the nightly billing pipeline\par
Cut report latency from 6 h to 40 min\par
\textbf{Data Analyst}, Northwind Bank\par
Automated the monthly risk dashboard\par
\medskip
\textbf{Skills}\quad Python, SQL, Spark, Airflow\par
\textbf{Languages}\quad English (native), French (C1)
pdftotext, both modes
Experience
Data Engineer, Acme Analytics
Built the nightly billing pipeline
Cut report latency from 6 h to 40 min
Data Analyst, Northwind Bank
Automated the monthly risk dashboard
Skills Python, SQL, Spark, Airflow
Languages English (native), French (C1)
pypdf
Experience
Data Engineer, Acme Analytics
Built the nightly billing pipeline
Cut report latency from 6 h to 40 min
Data Analyst, Northwind Bank
Automated the monthly risk dashboard
SkillsPython, SQL, Spark, Airflow
LanguagesEnglish (native), French (C1)
pypdf, with \pdfinterwordspaceon in the preamble
Experience
Data Engineer, Acme Analytics
Built the nightly billing pipeline
Cut report latency from 6 h to 40 min
Data Analyst, Northwind Bank
Automated the monthly risk dashboard
Skills Python, SQL, Spark, Airflow
Languages English (native), French (C1)
Figure 3. The same content in one column. pypdf glued the bold label to the next word until real spaces were written into the PDF.

pdfTeX does not write space characters: it moves the next word to the right, and the extractor has to guess that the gap is a space. pypdf did not guess across the \quad after a bold label. The pdfTeX primitive \pdfinterwordspaceon writes real spaces, and with it the words separated (Figure 3, last cell).

4Icons against labels

Icon fonts such as fontawesome5 draw an envelope or a phone with a glyph that has no letter behind it. Figure 4 shows what the extractors made of four icons, next to the same line with words.

Contact line with envelope, phone, LinkedIn and GitHub icons
Source, icons
\faEnvelope\ jane.doe@example.com \quad
\faPhone\ +33 6 12 34 56 78 \quad
\faLinkedin\ in/janedoe \quad
\faGithub\ janedoe
Source, labels
Email: jane.doe@example.com \quad
Phone: +33 6 12 34 56 78 \quad
LinkedIn: in/janedoe \quad
GitHub: janedoe
pdftotext, icons
# jane.doe@example.com

[U+0083] +33 6 12 34 56 78

ï in/janedoe

§ janedoe
pdftotext, labels
Email: jane.doe@example.com
GitHub: janedoe

Phone: +33 6 12 34 56 78

LinkedIn: in/janedoe
pypdf, icons
/envel⌢pejane.doe@example.com♂phone+33 6 12 34 56 78/linkedinin/janedoe/githubjanedoe
pypdf, labels
Email: jane.doe@example.com Phone: +33 6 12 34 56 78 LinkedIn: in/janedoe
GitHub: janedoe
Figure 4. Icons extract as stray symbols, an invisible control character (shown in red) or glyph names. Labels extract as written.

The labels survived, but notice the order in the pdftotext cell: the wide \quad gaps made it read the wrapped line as columns, so “GitHub” came second. A visible separator avoids that; the latexci builder puts a vertical bar between contact items, and its contact lines came out in order (Figure 6).

5Where to put the dates

Many templates give dates their own column. We set two jobs five ways and checked, in each reading, whether each date stayed on its job title’s line or alone on the line next to it (Table 3).

Layoutpdftotext-layoutpypdf
Date column, tabularkeptkeptkept, glued to the title
Same, with \pdfinterwordspaceonkeptkeptkept
Date column, two minipageseparatedseparatedseparated
Title line, date pushed right with \hfillseparatedkeptkept
Title line, date after a commakeptkeptkept

Table 3. Does each date stay with its job? Computed from the extracted text of each PDF.

Two jobs with dates pushed to the right margin
Source, \hfill
\textbf{Data Engineer}, Acme Analytics \hfill 2021--2024\par
Built the nightly billing pipeline\par
Cut report latency from 6 h to 40 min\par
\smallskip
\textbf{Data Analyst}, Northwind Bank \hfill 2019--2021\par
Automated the monthly risk dashboard
Source, minipage column
\begin{minipage}[t]{2.4cm}
  2021--2024\par\vspace{3\baselineskip}
  2019--2021
\end{minipage}%
\begin{minipage}[t]{10.6cm}
  \textbf{Data Engineer}, Acme Analytics\par
  Built the nightly billing pipeline\par
  Cut report latency from 6 h to 40 min\par
  \textbf{Data Analyst}, Northwind Bank\par
  Automated the monthly risk dashboard
\end{minipage}
pdftotext, \hfill
Data Engineer, Acme Analytics
Built the nightly billing pipeline
Cut report latency from 6 h to 40 min

2021–2024

Data Analyst, Northwind Bank
Automated the monthly risk dashboard

2019–2021
pdftotext, minipage column
2021–2024

2019–2021

Data Engineer, Acme Analytics
Built the nightly billing pipeline
Cut report latency from 6 h to 40 min
Data Analyst, Northwind Bank
Automated the monthly risk dashboard
Figure 5. Two ways to lose a date. Right-aligned dates moved below the bullet lines; a separate date column came out before both jobs.

Two layouts kept every date in place in all three readings: the date written on the title line after a comma, and a tabular date column once \pdfinterwordspaceon stopped pypdf from gluing the date to the job title. Dates in a separate minipage were lost in every reading. A date flushed right with \hfill depends on what follows it, as the next section shows.

6The latexci builder’s output

The CV builder uses T1, lmodern, the glyphtounicode line, hyperref metadata, one column and plain-text contact labels. Its dates are pushed right with \hfill but set smaller and followed by the employer line. We compiled its English example in the industry layout; Figure 6 is the start of the pdftotext output.

pdftotext, first lines
Dr Jane Smith
Postdoctoral Researcher, Computational Neuroscience
j.smith@university.edu | +44 20 0000 0000 | Oxford, UK
janesmith.github.io | github.com/janesmith | ORCID 0000-0002-1825-0097

Profile
Computational modelling of neural circuits, Bayesian inference in sensory processing and machine learning for neuroimaging.

Skills
Programming: Python, PyTorch, JAX, R
Tools: Git, LaTeX, Slurm, Docker
Languages: English (native), French (B2)

Experience
Postdoctoral Research Fellow

Oct. 2023 – present

University of Oxford • Oxford, UK

• Built a Bayesian decoder that cut analysis time for 7T fMRI data by 40%
• Leads a team of 3 PhD students and 2 master’s interns
Figure 6. The builder’s industry example as pdftotext reads it. The date follows its job title.

pypdf is less kind to it. It split a word at a kerned capital in the industry layout and, in the academic layout, glued the date to the title (Figure 7).

pypdf, industry layout
Postdoctoral Research F ellow Oct. 2023 – present
pypdf, academic layout
Ph.D. in Computational NeuroscienceOct. 2019 – Sept. 2023
Figure 7. The same job line in pypdf. pdftotext read both layouts correctly.

We report it as found: no extractor is perfect, which is why the checklist ends with reading your own PDF.

7Checklist

Each item points to the experiment behind it.

  1. Use the T1 encoding. \usepackage[T1]{fontenc} with lmodern made accented letters single characters (Table 2).
  2. Keep the glyphtounicode line. It changed nothing on TeX Live 2026, but it costs nothing and covers other installations (Section 2).
  3. One column. Sidebars were merged with the main column in layout mode (Figure 2).
  4. Dates on the title line. Inline dates stayed in place in every reading; a date column in its own box did not (Table 3).
  5. Words, not icons. Write “Email:” or nothing; separate contact items with a bar, not wide spaces (Figure 4).
  6. Fill the PDF metadata. hyperref writes a title and an author into the file (Figure 8).
  7. Read your own PDF. Run pdftotext in both modes and read the result from top to bottom (Figure 9).
Preamble
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\input{glyphtounicode}\pdfgentounicode=1
\usepackage[hidelinks,pdfusetitle]{hyperref}
\hypersetup{pdftitle={Jane Doe, CV}, pdfauthor={Jane Doe}, pdflang={en}}
pdfinfo on the result
Title:           Jane Doe, CV
Author:          Jane Doe
Creator:         LaTeX with hyperref
Producer:        pdfTeX-1.40.29
Figure 8. The checklist preamble, compiled on its own, and the metadata it produced.
Shell
pdftotext cv.pdf -          # reading order
pdftotext -layout cv.pdf -  # physical layout
Figure 9. Checking a PDF yourself with poppler’s pdftotext.

8Build one that passes

The LaTeX resume and CV builder applies items 1 to 6 for you, in English or French, with an academic or a one-page industry layout. Researchers can fill the publication list from ORCID: see ORCID to a LaTeX publication list. French speakers will find the French conventions in Modèle de CV LaTeX gratuit.

Open the CV builderIndustry resume templateAcademic CV template

Appendix A Limits of this test

Two extractors, one TeX distribution and short documents. A commercial parser may normalise Unicode, merge lines or use optical character recognition; we cannot see inside one. What the test does show is which LaTeX constructs produce text that depends on the reader, and those are the ones worth avoiding. Every source on this page compiled on the date above; the PDFs, the extracted text and the scripts can be regenerated with node scripts/seo-cv/run-ats.mjs.