How OfflineCV reads your resume
You drop in a PDF and get back two things: the text a generic extractor pulled out of it, and a score built from that text. This page describes what happens in between, including where it goes wrong.
The point is the gap, not the score
A resume PDF looks like a document to you. To a parser it is a bag of positioned glyphs with no reliable notion of “this line is a job title” or “these bullets belong to that role”. Section headers, two-column layouts, text drawn inside a graphic, and dates rendered as an image all have to be inferred, and the inference is sometimes wrong.
What OfflineCV shows you is one generic parser’s reading of your file. It is not a simulation of any particular applicant tracking system — those are closed, they differ from one another, and we have not measured them. Treat a field that comes back empty or garbled as a signal that the information is fragile in this file, not as a verdict from a specific employer’s software.
Step 1 — the extraction cascade
Text extraction runs as a cascade of tiers. Each tier only handles what the one before it left behind, so a clean single-column PDF is finished early and a difficult one keeps falling through.
| Tier | What it does |
|---|---|
| Tier 0 |
Pull raw text items and page geometry out of the PDF with pdf.js, plus the
link annotations — a mailto: or tel: href often
survives when the drawn text of the same email or phone number does not. Layout
probes run here too and flag a scanned or two-column file.
|
| Tier 1 | A heuristic parser turns those items into lines, finds section headers, and segments the experience and education regions into entries with titles, employers, dates and bullets. |
| Tier 1.5 | A regex pass fills in individual fields the heuristic parser missed — most often contact details that sit outside any recognised section. |
| On device AI | Optional, and only where your browser supports WebGPU. A small language model runs locally to critique the result and propose rewrites. Where its reading disagrees with the heuristic parser, the disagreement is shown rather than silently resolved. |
The cascade reports a per-field confidence alongside the values, so a field it guessed at is distinguishable from one it read cleanly.
Step 2 — the score
The score is deterministic and anonymous: the same text always produces the same number, and nothing about it is personalised or sent anywhere. It combines three dimensions.
| Dimension | Weight | What it looks for |
|---|---|---|
| Specificity | 0.4 | Bullets carrying a concrete quantity — a number, a percentage, a currency amount, a duration — rather than a description of responsibilities. |
| Structure | 0.3 | Bullets that are actually bullet-shaped and in a workable length band, under entries that have a title, an employer and a date range. |
| Completeness | 0.3 | Whether the fields a reader expects came through at all: name, contact details, a summary, a skills list, education, experience. |
Note what the weights imply. Completeness is scored on what the parser recovered, so a resume that contains everything but hides it in a layout the parser cannot follow loses points it would keep in a plainer file. That is the intended behaviour, and it is the whole reason to look at the extracted text next to the number.
Step 3 — the layout penalty
Layout is applied as a multiplier over the combined dimensions rather than as a fourth additive dimension, because a layout problem degrades every other signal at once instead of costing a fixed slice.
- No layout triggers — multiplier 1.0, no penalty.
- One trigger — multiplier 0.85.
- Two or more triggers — multiplier 0.7.
- Scanned — multiplier 0. A page of images carries no extractable text, so there is nothing underneath the score to defend.
The rounded result lands in one of three bands:
- 80 and above — Strong
- 60 to 79 — Getting There
- Below 60 — Needs Work
The scoring algorithm is versioned, so a score you recorded under an earlier version is not silently comparable to a new one.
Step 4 — fix it and export
Every extracted field is editable in place. Your edits are stored as overrides on top of the parse rather than written back over it, so the original reading stays visible. From there you can download a plain, single-column PDF built from the corrected fields.
That export is tested against the parser that produced it: the exported PDF has to re-parse to the same fields it was built from. It is the one round trip we can actually verify, and we verify it in CI.
Two more lanes
- JD fit — paste a job description and see which of its requirements your resume gives evidence for, then rewrite sections toward the ones it does not.
- Find jobs — search open job feeds using a query built from your resume (which you can edit before searching), rank the results by fit, and keep the ones worth applying to in a local tracker.
Both work from the parsed resume, never from the PDF bytes. What leaves your browser, and when, is itemised on the privacy page.
Try it on your own resume