# Research notes

Why the reference layout is shaped the way it is, what it costs, and what the
skill does about it. Claims that are not self-evident from the file itself are
sourced.

---

## 1. ATS parsing safety

Applicant tracking systems extract text linearly — top to bottom, left to
right — and anything that breaks that reading order gets scrambled or dropped.
The reference layout is close to the safest possible shape, and it got there by
being plain rather than by being clever.

**What the reference does right, verified against the XML:**

| Property | Evidence | Why it matters |
|---|---|---|
| Single column | no `<w:cols w:num>` above 1, no `<w:tbl>` | multi-column and table layouts get read across cell boundaries, interleaving unrelated sentences |
| No tables | zero `<w:tbl>` elements | the single most common cause of scrambled parses |
| No text boxes | zero `<w:txbxContent>`, `<w:pict>`, `<w:drawing>` | floating objects sit outside the main text flow and can be skipped entirely |
| No headers or footers | no `word/header*.xml` or `word/footer*.xml` parts | roughly a quarter of ATS platforms never read them, so contact details placed there vanish |
| No images | zero `<w:drawing>` | non-text, contributes nothing extractable |
| Real list numbering | `numId 1` → Symbol U+F0B7, never a typed `•` | the glyph lives in `numbering.xml`, so extractors emit clean text with no stray characters |
| Contact details in the body | ¶2, a normal centered paragraph | the safest possible place for them |
| Standard section names | `PROFESSIONAL SUMMARY`, `TECHNICAL SKILLS`, `PROFESSIONAL EXPERIENCE`, `EDUCATION` | parsers key off conventional headings; inventive ones ("Where I've Made An Impact") are not recognized |
| `.docx`, not PDF | — | `.docx` is universally parseable; PDF quality varies by generator |

`validate.py` asserts the absence of tables, drawings, pictures, text boxes,
frames, content controls, headers and footers on every build, so a future edit
cannot quietly reintroduce one.

**Mild risks that remain, and what the skill does:**

1. **Justified text.** Not an ATS problem — justification is a rendering
   property and the text layer is unaffected. It is a human-readability
   tradeoff; see §2.
2. **Bookman Old Style.** Not an ATS problem either — font names do not affect
   text extraction. It is a rendering-portability issue; see §4.
3. **The bold `Category:` label inside skills paragraphs.** Some naive parsers
   split on runs rather than paragraphs and can emit `Programming & Data:` and
   its item list as separate fields. This is inherent to the reference design.
   The skill keeps it because it matches the reference, and it is a minor risk:
   the text is contiguous within one paragraph, which is what conforming
   extractors read.
4. **A4 page size with a US phone number.** Cosmetic, see §3.

Sources: [Jobscan — why tables and columns break parsing](https://www.jobscan.co/blog/resume-tables-columns-ats/),
[Jobscan — ATS formatting mistakes](https://www.jobscan.co/blog/ats-formatting-mistakes/),
[Hireflow — why ATS rejects text boxes](https://hireflow.net/blog/why-ats-rejects-resumes-with-text-boxes),
[Resumemate — tables, columns, text boxes and safer layouts](https://www.resumemate.io/blog/tables-columns-text-boxes-do-they-break-ats-safer-layouts/).

---

## 2. Justified text (`jc="both"`)

The reference justifies all 107 bullets and all 8 skills lines. The skill keeps
it, because reproducing the reference is the point.

**The tradeoff.** Justification works by stretching inter-word spaces to force
both margins flush. Where the stretched spaces on consecutive lines happen to
line up vertically you get *rivers* — pale channels running down the paragraph
that pull the eye off the line. Rivers get worse as the measure narrows and as
average word length rises, and Word does not hyphenate by default, so it has
only one lever (word spacing) to work with.

Accessibility guidance is consistently against justification for body text:
uneven word spacing makes it harder to track from the end of one line to the
start of the next, which particularly affects readers with dyslexia. W3C's
WCAG technique **F88** documents fully-justified text as a readability failure
for low-vision users.

**How bad is it here?** The measure is 7.27″ at 10 pt — around 105–115
characters per line, which is roughly double the 45–75 character range usually
recommended for comfortable reading. A wide measure is actually the *good* case
for justification: there are enough spaces per line that each one only has to
stretch a little. Spot-checking the rendered pages, the spacing looks even and
no obvious rivers form. This is a defensible use of justification.

Exposed as **`--no-justify`**, which drops `<w:jc w:val="both"/>` from bullets,
skills lines and plain paragraphs. `validate.py --no-justify` adjusts its
structural checks to match.

Sources: [W3C WAI — WCAG F88, failure due to fully justified text](https://www.w3.org/WAI/WCAG20/Techniques/failures/F88),
[W3C WAI low-vision task force — justification](https://www.w3.org/WAI/GL/low-vision-a11y-tf/wiki/Justification),
[WebAIM — typographical layout](https://webaim.org/techniques/textlayout/),
[Wikipedia — river (typography)](https://en.wikipedia.org/wiki/River_(typography)).

---

## 3. A4 vs US Letter

The reference is **A4** (`w:w="11906" w:h="16838"`, `w:code="9"`) — 210 × 297 mm
— while the contact line carries a US phone number and every employer is in the
United States.

This is almost certainly unintentional: Word takes its default page size from
the system locale, and a document started on a machine configured outside the
US gets A4 without the author ever choosing it. The `Normal` style also carries
`<w:lang w:val="en-IN"/>`, which supports that reading.

**It is kept as the default anyway**, because the brief is to reproduce the
reference exactly, and because it barely matters: A4 is 5 mm narrower and 18 mm
taller than Letter, so an A4 document prints on Letter with slightly larger side
margins and slightly cropped-in top/bottom — nobody reviewing a resume will
notice, and virtually all resumes are read on screen.

Where it *does* matter is page count. A4's text height at these margins is
10.69″ versus Letter's 10.00″ — about 6.5% more room, roughly 4 extra lines per
page. A resume that fits 4 pages on A4 can spill to a 5th on Letter.

```bash
python3 scripts/build_resume.py resume.txt --page-size letter -o out.docx
```

sets `w:w="12240" w:h="15840"` with `w:code="1"`. Nothing else changes.

Dimensions are arithmetic, not opinion: 210 mm = 8.2677″ × 1440 twips = 11906;
297 mm = 11.6929″ × 1440 = 16838; 8.5″ × 1440 = 12240; 11″ × 1440 = 15840.

---

## 4. Bookman Old Style availability

Bookman Old Style is a Monotype design (drawn by Ong Chong Wah, digitized
1991–95) distributed **with Microsoft Office**, not with Windows or macOS
themselves. Microsoft's own typography entry lists its supplying product as
"available within Office applications" and points at the Office *cloud fonts*
list — meaning modern Office installs may fetch it on demand rather than
shipping it up front. On this machine it was present as an on-demand face at
`/System/Library/PrivateFrameworks/FontServices.framework/.../BookmanOldStyle.ttc`
with all four styles (Regular, Bold, Italic, Bold Italic).

**Where you can expect it:** any machine with Microsoft Office installed;
most Windows machines; many Macs.
**Where you cannot:** most Linux systems (it is proprietary, so distributions
do not ship it), Office-less Macs, Google Docs, and many web-based previewers.

**What happens when it is missing.** The document is still valid and still
carries `w:ascii="Bookman Old Style"`; the viewer substitutes a face using the
`panose1` and `sig` data in `fontTable.xml` (`02050604050505020204` — a
serif/roman, medium-weight, variable-pitch descriptor, so substitutes tend to be
serifs such as Bookman URW, Times, or the generic serif). Because Bookman is
unusually wide for its point size, most substitutes are narrower: lines rewrap,
fewer lines are needed, and the page count can drop. Text and structure are
never lost — only the metrics shift.

Two consequences worth knowing:

- Rendering the `.docx` on a Linux CI box to check page count gives an answer
  that does not apply to the recruiter opening it in Word.
- If you export to PDF *from a machine that has the font*, the font is embedded
  and everyone sees the intended layout. This is the reliable way to freeze it.

The skill ships `fontTable.xml` untouched, so the declaration and its
substitution metadata are always correct. `--font "<name>"` switches the body
face if you ever need to, though a font not declared in `fontTable.xml` will be
flagged by `validate.py`.

Sources: [Microsoft Learn — Bookman Old Style font family](https://learn.microsoft.com/en-us/typography/font-list/bookman-old-style),
[Microsoft — cloud fonts in Office](https://support.office.com/en-us/article/cloud-fonts-in-office-f7b009fe-037f-45ed-a556-b5fe6ede6adb).

---

## 5. Page-count behavior

Measured by rendering the reference through Microsoft Word with the real font
installed and counting lines per page in `pdftotext -layout` output:

```
reference.docx -> 5 pages, text lines per page = [61, 63, 63, 62, 33]
```

**≈ 63 rendered lines per A4 page** at 10 pt Bookman Old Style, `line="240"`
auto, 0.5″ margins. The text area is 769.9 pt tall, so the effective line height
is ~12.2 pt — Bookman has tall metrics for its nominal size.

Useful rules of thumb at this measure (7.27″, ~105–115 characters per line):

| Content | Lines consumed |
|---|---|
| A typical 180–220 character bullet | 2 |
| A short bullet under ~105 characters | 1 |
| A skills line (usually 200–350 characters) | 2–3 |
| Job header + title + `Responsibilities:` | 3 |
| A section header + its preceding blank | 2 |

The reference's 141 paragraphs render as 282 lines — exactly 2.0 lines per
paragraph on average — over 5 pages.

**So:** roughly **30 two-line bullets per page**, and each new job block costs 3
lines of overhead plus its bullets. A page ends up around 55–63 lines of real
content.

**Spotting an overrun.** The last page of the reference holds 33 lines, about
half full — comfortable. If your final page carries fewer than ~15 lines you
have overrun onto a page that looks like an accident. Removing 3–4 two-line
bullets, or a single job's worth of overhead, usually pulls it back. On US
Letter, budget ~59 lines per page instead of 63.

```bash
# quick page count on a machine with Word
osascript -e 'tell application "Microsoft Word"
  open file name POSIX file "…/out.docx" with read only
  compute statistics active document statistic statistic pages
end tell'
```

---

## 6. Date formats and the EN DASH

US resume convention for a date range is `Month Year – Month Year`, with an
**EN DASH (U+2013)**, not a hyphen. The en dash is the standard mark for spans
and ranges in English typography; the hyphen joins compounds, and the em dash
(U+2014) sets off clauses. Word's AutoFormat silently converts ` - ` between
words to ` – `, which is how the reference ended up consistent: all four job
headers use U+2013, and so does the second EDUCATION line.

`Present` is the conventional open end for a current role. `Current`, `Till
Date`, `To Date`, `Now` and `Ongoing` all appear in the wild; the parser accepts
them and passes them through as written — normalizing the *separator* is a
formatting decision, but rewriting `Till Date` to `Present` would be editing the
user's words, which this skill does not do.

Normalization applied to date ranges:

| Input | Output |
|---|---|
| `November 2025 - Present` | `November 2025 – Present` |
| `Jan 2022 -- Dec 2023` | `Jan 2022 – Dec 2023` |
| `2020 — 2021` | `2020 – 2021` |
| `March 2018 to December 2019` | `March 2018 – December 2019` |
| `November 2025 – Present` | unchanged |

Spacing is normalized to exactly one space either side of the dash.

Elsewhere in the text, U+2013 and U+2014 are left strictly alone — they are
legitimate punctuation the author may have meant. Only the hyphen *lookalikes*
that carry no semantic difference are folded to ASCII `-`: U+2010 HYPHEN,
U+2011 NON-BREAKING HYPHEN, U+2012 FIGURE DASH and U+2212 MINUS SIGN. These
arrive invisibly via copy-paste from PDFs and web pages, and leaving them in
makes the text fail a plain-text search for the word the reader typed.
