ResumeForge

Convert plain-text CVs into styled multi-page A4 PDFs using a small CSS-like DSL (.rcss)

About

ResumeForge parses section headings from a plain UTF-8 text file, applies styles defined in an .rcss file, and outputs a paginated A4 PDF. Supports single-column and 2-column grid layouts.

Installation

Clone the repository and install in editable mode:

git clone git@github.com:JohnStrong/ResumeForge.git
cd ResumeForge
python3 -m venv .venv
source .venv/bin/activate
pip install -e .

Source: github.com/JohnStrong/ResumeForge

Commands

validate working

Validate an RCSS style file for syntax errors. Reports the first error with line and column number.

resumeforge validate --style resume.rcss

render working

Render a plain-text CV to a styled PDF using an RCSS style file.

resumeforge render --input resume.txt --style resume.rcss --output resume.pdf

version working

resumeforge version

RCSS DSL

RCSS is a minimal CSS-like language with three selector types:

Font face properties

Used inside @font-face { ... } (optional — falls back to Helvetica):

Layout properties

Used inside layout { ... }:

Heading properties

Used inside heading { ... } (optional — styles the resume header. If omitted, ATS-friendly defaults are applied):

Section properties

Used inside section[name="..."] { ... }:

Style (applied as PDF state before writing):

Write (control how content is rendered):

Layout positioning (grid mode only):

layout { mode: grid; columns: 2; column-widths: 30% 70%; column-gap: 6mm; margins: 20mm 18mm 20mm 18mm; }

section[name="HEADER"] { grid-column: 1; padding: 8mm; align: center; }
section[name="EXPERIENCE"] { grid-column: 2; padding: 6mm; font-size: 12pt; }

Showcase

1. Write your CV as plain text

Lorem Ipsum
Senior Software Engineer
lorem.ipsum@fakeemail.xyz | +44 0000 000000

Links
github.com/loremipsum
linkedin.com/in/loremipsum
loremipsum.dev

Skills
- Python, 
- TypeScript
- Go
- Rust
- AWS (Lambda, DynamoDB, ECS, CDK)
- Terraform
- PostgreSQL, 
- Redis, 
- Kafka
- System Design
- CI/CD
- Kubernetes
- Observability

Work Experience
Senior Software Engineer - Acme Widget Corp
Jan 2021 - Present
- Architected event-driven microservices processing 2M+ events/day
- Led monolith-to-ECS migration reducing deploy times by 70%
- Designed real-time analytics pipeline using Kafka and Flink
- Mentored 4 junior engineers through pairing and code review

Software Engineer - Placeholder Technologies Inc
Mar 2018 - Dec 2020
- Built REST and gRPC APIs serving 500K daily active users
- Implemented canary deployments reducing rollback incidents by 85%
- Developed internal CLI tooling adopted by 3 engineering teams

Software Engineer - Foobar Systems Ltd
Sep 2015 - Feb 2018
- Developed customer-facing dashboard using React and TypeScript
- Designed multi-tenant SaaS schema in PostgreSQL
- Reduced API response times by 40% through caching

Education
MSc Computer Science - University of Nowhere, 2015
BSc Mathematics - University of Somewhere, 2013

References
Dolor Sit Amet
Engineering Director, Acme Widget Corp
dolor.sit@fakecorp.xyz

Consectetur Adipiscing
CTO, Placeholder Technologies Inc
consectetur@faketech.xyz

2. Style it with RCSS

@font-face { font-family: "Carlito"; src: "examples/fonts/Carlito-Regular.ttf"; src-bold: "examples/fonts/Carlito-Bold.ttf"; }

layout { mode: grid; columns: 2; column-widths: 30% 70%; column-gap: 6mm; margins: 20mm 18mm 20mm 18mm; font-family: "Carlito"; }

heading { font-size: 20pt; align: center; line-height: 7; color: #555555; }

section[name="Links"] {
  color: #336699;
  line-height: 5;
  grid-column: 1;
}

section[name="Skills"] {
  line-height: 5;
  grid-column: 1;
}

section[name="Work Experience"] {
  line-height: 5;
  grid-column: 2;
}

section[name="Education"] {
  line-height: 5;
  grid-column: 2;
}

section[name="References"] {
  color: #555555;
  line-height: 5;
  grid-column: 2;
}

3. Render to PDF

resumeforge render --input examples/resume.txt --style examples/valid.rcss --output examples/resume.pdf

Result

Resume PDF output

Troubleshooting

"Unexpected token ... Expected one of: HEADING, LAYOUT, @font-face, SECTION"

Invalid selector in your .rcss file. Only layout { }, heading { }, @font-face { }, and section[name="..."] { } are valid.

"Unexpected token ... Expected one of: SEMICOLON"

A property declaration is missing its trailing semicolon. Every declaration must end with ;

"RCSS must contain a layout { ... } rule"

Your stylesheet has no layout block. Every .rcss file requires one.

"RCSS must contain at least one section[name=...] rule"

Your stylesheet defines a layout but no section rules. Add at least one section[name="..."] { } block.

"No sections found in CV text matching the stylesheet"

The headings in your .txt file don't match any section[name="..."] values. Headings must match exactly (case-sensitive, full line).

"CV text is missing one or more sections defined in the stylesheet"

Your .txt file is missing a heading the stylesheet expects. Ensure every section[name="..."] has a matching heading line.

"No raw sections to apply rules to"

The section mapper received no parsed sections to style. This typically means your CV text was empty or contained no lines matching any stylesheet section names.

"No matching stylesheet rule for one or more sections"

A section was parsed from the CV text but has no corresponding section[name="..."] rule in the stylesheet. Ensure every heading in your .txt file has a matching rule in the .rcss.

"column-widths must sum to 100%"

The percentage values in column-widths do not add up to 100. For example, column-widths: 30% 60%; totals 90%. Adjust so they equal 100%.

"column-widths values must be whole numbers with %"

Each value in column-widths must be an integer followed by %. Decimal values like 33.3% and bare numbers like 35 are not allowed.

"layout property '...' is not valid"

The layout adapter encountered an unrecognised property in layout { ... }. Valid properties: mode, columns, column-widths, column-gap, margins, font-family.

"CV text must have heading content (name/contact) before the first section"

Your .txt file begins immediately with a section heading (e.g. Skills or Experience) with no name or contact information above it. Every CV must have at least one line of text before the first section — typically your full name, job title, and contact details (email, phone, LinkedIn). This heading block is rendered at the top of the PDF before any sections.