How the PDF Generation Pipeline Converts HTML to ATS‑Parseable PDFs in Career‑Ops
The PDF generation pipeline in santifer/career-ops uses a Node.js script to sanitize HTML for ATS compatibility, resolve local font paths to absolute URLs, and render the final document via headless Chromium using Playwright, producing clean, searchable PDFs that pass through applicant tracking systems without character mangling.
The career-ops repository by santifer provides a robust toolkit for generating job application materials. At its core, the PDF generation pipeline transforms styled HTML templates into professional, ATS‑friendly documents through a carefully orchestrated sequence of sanitization and rendering steps.
Pipeline Stages Explained
CLI and Programmatic Entry Points
The pipeline begins in generate-pdf.mjs, which supports both command‑line and module invocation. When run from the terminal, it accepts positional arguments for input HTML and output PDF paths, plus an optional --format flag (values: letter or a4). The entry point validates these parameters, resolves absolute paths for both input and output files, and prepares the environment for processing.
HTML Loading and CV Validation
The script reads the HTML file using fs/promises.readFile. If a cv.md file exists alongside the template, the pipeline loads it to verify section ordering through validateCvSectionOrder. This check ensures the rendered PDF maintains the same structure as the source markdown, preventing accidental reordering during conversion.
Font URL Resolution
To ensure Chromium can access typography regardless of where the HTML is temporarily stored, the pipeline rewrites all relative url('./fonts/...') references inside the HTML to absolute file:// URLs pointing to the fonts/ directory adjacent to the script. This guarantees font availability even when the HTML is opened from a temporary location.
ATS‑Friendly Text Normalisation
Before rendering, normalizeTextForATS scans the HTML body for typographic Unicode characters—including smart quotes, em‑dashes, en‑dashes, non‑breaking spaces, zero‑width characters, arrows, bullets, and currency symbols—and replaces them with plain ASCII equivalents. This step is critical because many ATS parsers fail when encountering these special characters, resulting in mangled or unreadable text. The function logs a detailed map of replacements for debugging purposes.
Headless Rendering with Playwright
The renderHtmlToPdf function launches a headless Chromium instance via Playwright, creates a new page, and injects the processed HTML using page.setContent with a baseURL option pointing to the original HTML directory. It waits for document.fonts.ready to ensure all web fonts load before generating the PDF. The output uses the specified paper size with printable backgrounds enabled and 0.6‑inch margins on all sides.
Output Generation and Metadata
After rendering, the PDF buffer is written to the target path. The script analyzes the PDF structure to count pages, logs the file size, and returns an object containing outputPath, pageCount, and byte size.
Usage Examples
Command‑Line Usage
Generate a PDF from the command line by specifying the input HTML and output paths:
node generate-pdf.mjs templates/cv-template.html output/my-cv.pdf --format=letter
Programmatic Integration
Import the rendering function into your own scripts for custom workflows:
import { readFile } from "fs/promises";
import { renderHtmlToPdf } from "./generate-pdf.mjs";
async function makePdf() {
const html = await readFile("templates/cv-template.html", "utf-8");
const result = await renderHtmlToPdf(html, "output/cv.pdf", { format: "a4" });
console.log(`PDF written to ${result.outputPath}, ${result.pageCount} pages`);
}
makePdf();
Cover Letter Generation
The same pipeline powers the cover letter generator, ensuring consistent output:
node generate-cover-letter.mjs --payload data/cover-letter.json
Re‑using the Pipeline for Cover Letters
The same rendering engine powers the cover letter generator in generate-cover-letter.mjs. This script constructs an HTML payload from a JSON template, then calls renderHtmlToPdf identically to the CV pipeline, ensuring consistent ATS compatibility and formatting across both CVs and cover letters.
Summary
- The pipeline is orchestrated by
generate-pdf.mjs, which handles both CLI and programmatic workflows. - ATS text normalisation via
normalizeTextForATSconverts Unicode typographic characters to ASCII to prevent parsing errors. - Font resources are resolved to absolute
file://paths to ensure portability across different execution environments. - Rendering uses Playwright with headless Chromium, waiting for font loading before PDF generation.
- The same
renderHtmlToPdffunction is reused bygenerate-cover-letter.mjsfor consistent output across document types.
Frequently Asked Questions
What is ATS‑friendly text normalisation and why does it matter?
ATS‑friendly text normalisation is the process of converting typographic Unicode characters—such as smart quotes, em‑dashes, and special symbols—into plain ASCII equivalents. Most applicant tracking systems use older parsing engines that cannot interpret these Unicode characters, often resulting in garbled text or complete parsing failures. The normalizeTextForATS function in generate-pdf.mjs ensures the final PDF contains only characters that ATS software can reliably read.
Can I use the PDF generator with custom HTML templates?
Yes. The pipeline accepts any valid HTML file as input. You can invoke it programmatically by importing renderHtmlToPdf from generate-pdf.mjs and passing your HTML string, output path, and format options (A4 or Letter). Ensure your template references fonts using relative paths (./fonts/...), as the script automatically rewrites these to absolute file:// URLs during processing.
How does the pipeline ensure fonts render correctly in the final PDF?
The script resolves all local font references to absolute file:// paths before rendering. It then uses Playwright to launch Chromium and waits for document.fonts.ready to fire before generating the PDF, guaranteeing that all web fonts are fully loaded and embedded in the output document.
Is the PDF generation process the same for CVs and cover letters?
Yes. Both document types use the identical renderHtmlToPdf function from generate-pdf.mjs. The cover letter generator (generate-cover-letter.mjs) constructs its HTML from a JSON payload, but delegates the actual rendering, ATS sanitisation, and PDF writing to the same core function used for CVs, ensuring consistent formatting and compatibility.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →