A resume/CV PDF generator built on jsPDF with ATS-optimized ghost tags
This is a working resume builder with 11 themes. If you need to generate resumes, use it directly. If you need CSS-to-PDF for something else (invoices, reports, certificates), this codebase demonstrates the approach—study it and adapt the patterns for your needs.
The core breakthrough: device-independent formulas that translate CSS values directly into PDF coordinates, bypassing unreliable DOM measurement.
Building a resume/CV generator? You've hit the wall where: -CSS-to-PDF tools don't support ghost tags for ATS parsing -DOM measurement breaks across devices (mobile vs desktop vs Retina) -jsPDF has no CSS support - you're stuck with raw coordinates -You need perfect spacing consistency between preview and PDF
CSS-jsPDF fixes all of this.
// The breakthrough formulas
PX_TO_MM = 25.4 / 96 // CSS pixels → PDF millimeters
PT_TO_MM = 25.4 / 72 // Points → millimeters
// Apply CSS spacing directly to jsPDF
y += px(theme, 12) // margin-bottom: 12px in CSSInvisible markdown syntax embedded in PDFs for perfect ATS parsing:
// Ghost tags (background color = invisible to humans, visible to ATS)
doc.setTextColor(...bg);
doc.text('## ', margin, y); // Marks section header
// Visible content
doc.setTextColor(...theme.colors.heading);
doc.text('PROFESSIONAL EXPERIENCE', margin, y);Themes are data + rendering functions - extend infinitely:
export default {
name: 'Modern Professional',
spacing: { h2MarginTop: 12, h2MarginBottom: 6 },
fonts: { h1: 18, h2: 10, body: 9 },
colors: { heading: [37, 99, 235], text: [71, 85, 105] },
prefixes: { bullet: '•' },
}Six themes included:
- Modern Professional - Clean, corporate
- Classic Minimalist - Traditional serif
- Compact Executive - Maximum info density
- Terminal/Hacker - Dark mode with code aesthetics
- Creative Designer - Purple gradients + vector bullets
- Creative 2 - Teal variant
Baseline calculations ensure text aligns exactly as designed:
// jsPDF draws from baseline, CSS measures from top
baselineOffset(fontSize, lineHeight) {
const lineBox = fontSize * lineHeight;
const halfLeading = (lineBox - fontSize) / 2;
const fontAscent = fontSize * 0.8; // Inter/Helvetica
return (halfLeading + fontAscent) * PT_TO_MM;
}This project uses ES modules and fetches fonts at runtime, which requires HTTP. Opening index.html directly from the filesystem (file://) will not work due to CORS restrictions.
Run a local server:
# Python (built-in)
python3 -m http.server 8000
# Node.js
npx serve .
# PHP
php -S localhost:8000Then open http://localhost:8000 in your browser.
# Include jsPDF (peer dependency)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
# Load CSS-jsPDF core + theme
<script type="module">
import { generatePDF } from './src/core.js';
import modernTheme from './src/themes/theme-modern.js';
// Your resume data
const data = {
sections: [
{
type: 'header',
content: {
name: 'Jane Developer',
contact: 'jane@example.com | linkedin.com/in/jane'
}
},
{
type: 'summary',
title: 'Professional Summary',
content: {
text: 'Full-stack engineer with 5 years experience...'
}
},
{
type: 'experience',
title: 'Professional Experience',
content: {
jobs: [{
company: 'TechCorp',
roles: [{
title: 'Senior Engineer',
period: '2020 - Present',
bullets: [
'Built microservices architecture serving 10M users',
'Led team of 4 engineers on critical infrastructure'
]
}]
}]
}
}
]
};
// Generate PDF
await generatePDF(modernTheme, data);
</script>src/core.js- Main rendering enginesrc/shapes.js- Vector shape primitives for custom bulletssrc/themes/- Theme implementations (11 themes)docs/ADAPTING_FOR_YOUR_PROJECT.md- Guide for using this code in your own projects
{
sections: [
{
type: 'header' | 'summary' | 'section' | 'experience',
title?: string, // Section heading
content: {
// For header
name?: string,
contact?: string,
// For summary
text?: string,
// For section (bullet list)
bullets?: string[],
// For experience
jobs?: [{
company: string,
roles: [{
title: string,
period: string,
bullets: string[]
}]
}]
}
}
]
}export default {
name: string, // Theme display name
margin: number, // Page margins (mm)
spacing: { // All values in px
headerBottomMargin: number,
h1MarginBottom: number,
h2MarginTop: number,
// ... etc
},
fonts: { // All values in pt
h1: number,
h2: number,
body: number,
// ... etc
},
lineHeights: { // Unitless multipliers
li: number,
body: number
},
colors: { // RGB arrays [r, g, b]
bg: [number, number, number],
name: [number, number, number],
heading: [number, number, number],
// ... etc
},
prefixes: { // Text prefixes for themes like Terminal
h1?: string,
h2?: string,
bullet?: string,
// ... etc
},
images?: { // Optional PNG graphics (Creative themes)
headerBar: string, // Path to image
sectionBar: string,
headerBarHeight: number, // mm
sectionBarWidth: number, // mm
// ... etc
},
fontFamily: string // 'Inter' | 'JetBrainsMono'
}- Copy an existing theme from
src/themes/ - Modify colors, spacing, fonts as needed
- Add custom prefixes or images
- Import and use:
import myTheme from './my-custom-theme.js';
await generatePDF(myTheme, data);Pro tip: Start with theme-modern.js for clean layouts, theme-terminal.js for dark mode, or theme-creative.js for visual flair.
Creative themes draw custom bullet shapes using jsPDF primitives:
// Diamond bullet (4-pointed star)
const cx = margin + 6;
const cy = y - 1;
const size = 0.9;
doc.triangle(cx, cy - size, cx - size*0.3, cy - size*0.3, cx, cy, 'F');
doc.triangle(cx + size, cy, cx + size*0.3, cy - size*0.3, cx, cy, 'F');
// ... 2 more triangles for bottom halfSee theme-creative.js for the complete implementation.
The beauty of device-independent formulas: PDFs look identical everywhere.
Test on:
- Desktop (96 DPI)
- Retina displays (2x, 3x pixel density)
- Mobile devices (variable DPI)
- Print preview
Spacing will be pixel-perfect across all devices.
This project is the culmination of solving a real problem through first principles. Contributions that maintain the device-independent philosophy are welcome:
- ✓New themes
- ✓Enhanced ghost tag support
- ✓Better font loading
- ✓Improved page break logic -DOM measurement (breaks the core principle) -CSS parsing (reinventing the wheel)
MIT License - See LICENSE file for details
CSS-jsPDF is built on top of jsPDF, which is also MIT licensed. We comply with jsPDF's license by:
- Listing jsPDF as a peer dependency
- Maintaining clear attribution
- Not redistributing jsPDF code (users install it separately)
Built during the development of a privacy-first resume builder where all PDF generation happens client-side. The theme toggle integration that destroys and re-renders charts taught us the importance of true device independence.
Special thanks to:
- The jsPDF maintainers (MIT License) for providing the foundational PDF library that made this possible
- The CSS and PDF specification authors for defining the device-independent standards we rely on
- jsPDF - The underlying PDF library
- Inter Font - Beautiful UI font used in modern themes
- JetBrains Mono - Code-style font for Terminal theme
Built with first principles thinking
Questions? Found a bug? Open an issue!