md-2-ats 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/cli.js +31 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +30 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +31 -6
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -13,3 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
13
13
|
- CLI binary `md-2-ats` (`md-2-ats <input.md> [options]`) with `--output`,
|
|
14
14
|
`--profile`, `--profile-size`, `--profile-position`, `--font-*`, `--help`,
|
|
15
15
|
and `--version` flags.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- Inline Markdown links now render as clickable PDF link annotations (previously
|
|
19
|
+
the href was discarded and only plain text was drawn). Link text is drawn in
|
|
20
|
+
the theme accent color.
|
|
21
|
+
|
package/dist/cli.js
CHANGED
|
@@ -317,7 +317,7 @@ function createDefaultTheme() {
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
// src/renderer/render.ts
|
|
320
|
-
import { StandardFonts, rgb } from "pdf-lib";
|
|
320
|
+
import { PDFString, StandardFonts, rgb } from "pdf-lib";
|
|
321
321
|
import fontkit from "@pdf-lib/fontkit";
|
|
322
322
|
|
|
323
323
|
// src/page/config.ts
|
|
@@ -379,7 +379,7 @@ function wrapRuns(runs, maxWidth) {
|
|
|
379
379
|
const parts = run.text.split(/(\s+)/);
|
|
380
380
|
for (const part of parts) {
|
|
381
381
|
if (part === "" || /^\s+$/.test(part)) continue;
|
|
382
|
-
words.push({ text: part, style: run.style });
|
|
382
|
+
words.push({ text: part, style: run.style, href: run.href });
|
|
383
383
|
}
|
|
384
384
|
}
|
|
385
385
|
const lines = [];
|
|
@@ -672,7 +672,7 @@ function nodesToRuns(ctx, nodes, base) {
|
|
|
672
672
|
walk(node.children, { font: ctx.italic, size: style.size });
|
|
673
673
|
break;
|
|
674
674
|
case "link":
|
|
675
|
-
runs.push({ text: ctx.sanitize ? sanitizeText(node.text) : node.text, style });
|
|
675
|
+
runs.push({ text: ctx.sanitize ? sanitizeText(node.text) : node.text, style, href: node.href });
|
|
676
676
|
break;
|
|
677
677
|
case "image":
|
|
678
678
|
break;
|
|
@@ -690,18 +690,43 @@ function drawLine(ctx, line, color) {
|
|
|
690
690
|
const pdfPage = currentPdfPage(ctx);
|
|
691
691
|
const size = Math.max(...line.map((r) => r.style.size));
|
|
692
692
|
const baseline = ctx.flow.cursor.y + size * ASCENT;
|
|
693
|
+
const baselinePdf = yToPdf(ctx, baseline);
|
|
693
694
|
let x = marginLeft(ctx);
|
|
694
695
|
for (const run of line) {
|
|
696
|
+
const runColor = run.href ? ctx.theme.color.accent : color;
|
|
695
697
|
pdfPage.drawText(run.text, {
|
|
696
698
|
x,
|
|
697
|
-
y:
|
|
699
|
+
y: baselinePdf,
|
|
698
700
|
size: run.style.size,
|
|
699
701
|
font: run.style.font,
|
|
700
|
-
color: toColor(
|
|
702
|
+
color: toColor(runColor)
|
|
701
703
|
});
|
|
702
|
-
|
|
704
|
+
const width = measureWidth(run);
|
|
705
|
+
if (run.href && width > 0) {
|
|
706
|
+
addLinkAnnotation(
|
|
707
|
+
ctx,
|
|
708
|
+
pdfPage,
|
|
709
|
+
run.href,
|
|
710
|
+
x,
|
|
711
|
+
baselinePdf - run.style.size * 0.25,
|
|
712
|
+
x + width,
|
|
713
|
+
baselinePdf + run.style.size * 0.85
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
x += width;
|
|
703
717
|
}
|
|
704
718
|
}
|
|
719
|
+
function addLinkAnnotation(ctx, pdfPage, href, x1, y1, x2, y2) {
|
|
720
|
+
const context = ctx.doc.context;
|
|
721
|
+
const annot = context.obj({
|
|
722
|
+
Type: "Annot",
|
|
723
|
+
Subtype: "Link",
|
|
724
|
+
Rect: [x1, y1, x2, y2],
|
|
725
|
+
Border: [0, 0, 0],
|
|
726
|
+
A: { Type: "Action", S: "URI", URI: PDFString.of(href) }
|
|
727
|
+
});
|
|
728
|
+
pdfPage.node.addAnnot(context.register(annot));
|
|
729
|
+
}
|
|
705
730
|
function marginLeft(ctx) {
|
|
706
731
|
return ctx.page.margin.left;
|
|
707
732
|
}
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/generate.ts","../src/validator/index.ts","../src/parser/index.ts","../src/renderer/index.ts","../src/theme/default.ts","../src/renderer/render.ts","../src/page/config.ts","../src/page/flow.ts","../src/renderer/measure.ts","../src/renderer/sanitize.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { existsSync, realpathSync } from \"node:fs\";\nimport { extname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { generateCV } from \"./generate\";\nimport type { FontSet, ProfilePosition } from \"./renderer/render\";\n\nexport type CliOptions = {\n input?: string;\n output?: string;\n profile?: string;\n profileSize?: number;\n profilePosition?: ProfilePosition;\n fontRegular?: string;\n fontBold?: string;\n fontItalic?: string;\n help: boolean;\n version: boolean;\n};\n\nconst HELP = `md-2-ats - render an ATS-friendly PDF from a Markdown resume\n\nUsage:\n md-2-ats <input.md> [options]\n\nOptions:\n -o, --output <path> Output PDF path (default: input with .pdf)\n -p, --profile <path> Profile photo (PNG or JPEG)\n --profile-size <points> Profile photo side length (default: 96)\n --profile-position <pos> left | center | right (default: right)\n --font-regular <path> Custom TTF regular font\n --font-bold <path> Custom TTF bold font\n --font-italic <path> Custom TTF italic font\n -h, --help Show this help\n -v, --version Show version\n\nExamples:\n md-2-ats cv.md\n md-2-ats cv.md -o out.pdf -p photo.png\n md-2-ats cv.md --font-regular NotoSansSC-Regular.ttf`;\n\nexport function parseArgs(argv: string[]): CliOptions {\n const opts: CliOptions = { help: false, version: false };\n\n const value = (flag: string, i: number): string => {\n const v = argv[i + 1];\n if (v === undefined || v.startsWith(\"-\")) {\n throw new Error(`Missing value for ${flag}`);\n }\n return v;\n };\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]!;\n\n switch (arg) {\n case \"-h\":\n case \"--help\":\n opts.help = true;\n break;\n case \"-v\":\n case \"--version\":\n opts.version = true;\n break;\n case \"-o\":\n case \"--output\":\n opts.output = value(arg, i);\n i++;\n break;\n case \"-p\":\n case \"--profile\":\n opts.profile = value(arg, i);\n i++;\n break;\n case \"--profile-size\": {\n const raw = value(arg, i);\n i++;\n const n = Number(raw);\n if (!Number.isFinite(n) || n <= 0) throw new Error(`Invalid --profile-size: \"${raw}\"`);\n opts.profileSize = n;\n break;\n }\n case \"--profile-position\": {\n const raw = value(arg, i);\n i++;\n if (raw !== \"left\" && raw !== \"center\" && raw !== \"right\") {\n throw new Error(`Invalid --profile-position: \"${raw}\" (use left, center, or right)`);\n }\n opts.profilePosition = raw;\n break;\n }\n case \"--font-regular\":\n opts.fontRegular = value(arg, i);\n i++;\n break;\n case \"--font-bold\":\n opts.fontBold = value(arg, i);\n i++;\n break;\n case \"--font-italic\":\n opts.fontItalic = value(arg, i);\n i++;\n break;\n default:\n if (arg.startsWith(\"-\")) throw new Error(`Unknown option: ${arg}`);\n if (opts.input !== undefined) throw new Error(`Unexpected argument: ${arg}`);\n opts.input = arg;\n }\n }\n\n return opts;\n}\n\nasync function readVersion(): Promise<string> {\n try {\n const url = new URL(\"../package.json\", import.meta.url);\n const pkg = JSON.parse(await readFile(url, \"utf8\")) as { version?: string };\n return pkg.version ?? \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\nexport async function runCli(argv: string[]): Promise<number> {\n let opts: CliOptions;\n try {\n opts = parseArgs(argv);\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n console.error(`Run \"md-2-ats --help\" for usage.`);\n return 1;\n }\n\n if (opts.help) {\n console.log(HELP);\n return 0;\n }\n\n if (opts.version) {\n console.log(await readVersion());\n return 0;\n }\n\n if (!opts.input) {\n console.error(\"Error: missing input Markdown file.\");\n console.error(`Run \"md-2-ats --help\" for usage.`);\n return 1;\n }\n\n if (!existsSync(opts.input)) {\n console.error(`Error: input file not found: ${opts.input}`);\n return 1;\n }\n\n const output = opts.output ?? `${opts.input.slice(0, opts.input.length - extname(opts.input).length)}.pdf`;\n\n if ((opts.fontBold || opts.fontItalic) && !opts.fontRegular) {\n console.error(\"Error: --font-regular is required when specifying custom fonts.\");\n return 1;\n }\n\n try {\n const profile = opts.profile\n ? {\n bytes: await readFile(opts.profile),\n ...(opts.profileSize !== undefined ? { size: opts.profileSize } : {}),\n ...(opts.profilePosition !== undefined ? { position: opts.profilePosition } : {}),\n }\n : undefined;\n\n const fonts: FontSet | undefined = opts.fontRegular\n ? {\n regular: await readFile(opts.fontRegular),\n ...(opts.fontBold ? { bold: await readFile(opts.fontBold) } : {}),\n ...(opts.fontItalic ? { italic: await readFile(opts.fontItalic) } : {}),\n }\n : undefined;\n\n const result = await generateCV({\n input: opts.input,\n output,\n ...(profile ? { profile } : {}),\n ...(fonts ? { fonts } : {}),\n });\n\n for (const issue of result.issues) {\n console.warn(`warning: ${issue.message}`);\n }\n console.log(`Generated ${resolve(result.output)}`);\n return 0;\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n return 1;\n }\n}\n\nconst entry = process.argv[1];\nconst isMain =\n entry !== undefined &&\n existsSync(entry) &&\n realpathSync(fileURLToPath(import.meta.url)) === realpathSync(entry);\n\nif (isMain) {\n void runCli(process.argv.slice(2)).then((code) => {\n process.exitCode = code;\n });\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { validateCV, type Issue } from \"./validator\";\nimport { renderMarkdownToPdf, type FontSet, type ProfileImage } from \"./renderer\";\nimport { createDefaultTheme, type Theme } from \"./theme\";\nimport type { PageConfig } from \"./page\";\n\nexport type GenerateCVOptions = {\n /** Path to the source Markdown file. */\n input: string;\n /** Path where the generated PDF will be written. */\n output: string;\n /** Profile photo to render in the header. */\n profile?: ProfileImage;\n /** Custom TTF fonts for non-Latin text (e.g. Noto Sans CJK). */\n fonts?: FontSet;\n theme?: Theme;\n page?: PageConfig;\n};\n\nexport type GenerateCVResult = {\n /** All validation issues found (warnings only; errors throw). */\n issues: Issue[];\n /** Path the PDF was written to. */\n output: string;\n};\n\nexport async function generateCV(options: GenerateCVOptions): Promise<GenerateCVResult> {\n const markdown = await readFile(options.input, \"utf8\");\n\n const validation = validateCV(markdown);\n const errors = validation.issues.filter((i) => i.severity === \"error\");\n\n if (errors.length > 0) {\n const summary = errors.map((e) => `- ${e.message}`).join(\"\\n\");\n throw new Error(`CV validation failed:\\n${summary}`);\n }\n\n const theme = options.theme ?? createDefaultTheme();\n const bytes = await renderMarkdownToPdf(markdown, {\n theme,\n page: options.page,\n profile: options.profile,\n fonts: options.fonts,\n });\n\n await writeFile(options.output, bytes);\n\n return {\n issues: validation.issues,\n output: options.output,\n };\n}\n","import { marked } from \"marked\";\nimport { parseMarkdown } from \"../parser\";\nimport type {\n HeadingBlock,\n InlineNode,\n MarkdownBlock,\n MarkdownDocument,\n} from \"../parser/types\";\n\nexport type Severity = \"error\" | \"warning\";\n\nexport type Issue = {\n severity: Severity;\n blockIndex?: number;\n message: string;\n};\n\nexport type ValidationResult = {\n issues: Issue[];\n valid: boolean;\n};\n\nconst EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$/i;\nconst RAW_EMAIL_REGEX = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/i;\n\nexport function validateCV(markdown: string): ValidationResult {\n const doc = parseMarkdown(markdown);\n\n if (doc.blocks.length === 0) {\n return { issues: [{ severity: \"error\", message: \"Document is empty.\" }], valid: false };\n }\n\n const issues: Issue[] = [];\n\n const hasName = doc.blocks.some((b) => b.type === \"heading\" && b.level === 1);\n if (!hasName) {\n issues.push({\n severity: \"error\",\n message: 'Missing a level-1 heading. Add \"# Your Name\" for the candidate name.',\n });\n }\n\n validateContact(doc, issues);\n validateHierarchy(doc, issues);\n validateSection(doc, \"experience\", issues);\n validateSection(doc, \"education\", issues);\n detectUnsupported(markdown, issues);\n\n return { issues, valid: issues.every((i) => i.severity !== \"error\") };\n}\n\nfunction validateContact(doc: MarkdownDocument, issues: Issue[]): void {\n const texts = flattenText(doc);\n const links = flattenLinks(doc);\n\n const emails = new Set<string>();\n for (const t of texts) {\n const m = t.match(RAW_EMAIL_REGEX);\n if (m) emails.add(m[0]);\n }\n for (const href of links) {\n if (href.startsWith(\"mailto:\")) emails.add(href.slice(\"mailto:\".length).split(\"?\")[0] ?? \"\");\n }\n\n const validEmails = [...emails].filter((e) => EMAIL_REGEX.test(e));\n const hasContactLink = links.some((href) => {\n const lower = href.toLowerCase();\n return /mailto:|github\\.com|linkedin\\.com|twitter\\.com|x\\.com|dev\\.to|t\\.me/.test(lower);\n });\n\n if (validEmails.length === 0 && !hasContactLink) {\n issues.push({\n severity: \"warning\",\n message: 'No contact info found. Add an email or a contact link (e.g. mailto:, github, linkedin).',\n });\n }\n\n if ([...emails].some((e) => !EMAIL_REGEX.test(e))) {\n issues.push({\n severity: \"warning\",\n message: `Invalid email address found: \"${[...emails].filter((e) => !EMAIL_REGEX.test(e)).join(\", \")}\".`,\n });\n }\n}\n\nfunction validateHierarchy(doc: MarkdownDocument, issues: Issue[]): void {\n let prevLevel = 0;\n\n doc.blocks.forEach((block, index) => {\n if (block.type !== \"heading\") return;\n if (prevLevel !== 0 && block.level > prevLevel + 1) {\n issues.push({\n severity: \"warning\",\n blockIndex: index,\n message: `Heading level skipped from H${prevLevel} to H${block.level}. Prefer H1 → H2 → H3 order.`,\n });\n }\n prevLevel = block.level;\n });\n}\n\nfunction validateSection(doc: MarkdownDocument, sectionName: string, issues: Issue[]): void {\n const headings: { block: HeadingBlock; index: number }[] = [];\n doc.blocks.forEach((block, index) => {\n if (block.type === \"heading\") headings.push({ block, index });\n });\n\n const target = headings.find(\n ({ block }) => block.level === 2 && headingText(block).toLowerCase() === sectionName,\n );\n\n if (!target) {\n issues.push({\n severity: \"warning\",\n message: `Section \"${sectionName}\" not found. Add \"## ${sectionName}\" heading.`,\n });\n return;\n }\n\n const body: { block: MarkdownBlock; index: number }[] = [];\n const nextSection = doc.blocks\n .slice(target.index + 1)\n .findIndex((b) => b.type === \"heading\" && b.level === 2);\n const end = nextSection === -1 ? doc.blocks.length : target.index + 1 + nextSection;\n\n for (let k = target.index + 1; k < end; k++) {\n const block = doc.blocks[k];\n if (block) body.push({ block, index: k });\n }\n\n const hasBody = body.some(({ block }) => block.type !== \"heading\" && block.type !== \"thematicBreak\");\n if (!hasBody) {\n issues.push({\n severity: \"warning\",\n blockIndex: target.index,\n message: `Section \"${sectionName}\" has no content below its heading.`,\n });\n return;\n }\n\n const entries = body.filter(\n (s): s is { block: HeadingBlock; index: number } =>\n s.block.type === \"heading\" && s.block.level === 3,\n );\n\n entries.forEach(({ block, index }) => {\n const hasContent = body\n .filter((s) => s.index > index)\n .some(({ block: b }) => b.type !== \"heading\" && b.type !== \"thematicBreak\");\n if (!hasContent) {\n issues.push({\n severity: \"warning\",\n blockIndex: index,\n message: `Entry \"${headingText(block)}\" in \"${sectionName}\" has no content below it.`,\n });\n }\n });\n}\n\nfunction detectUnsupported(markdown: string, issues: Issue[]): void {\n const supported = new Set([\"space\", \"heading\", \"paragraph\", \"list\", \"hr\", \"blockquote\"]);\n\n for (const token of marked.lexer(markdown)) {\n if (!supported.has(token.type)) {\n issues.push({\n severity: \"warning\",\n message: `Unsupported Markdown ignored: \"${token.type}\".`,\n });\n }\n }\n}\n\nfunction headingText(block: HeadingBlock): string {\n return inlineText(block.children);\n}\n\nfunction inlineText(nodes: InlineNode[]): string {\n let out = \"\";\n for (const node of nodes) {\n switch (node.type) {\n case \"text\":\n out += node.text;\n break;\n case \"strong\":\n case \"emphasis\":\n out += inlineText(node.children);\n break;\n case \"link\":\n out += node.text;\n break;\n case \"image\":\n break;\n }\n }\n return out;\n}\n\nfunction flattenText(doc: MarkdownDocument): string[] {\n const out: string[] = [];\n for (const block of doc.blocks) {\n if (block.type === \"paragraph\") out.push(inlineText(block.children));\n else if (block.type === \"heading\") out.push(inlineText(block.children));\n }\n return out;\n}\n\nfunction flattenLinks(doc: MarkdownDocument): string[] {\n const out: string[] = [];\n const walk = (nodes: InlineNode[]): void => {\n for (const node of nodes) {\n if (node.type === \"link\") out.push(node.href);\n else if (node.type === \"strong\" || node.type === \"emphasis\") walk(node.children);\n }\n };\n for (const block of doc.blocks) {\n if (block.type === \"paragraph\") walk(block.children);\n else if (block.type === \"heading\") walk(block.children);\n }\n return out;\n}\n","import { marked, type Tokens, type Token } from \"marked\";\nimport type {\n InlineNode,\n ListBlock,\n ListItem,\n MarkdownBlock,\n MarkdownDocument,\n} from \"./types\";\n\nexport function parseMarkdown(markdown: string): MarkdownDocument {\n const tokens = marked.lexer(markdown);\n\n const blocks: MarkdownBlock[] = [];\n\n for (const token of tokens) {\n const block = parseBlock(token);\n\n if (block) {\n blocks.push(block);\n }\n }\n\n return { blocks };\n}\n\nfunction parseBlock(token: Token): MarkdownBlock | null {\n switch (token.type) {\n case \"heading\": {\n const heading = token as Tokens.Heading;\n const level = clampHeadingLevel(heading.depth);\n return { type: \"heading\", level, children: parseInline(heading.tokens) };\n }\n\n case \"paragraph\":\n return { type: \"paragraph\", children: parseInline(token.tokens ?? []) };\n\n case \"list\":\n return parseList(token as Tokens.List);\n\n case \"hr\":\n return { type: \"thematicBreak\" };\n\n case \"blockquote\":\n return parseBlockquote(token as Tokens.Blockquote);\n\n case \"space\":\n return null;\n\n default:\n return null;\n }\n}\n\nfunction parseBlockquote(token: Tokens.Blockquote): MarkdownBlock {\n const children: InlineNode[] = [];\n\n for (const block of token.tokens ?? []) {\n if (block.type === \"paragraph\") {\n if (children.length > 0) {\n children.push({ type: \"text\", text: \" \" });\n }\n children.push(...parseInline((block as Tokens.Paragraph).tokens ?? []));\n }\n }\n\n return { type: \"blockquote\", children };\n}\n\nfunction parseList(token: Tokens.List): ListBlock {\n return {\n type: \"list\",\n ordered: token.ordered,\n items: token.items.map(parseListItem),\n };\n}\n\nfunction parseListItem(item: Tokens.ListItem): ListItem {\n const children: InlineNode[] = [];\n const nested: ListBlock[] = [];\n\n for (const block of item.tokens ?? []) {\n if (block.type === \"list\") {\n nested.push(parseList(block as Tokens.List));\n } else if (\"tokens\" in block && block.tokens) {\n children.push(...parseInline(block.tokens));\n }\n }\n\n return { children, ...(nested.length > 0 ? { nested } : {}) };\n}\n\nfunction clampHeadingLevel(depth: number): 1 | 2 | 3 {\n return Math.min(Math.max(depth, 1), 3) as 1 | 2 | 3;\n}\n\nfunction parseInline(tokens: Token[]): InlineNode[] {\n const nodes: InlineNode[] = [];\n\n for (const token of tokens) {\n switch (token.type) {\n case \"text\": {\n const text = token as Tokens.Text;\n nodes.push({ type: \"text\", text: text.text });\n break;\n }\n\n case \"strong\": {\n const strong = token as Tokens.Strong;\n nodes.push({ type: \"strong\", children: parseInline(strong.tokens) });\n break;\n }\n\n case \"em\": {\n const em = token as Tokens.Em;\n nodes.push({ type: \"emphasis\", children: parseInline(em.tokens) });\n break;\n }\n\n case \"link\": {\n const link = token as Tokens.Link;\n nodes.push({ type: \"link\", text: link.text, href: link.href });\n break;\n }\n\n case \"image\": {\n const image = token as Tokens.Image;\n nodes.push({ type: \"image\", href: image.href });\n break;\n }\n\n case \"codespan\": {\n const codespan = token as Tokens.Codespan;\n nodes.push({ type: \"text\", text: codespan.text });\n break;\n }\n }\n }\n\n return nodes;\n}\n","import { PDFDocument } from \"pdf-lib\";\nimport { parseMarkdown } from \"../parser\";\nimport { createDefaultTheme, type Theme } from \"../theme\";\nimport { renderDocument, type FontSet, type ProfileImage } from \"./render\";\nimport type { PageConfig } from \"../page\";\n\nexport type RenderMarkdownOptions = {\n theme?: Theme;\n page?: PageConfig;\n profile?: ProfileImage;\n fonts?: FontSet;\n};\n\nexport async function renderMarkdownToPdf(\n markdown: string,\n opts: RenderMarkdownOptions = {},\n): Promise<Uint8Array> {\n const doc = await PDFDocument.create();\n const theme = opts.theme ?? createDefaultTheme();\n await renderDocument(doc, parseMarkdown(markdown), {\n theme,\n page: opts.page,\n profile: opts.profile,\n fonts: opts.fonts,\n });\n return doc.save();\n}\n\nexport { renderDocument } from \"./render\";\nexport type { FontSet, ProfileImage } from \"./render\";","import type { Theme } from \"./types\";\n\nexport function createDefaultTheme(): Theme {\n return {\n name: \"ats-simple\",\n text: {\n name: { family: \"Helvetica\", size: 22, weight: \"bold\" },\n section: { family: \"Helvetica\", size: 13, weight: \"bold\" },\n entryTitle: { family: \"Helvetica\", size: 11, weight: \"bold\" },\n body: { family: \"Helvetica\", size: 10, weight: \"normal\" },\n },\n color: {\n ink: { r: 0.1, g: 0.1, b: 0.1 },\n accent: { r: 0.05, g: 0.15, b: 0.4 },\n muted: { r: 0.35, g: 0.35, b: 0.35 },\n },\n spacing: {\n sectionGap: 6,\n entryGap: 6,\n entrySpacing: 8,\n lineHeight: 1.35,\n columnGap: 8,\n headerGap: 8,\n },\n };\n}\n","import { PDFDocument, StandardFonts, rgb, type PDFFont, type PDFPage, type PDFImage } from \"pdf-lib\";\nimport fontkit from \"@pdf-lib/fontkit\";import type { MarkdownBlock, MarkdownDocument, InlineNode } from \"../parser/types\";\nimport { createA4, createFlow, type PageConfig, type Points, type Flow } from \"../page\";\nimport type { FontSpec, Theme, RGB } from \"../theme\";\nimport type { PdfStyle } from \"./fonts\";\nimport { measureWidth, wrapRuns, type Run } from \"./measure\";\nimport { sanitizeText } from \"./sanitize\";\n\nexport type ProfilePosition = \"left\" | \"center\" | \"right\";\n\nexport type ProfileImage = {\n /** PNG or JPEG bytes. */\n bytes: Uint8Array;\n /** Side length of the (square) photo in points. Default 96. */\n size?: number;\n /** Horizontal position within the header. Default \"right\". */\n position?: ProfilePosition;\n};\n\ntype Ctx = {\n doc: PDFDocument;\n flow: Flow;\n page: PageConfig;\n theme: Theme;\n font: PDFFont;\n bold: PDFFont;\n italic: PDFFont;\n profile: { image: PDFImage; size: number; position: ProfilePosition } | null;\n sanitize: boolean;\n};\n\nexport type FontSet = {\n /** Regular weight TTF bytes (e.g. Noto Sans CJK). */\n regular: Uint8Array;\n bold?: Uint8Array;\n italic?: Uint8Array;\n};\n\nexport type RenderOptions = {\n theme?: Theme;\n page?: PageConfig;\n profile?: ProfileImage;\n fonts?: FontSet;\n};\n\n/** Fraction of the em-size where the text baseline sits (Helvetica ascent). */\nconst ASCENT = 0.72;\n\ntype StandardFamily = {\n regular: StandardFonts;\n bold: StandardFonts;\n italic: StandardFonts;\n};\n\nconst STANDARD_FONTS: Record<string, StandardFamily> = {\n helvetica: {\n regular: StandardFonts.Helvetica,\n bold: StandardFonts.HelveticaBold,\n italic: StandardFonts.HelveticaOblique,\n },\n times: {\n regular: StandardFonts.TimesRoman,\n bold: StandardFonts.TimesRomanBold,\n italic: StandardFonts.TimesRomanItalic,\n },\n courier: {\n regular: StandardFonts.Courier,\n bold: StandardFonts.CourierBold,\n italic: StandardFonts.CourierOblique,\n },\n};\n\n/**\n * Map a theme `family` name to a set of bundled standard fonts, falling back\n * to Helvetica for unknown families.\n */\nfunction resolveStandardFamily(family: string | undefined): StandardFamily {\n const key = (family ?? \"helvetica\").toLowerCase();\n return STANDARD_FONTS[key] ?? STANDARD_FONTS.helvetica!;\n}\n\nexport async function renderDocument(doc: PDFDocument, document: MarkdownDocument, opts: RenderOptions = {}): Promise<void> {\n const theme = opts.theme;\n if (!theme) throw new Error(\"renderDocument requires a theme\");\n const page = opts.page ?? createA4({ top: 40, right: 40, bottom: 40, left: 40 });\n const flow = createFlow(page);\n createPage(doc, page);\n\n if (opts.fonts) doc.registerFontkit(fontkit);\n\n const standard = resolveStandardFamily(theme.text.body.family);\n\n const font = opts.fonts?.regular\n ? await doc.embedFont(opts.fonts.regular, { subset: true })\n : await doc.embedFont(standard.regular);\n const bold = opts.fonts?.bold\n ? await doc.embedFont(opts.fonts.bold, { subset: true })\n : opts.fonts?.regular\n ? font\n : await doc.embedFont(standard.bold);\n const italic = opts.fonts?.italic\n ? await doc.embedFont(opts.fonts.italic, { subset: true })\n : opts.fonts?.regular\n ? font\n : await doc.embedFont(standard.italic);\n\n const profile = opts.profile\n ? {\n image: await embedImage(doc, opts.profile.bytes),\n size: opts.profile.size ?? 96,\n position: opts.profile.position ?? \"right\",\n }\n : null;\n\n const sanitize = !opts.fonts?.regular;\n\n const ctx: Ctx = { doc, flow, page, theme, font, bold, italic, profile, sanitize };\n\n renderProfile(ctx);\n\n for (const block of document.blocks) {\n renderBlock(ctx, block);\n }\n}\n\nfunction renderBlock(ctx: Ctx, block: MarkdownBlock): void {\n switch (block.type) {\n case \"heading\":\n renderHeading(ctx, block);\n break;\n case \"paragraph\":\n renderParagraph(ctx, block.children, specToStyle(ctx, ctx.theme.text.body));\n break;\n case \"list\":\n renderList(ctx, block);\n break;\n case \"thematicBreak\":\n renderThematicBreak(ctx);\n break;\n case \"blockquote\":\n renderBlockquote(ctx, block);\n break;\n }\n}\n\nfunction specToStyle(ctx: Ctx, spec: FontSpec): PdfStyle {\n const weight = spec.weight === \"bold\" ? \"bold\" : \"normal\";\n return { font: weight === \"bold\" ? ctx.bold : ctx.font, size: spec.size };\n}\n\nfunction lineHeightFor(ctx: Ctx, size: Points): Points {\n return size * ctx.theme.spacing.lineHeight;\n}\n\n/** Advance the cursor down by `pts`, breaking to a new page when needed. */\nfunction gap(ctx: Ctx, pts: Points): void {\n if (pts <= 0) return;\n if (ctx.flow.needNewPage(pts)) ensurePage(ctx);\n ctx.flow.advance(pts);\n}\n\nfunction renderHeading(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"heading\" }>): void {\n const spacing = ctx.theme.spacing;\n\n // Space above the heading, based on its level.\n if (block.level === 2) gap(ctx, spacing.sectionGap);\n else if (block.level === 3) gap(ctx, spacing.entrySpacing);\n\n const spec =\n block.level === 1 ? ctx.theme.text.name : block.level === 2 ? ctx.theme.text.section : ctx.theme.text.entryTitle;\n const color = block.level === 2 ? ctx.theme.color.accent : ctx.theme.color.ink;\n const style = specToStyle(ctx, spec);\n renderLines(ctx, block.children, style, color);\n\n // Space below the heading before its content.\n if (block.level === 1) gap(ctx, spacing.headerGap);\n else gap(ctx, spacing.entryGap);\n}\n\nfunction renderParagraph(ctx: Ctx, nodes: InlineNode[], spec: PdfStyle): void {\n const spacing = ctx.theme.spacing;\n renderLines(ctx, nodes, spec, ctx.theme.color.ink);\n gap(ctx, spacing.entryGap);\n}\n\nfunction renderProfile(ctx: Ctx): void {\n if (!ctx.profile) return;\n\n const { image, size, position } = ctx.profile;\n const pdfPage = currentPdfPage(ctx);\n const contentWidth = ctx.flow.contentWidth();\n\n let x: number;\n if (position === \"left\") x = marginLeft(ctx);\n else if (position === \"center\") x = marginLeft(ctx) + (contentWidth - size) / 2;\n else x = marginLeft(ctx) + contentWidth - size;\n\n const y = yToPdf(ctx, size);\n pdfPage.drawImage(image, { x, y, width: size, height: size });\n}\n\nfunction renderBlockquote(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"blockquote\" }>): void {\n const spacing = ctx.theme.spacing;\n const spec = specToStyle(ctx, ctx.theme.text.body);\n renderLines(ctx, block.children, spec, ctx.theme.color.muted);\n gap(ctx, spacing.entryGap);\n}\n\nfunction renderLines(ctx: Ctx, nodes: InlineNode[], spec: PdfStyle, color: RGB): void {\n const runs = nodesToRuns(ctx, nodes, spec);\n const maxWidth = ctx.flow.contentWidth();\n const lines = wrapRuns(runs, maxWidth);\n const lineHeight = lineHeightFor(ctx, spec.size);\n\n for (const line of lines) {\n if (ctx.flow.needNewPage(lineHeight)) ensurePage(ctx);\n drawLine(ctx, line, color);\n ctx.flow.advance(lineHeight);\n }\n}\n\nfunction renderList(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"list\" }>): void {\n const spacing = ctx.theme.spacing;\n const spec = specToStyle(ctx, ctx.theme.text.body);\n const lineHeight = lineHeightFor(ctx, spec.size);\n const indent = spec.size * 1.2;\n\n for (const item of block.items) {\n const bullet = block.ordered ? `${block.items.indexOf(item) + 1}. ` : \"• \";\n const runs: Run[] = [{ text: bullet, style: spec }];\n const wrapped = wrapRuns([...runs, ...nodesToRuns(ctx, item.children, spec)], ctx.flow.contentWidth() - indent);\n\n wrapped.forEach((line, i) => {\n const padded = i === 0 ? line : padRuns(line, indent, spec);\n if (ctx.flow.needNewPage(lineHeight)) ensurePage(ctx);\n drawLine(ctx, padded, ctx.theme.color.ink);\n ctx.flow.advance(lineHeight);\n });\n\n if (item.nested) {\n for (const sub of item.nested) {\n renderNestedList(ctx, sub, indent);\n }\n }\n }\n\n gap(ctx, spacing.entryGap);\n}\n\nfunction renderNestedList(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"list\" }>, baseIndent: Points): void {\n const spec = specToStyle(ctx, ctx.theme.text.body);\n const lineHeight = lineHeightFor(ctx, spec.size);\n const indent = baseIndent + spec.size * 1.2;\n\n for (const item of block.items) {\n const bullet = block.ordered ? `${block.items.indexOf(item) + 1}. ` : \"• \";\n const runs: Run[] = [{ text: bullet, style: spec }];\n const wrapped = wrapRuns([...runs, ...nodesToRuns(ctx, item.children, spec)], ctx.flow.contentWidth() - indent);\n\n wrapped.forEach((line, i) => {\n const padded = i === 0 ? padRuns(line, baseIndent, spec) : padRuns(line, indent, spec);\n if (ctx.flow.needNewPage(lineHeight)) ensurePage(ctx);\n drawLine(ctx, padded, ctx.theme.color.ink);\n ctx.flow.advance(lineHeight);\n });\n\n if (item.nested) {\n for (const sub of item.nested) {\n renderNestedList(ctx, sub, indent);\n }\n }\n }\n}\n\nfunction renderThematicBreak(ctx: Ctx): void {\n const spacing = ctx.theme.spacing;\n const y = ctx.flow.cursor.y;\n const h = 0.75;\n if (ctx.flow.needNewPage(spacing.entryGap + h)) ensurePage(ctx);\n const pdfPage = currentPdfPage(ctx);\n pdfPage.drawRectangle({\n x: marginLeft(ctx),\n y: yToPdf(ctx, y + spacing.entryGap + h),\n width: ctx.flow.contentWidth(),\n height: h,\n color: toColor(ctx.theme.color.muted),\n });\n ctx.flow.advance(spacing.entryGap * 2 + h);\n}\n\nfunction nodesToRuns(ctx: Ctx, nodes: InlineNode[], base: PdfStyle): Run[] {\n const runs: Run[] = [];\n const walk = (list: InlineNode[], style: PdfStyle): void => {\n for (const node of list) {\n switch (node.type) {\n case \"text\":\n if (node.text) runs.push({ text: ctx.sanitize ? sanitizeText(node.text) : node.text, style });\n break;\n case \"strong\":\n walk(node.children, { font: ctx.bold, size: style.size });\n break;\n case \"emphasis\":\n walk(node.children, { font: ctx.italic, size: style.size });\n break;\n case \"link\":\n runs.push({ text: ctx.sanitize ? sanitizeText(node.text) : node.text, style });\n break;\n case \"image\":\n break;\n }\n }\n };\n walk(nodes, base);\n return runs;\n}\n\nfunction padRuns(runs: Run[], pad: Points, spec: PdfStyle): Run[] {\n if (pad <= 0) return runs;\n return [{ text: \" \".repeat(Math.max(1, Math.round(pad / (spec.size * 0.5)))), style: spec }, ...runs];\n}\n\nfunction drawLine(ctx: Ctx, line: Run[], color: RGB): void {\n const pdfPage = currentPdfPage(ctx);\n const size = Math.max(...line.map((r) => r.style.size));\n const baseline = ctx.flow.cursor.y + size * ASCENT;\n let x = marginLeft(ctx);\n for (const run of line) {\n pdfPage.drawText(run.text, {\n x,\n y: yToPdf(ctx, baseline),\n size: run.style.size,\n font: run.style.font,\n color: toColor(color),\n });\n x += measureWidth(run);\n }\n}\n\nfunction marginLeft(ctx: Ctx): number {\n return ctx.page.margin.left;\n}\n\n/** Convert flow Y (from content-area top) to pdf-lib Y (from page bottom). */\nfunction yToPdf(ctx: Ctx, y: Points): Points {\n return ctx.page.size.height - ctx.page.margin.top - y;\n}\n\nfunction toColor(c: RGB): ReturnType<typeof rgb> {\n return rgb(c.r, c.g, c.b);\n}\n\nfunction createPage(doc: PDFDocument, page: PageConfig): void {\n doc.addPage([page.size.width, page.size.height]);\n}\n\n/** Embed PNG or JPEG bytes into the document, auto-detecting the format. */\nasync function embedImage(doc: PDFDocument, bytes: Uint8Array): Promise<PDFImage> {\n const isPng = bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47;\n const isJpeg = bytes[0] === 0xff && bytes[1] === 0xd8;\n if (isPng) return doc.embedPng(bytes);\n if (isJpeg) return doc.embedJpg(bytes);\n throw new Error(\"Unsupported image format: only PNG and JPEG are supported.\");\n}\nfunction ensurePage(ctx: Ctx): void {\n ctx.flow.newPage();\n ctx.doc.addPage([ctx.page.size.width, ctx.page.size.height]);\n}\n\nfunction currentPdfPage(ctx: Ctx): PDFPage {\n return ctx.doc.getPage(ctx.flow.pageNumber);\n}\n","export type Points = number;\n\nexport type PageMargins = {\n top: Points;\n right: Points;\n bottom: Points;\n left: Points;\n};\n\nexport type PageSize = {\n width: Points;\n height: Points;\n};\n\nexport type PageConfig = {\n size: PageSize;\n margin: PageMargins;\n};\n\nconst A4 = Object.freeze({ width: 595.28, height: 841.89 });\n\nexport function contentWidth(page: PageConfig): Points {\n return page.size.width - page.margin.left - page.margin.right;\n}\n\nexport function contentHeight(page: PageConfig): Points {\n return page.size.height - page.margin.top - page.margin.bottom;\n}\n\nexport function createA4(margin: Partial<PageMargins> = {}): PageConfig {\n const marginValue: PageMargins = {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n ...margin,\n };\n\n return { size: { ...A4 }, margin: marginValue };\n}\n","import type { PageConfig, Points } from \"./config\";\nimport { contentHeight, contentWidth } from \"./config\";\n\nexport type Cursor = {\n x: Points;\n y: Points;\n};\n\n/**\n * Holds cursor state (relative to the current page's content area, origin at\n * the top-left) and coordinates page-breaks. Does not hardcode any dimension;\n * it asks the page config how much room is left.\n */\nexport type Flow = {\n readonly page: PageConfig;\n readonly cursor: Cursor;\n readonly pageNumber: number;\n contentWidth(): Points;\n contentHeight(): Points;\n isLastPageEmpty(): boolean;\n /** True when advancing `dy` would overflow the current content area. */\n needNewPage(dy: Points): boolean;\n /** Break to the next page, resetting the cursor to the top-left. */\n newPage(): void;\n /** Move the cursor down by `dy`. */\n advance(dy: Points): void;\n};\n\nexport function createFlow(page: PageConfig): Flow {\n const cursor: Cursor = { x: 0, y: 0 };\n let pageNumber = 0;\n\n return {\n get page() {\n return page;\n },\n get cursor() {\n return cursor;\n },\n get pageNumber() {\n return pageNumber;\n },\n contentWidth: () => contentWidth(page),\n contentHeight: () => contentHeight(page),\n\n isLastPageEmpty: () => cursor.y === 0,\n\n needNewPage: (dy) => cursor.y + dy >= contentHeight(page),\n\n newPage: () => {\n pageNumber += 1;\n cursor.x = 0;\n cursor.y = 0;\n },\n\n advance: (dy) => {\n cursor.y += dy;\n },\n };\n}\n","import type { PdfStyle } from \"./fonts\";\n\n/** A styled run of text. */\nexport type Run = {\n text: string;\n style: PdfStyle;\n};\n\nexport function measureWidth(run: Run): number {\n return run.style.font.widthOfTextAtSize(run.text, run.style.size);\n}\n\nexport function measureLine(line: Run[]): number {\n return line.reduce((sum, run) => sum + measureWidth(run), 0);\n}\n\n/**\n * Greedy word-wrap: pack styled runs into lines that fit `maxWidth`.\n * Words are never split; a single word wider than the box wraps onto its own line.\n */\nexport function wrapRuns(runs: Run[], maxWidth: number): Run[][] {\n if (runs.length === 0) return [];\n\n const words: { text: string; style: PdfStyle }[] = [];\n for (const run of runs) {\n const parts = run.text.split(/(\\s+)/);\n for (const part of parts) {\n if (part === \"\" || /^\\s+$/.test(part)) continue;\n words.push({ text: part, style: run.style });\n }\n }\n\n const lines: Run[][] = [];\n let current: Run[] = [];\n let currentWidth = 0;\n\n for (const word of words) {\n const w = measureWidth(word);\n const gapWidth = current.length === 0 ? 0 : measureWidth({ text: \" \", style: word.style });\n\n if (current.length > 0 && currentWidth + gapWidth + w > maxWidth) {\n lines.push(current);\n current = [{ ...word }];\n currentWidth = w;\n continue;\n }\n\n if (current.length > 0) {\n current.push({ text: \" \", style: word.style });\n currentWidth += gapWidth;\n }\n current.push({ ...word });\n currentWidth += w;\n }\n\n if (current.length > 0) lines.push(current);\n return lines;\n}","// Characters representable by the WinAnsi (CP1252) encoding used by the\n// standard PDF fonts (Helvetica, Times, Courier). Anything outside this set\n// would make pdf-lib throw, so we sanitize text before measuring/drawing.\n//\n// WinAnsi covers: ASCII (0x20-0x7E), Latin-1 (0xA0-0xFF), plus a handful of\n// CP1252 specials (curly quotes, dashes, bullet, euro, etc.).\n\nconst CP1252_SPECIALS = new Set<number>([\n 0x20ac, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030,\n 0x0160, 0x2039, 0x0152, 0x017d, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022,\n 0x2013, 0x2014, 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x017e, 0x0178,\n]);\n\n/** Common unsupported punctuation mapped to a WinAnsi-safe equivalent. */\nconst FALLBACKS: Record<number, string> = {\n 0x30fb: \"\\u00b7\", // ・ -> ·\n 0x3001: \",\", // 、-> ,\n 0xff0c: \",\", // ,-> ,\n 0x3002: \".\", // 。-> .\n 0xff1f: \"?\", // ?-> ?\n 0xff01: \"!\", // !-> !\n 0x201c: '\"', // \" -> \"\n 0x201d: '\"', // \" -> \"\n 0x2018: \"'\", // ' -> '\n 0x2019: \"'\", // ' -> '\n};\n\nfunction isWinAnsi(code: number): boolean {\n if (code >= 0x20 && code <= 0x7e) return true;\n if (code >= 0xa0 && code <= 0xff) return true;\n return CP1252_SPECIALS.has(code);\n}\n\n/**\n * Replace characters that cannot be encoded by standard PDF fonts with a safe\n * equivalent, or drop them. This keeps CJK/kana text from crashing the render.\n */\nexport function sanitizeText(text: string): string {\n let out = \"\";\n for (const ch of text) {\n const code = ch.codePointAt(0)!;\n if (isWinAnsi(code)) {\n out += ch;\n continue;\n }\n const fallback = FALLBACKS[code];\n if (fallback) out += fallback;\n // otherwise: drop the character\n }\n return out;\n}\n"],"mappings":"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","names":["readFile","marked","marked","PDFDocument","contentWidth","PDFDocument","readFile"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/generate.ts","../src/validator/index.ts","../src/parser/index.ts","../src/renderer/index.ts","../src/theme/default.ts","../src/renderer/render.ts","../src/page/config.ts","../src/page/flow.ts","../src/renderer/measure.ts","../src/renderer/sanitize.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { existsSync, realpathSync } from \"node:fs\";\nimport { extname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { generateCV } from \"./generate\";\nimport type { FontSet, ProfilePosition } from \"./renderer/render\";\n\nexport type CliOptions = {\n input?: string;\n output?: string;\n profile?: string;\n profileSize?: number;\n profilePosition?: ProfilePosition;\n fontRegular?: string;\n fontBold?: string;\n fontItalic?: string;\n help: boolean;\n version: boolean;\n};\n\nconst HELP = `md-2-ats - render an ATS-friendly PDF from a Markdown resume\n\nUsage:\n md-2-ats <input.md> [options]\n\nOptions:\n -o, --output <path> Output PDF path (default: input with .pdf)\n -p, --profile <path> Profile photo (PNG or JPEG)\n --profile-size <points> Profile photo side length (default: 96)\n --profile-position <pos> left | center | right (default: right)\n --font-regular <path> Custom TTF regular font\n --font-bold <path> Custom TTF bold font\n --font-italic <path> Custom TTF italic font\n -h, --help Show this help\n -v, --version Show version\n\nExamples:\n md-2-ats cv.md\n md-2-ats cv.md -o out.pdf -p photo.png\n md-2-ats cv.md --font-regular NotoSansSC-Regular.ttf`;\n\nexport function parseArgs(argv: string[]): CliOptions {\n const opts: CliOptions = { help: false, version: false };\n\n const value = (flag: string, i: number): string => {\n const v = argv[i + 1];\n if (v === undefined || v.startsWith(\"-\")) {\n throw new Error(`Missing value for ${flag}`);\n }\n return v;\n };\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]!;\n\n switch (arg) {\n case \"-h\":\n case \"--help\":\n opts.help = true;\n break;\n case \"-v\":\n case \"--version\":\n opts.version = true;\n break;\n case \"-o\":\n case \"--output\":\n opts.output = value(arg, i);\n i++;\n break;\n case \"-p\":\n case \"--profile\":\n opts.profile = value(arg, i);\n i++;\n break;\n case \"--profile-size\": {\n const raw = value(arg, i);\n i++;\n const n = Number(raw);\n if (!Number.isFinite(n) || n <= 0) throw new Error(`Invalid --profile-size: \"${raw}\"`);\n opts.profileSize = n;\n break;\n }\n case \"--profile-position\": {\n const raw = value(arg, i);\n i++;\n if (raw !== \"left\" && raw !== \"center\" && raw !== \"right\") {\n throw new Error(`Invalid --profile-position: \"${raw}\" (use left, center, or right)`);\n }\n opts.profilePosition = raw;\n break;\n }\n case \"--font-regular\":\n opts.fontRegular = value(arg, i);\n i++;\n break;\n case \"--font-bold\":\n opts.fontBold = value(arg, i);\n i++;\n break;\n case \"--font-italic\":\n opts.fontItalic = value(arg, i);\n i++;\n break;\n default:\n if (arg.startsWith(\"-\")) throw new Error(`Unknown option: ${arg}`);\n if (opts.input !== undefined) throw new Error(`Unexpected argument: ${arg}`);\n opts.input = arg;\n }\n }\n\n return opts;\n}\n\nasync function readVersion(): Promise<string> {\n try {\n const url = new URL(\"../package.json\", import.meta.url);\n const pkg = JSON.parse(await readFile(url, \"utf8\")) as { version?: string };\n return pkg.version ?? \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\nexport async function runCli(argv: string[]): Promise<number> {\n let opts: CliOptions;\n try {\n opts = parseArgs(argv);\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n console.error(`Run \"md-2-ats --help\" for usage.`);\n return 1;\n }\n\n if (opts.help) {\n console.log(HELP);\n return 0;\n }\n\n if (opts.version) {\n console.log(await readVersion());\n return 0;\n }\n\n if (!opts.input) {\n console.error(\"Error: missing input Markdown file.\");\n console.error(`Run \"md-2-ats --help\" for usage.`);\n return 1;\n }\n\n if (!existsSync(opts.input)) {\n console.error(`Error: input file not found: ${opts.input}`);\n return 1;\n }\n\n const output = opts.output ?? `${opts.input.slice(0, opts.input.length - extname(opts.input).length)}.pdf`;\n\n if ((opts.fontBold || opts.fontItalic) && !opts.fontRegular) {\n console.error(\"Error: --font-regular is required when specifying custom fonts.\");\n return 1;\n }\n\n try {\n const profile = opts.profile\n ? {\n bytes: await readFile(opts.profile),\n ...(opts.profileSize !== undefined ? { size: opts.profileSize } : {}),\n ...(opts.profilePosition !== undefined ? { position: opts.profilePosition } : {}),\n }\n : undefined;\n\n const fonts: FontSet | undefined = opts.fontRegular\n ? {\n regular: await readFile(opts.fontRegular),\n ...(opts.fontBold ? { bold: await readFile(opts.fontBold) } : {}),\n ...(opts.fontItalic ? { italic: await readFile(opts.fontItalic) } : {}),\n }\n : undefined;\n\n const result = await generateCV({\n input: opts.input,\n output,\n ...(profile ? { profile } : {}),\n ...(fonts ? { fonts } : {}),\n });\n\n for (const issue of result.issues) {\n console.warn(`warning: ${issue.message}`);\n }\n console.log(`Generated ${resolve(result.output)}`);\n return 0;\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n return 1;\n }\n}\n\nconst entry = process.argv[1];\nconst isMain =\n entry !== undefined &&\n existsSync(entry) &&\n realpathSync(fileURLToPath(import.meta.url)) === realpathSync(entry);\n\nif (isMain) {\n void runCli(process.argv.slice(2)).then((code) => {\n process.exitCode = code;\n });\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { validateCV, type Issue } from \"./validator\";\nimport { renderMarkdownToPdf, type FontSet, type ProfileImage } from \"./renderer\";\nimport { createDefaultTheme, type Theme } from \"./theme\";\nimport type { PageConfig } from \"./page\";\n\nexport type GenerateCVOptions = {\n /** Path to the source Markdown file. */\n input: string;\n /** Path where the generated PDF will be written. */\n output: string;\n /** Profile photo to render in the header. */\n profile?: ProfileImage;\n /** Custom TTF fonts for non-Latin text (e.g. Noto Sans CJK). */\n fonts?: FontSet;\n theme?: Theme;\n page?: PageConfig;\n};\n\nexport type GenerateCVResult = {\n /** All validation issues found (warnings only; errors throw). */\n issues: Issue[];\n /** Path the PDF was written to. */\n output: string;\n};\n\nexport async function generateCV(options: GenerateCVOptions): Promise<GenerateCVResult> {\n const markdown = await readFile(options.input, \"utf8\");\n\n const validation = validateCV(markdown);\n const errors = validation.issues.filter((i) => i.severity === \"error\");\n\n if (errors.length > 0) {\n const summary = errors.map((e) => `- ${e.message}`).join(\"\\n\");\n throw new Error(`CV validation failed:\\n${summary}`);\n }\n\n const theme = options.theme ?? createDefaultTheme();\n const bytes = await renderMarkdownToPdf(markdown, {\n theme,\n page: options.page,\n profile: options.profile,\n fonts: options.fonts,\n });\n\n await writeFile(options.output, bytes);\n\n return {\n issues: validation.issues,\n output: options.output,\n };\n}\n","import { marked } from \"marked\";\nimport { parseMarkdown } from \"../parser\";\nimport type {\n HeadingBlock,\n InlineNode,\n MarkdownBlock,\n MarkdownDocument,\n} from \"../parser/types\";\n\nexport type Severity = \"error\" | \"warning\";\n\nexport type Issue = {\n severity: Severity;\n blockIndex?: number;\n message: string;\n};\n\nexport type ValidationResult = {\n issues: Issue[];\n valid: boolean;\n};\n\nconst EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$/i;\nconst RAW_EMAIL_REGEX = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/i;\n\nexport function validateCV(markdown: string): ValidationResult {\n const doc = parseMarkdown(markdown);\n\n if (doc.blocks.length === 0) {\n return { issues: [{ severity: \"error\", message: \"Document is empty.\" }], valid: false };\n }\n\n const issues: Issue[] = [];\n\n const hasName = doc.blocks.some((b) => b.type === \"heading\" && b.level === 1);\n if (!hasName) {\n issues.push({\n severity: \"error\",\n message: 'Missing a level-1 heading. Add \"# Your Name\" for the candidate name.',\n });\n }\n\n validateContact(doc, issues);\n validateHierarchy(doc, issues);\n validateSection(doc, \"experience\", issues);\n validateSection(doc, \"education\", issues);\n detectUnsupported(markdown, issues);\n\n return { issues, valid: issues.every((i) => i.severity !== \"error\") };\n}\n\nfunction validateContact(doc: MarkdownDocument, issues: Issue[]): void {\n const texts = flattenText(doc);\n const links = flattenLinks(doc);\n\n const emails = new Set<string>();\n for (const t of texts) {\n const m = t.match(RAW_EMAIL_REGEX);\n if (m) emails.add(m[0]);\n }\n for (const href of links) {\n if (href.startsWith(\"mailto:\")) emails.add(href.slice(\"mailto:\".length).split(\"?\")[0] ?? \"\");\n }\n\n const validEmails = [...emails].filter((e) => EMAIL_REGEX.test(e));\n const hasContactLink = links.some((href) => {\n const lower = href.toLowerCase();\n return /mailto:|github\\.com|linkedin\\.com|twitter\\.com|x\\.com|dev\\.to|t\\.me/.test(lower);\n });\n\n if (validEmails.length === 0 && !hasContactLink) {\n issues.push({\n severity: \"warning\",\n message: 'No contact info found. Add an email or a contact link (e.g. mailto:, github, linkedin).',\n });\n }\n\n if ([...emails].some((e) => !EMAIL_REGEX.test(e))) {\n issues.push({\n severity: \"warning\",\n message: `Invalid email address found: \"${[...emails].filter((e) => !EMAIL_REGEX.test(e)).join(\", \")}\".`,\n });\n }\n}\n\nfunction validateHierarchy(doc: MarkdownDocument, issues: Issue[]): void {\n let prevLevel = 0;\n\n doc.blocks.forEach((block, index) => {\n if (block.type !== \"heading\") return;\n if (prevLevel !== 0 && block.level > prevLevel + 1) {\n issues.push({\n severity: \"warning\",\n blockIndex: index,\n message: `Heading level skipped from H${prevLevel} to H${block.level}. Prefer H1 → H2 → H3 order.`,\n });\n }\n prevLevel = block.level;\n });\n}\n\nfunction validateSection(doc: MarkdownDocument, sectionName: string, issues: Issue[]): void {\n const headings: { block: HeadingBlock; index: number }[] = [];\n doc.blocks.forEach((block, index) => {\n if (block.type === \"heading\") headings.push({ block, index });\n });\n\n const target = headings.find(\n ({ block }) => block.level === 2 && headingText(block).toLowerCase() === sectionName,\n );\n\n if (!target) {\n issues.push({\n severity: \"warning\",\n message: `Section \"${sectionName}\" not found. Add \"## ${sectionName}\" heading.`,\n });\n return;\n }\n\n const body: { block: MarkdownBlock; index: number }[] = [];\n const nextSection = doc.blocks\n .slice(target.index + 1)\n .findIndex((b) => b.type === \"heading\" && b.level === 2);\n const end = nextSection === -1 ? doc.blocks.length : target.index + 1 + nextSection;\n\n for (let k = target.index + 1; k < end; k++) {\n const block = doc.blocks[k];\n if (block) body.push({ block, index: k });\n }\n\n const hasBody = body.some(({ block }) => block.type !== \"heading\" && block.type !== \"thematicBreak\");\n if (!hasBody) {\n issues.push({\n severity: \"warning\",\n blockIndex: target.index,\n message: `Section \"${sectionName}\" has no content below its heading.`,\n });\n return;\n }\n\n const entries = body.filter(\n (s): s is { block: HeadingBlock; index: number } =>\n s.block.type === \"heading\" && s.block.level === 3,\n );\n\n entries.forEach(({ block, index }) => {\n const hasContent = body\n .filter((s) => s.index > index)\n .some(({ block: b }) => b.type !== \"heading\" && b.type !== \"thematicBreak\");\n if (!hasContent) {\n issues.push({\n severity: \"warning\",\n blockIndex: index,\n message: `Entry \"${headingText(block)}\" in \"${sectionName}\" has no content below it.`,\n });\n }\n });\n}\n\nfunction detectUnsupported(markdown: string, issues: Issue[]): void {\n const supported = new Set([\"space\", \"heading\", \"paragraph\", \"list\", \"hr\", \"blockquote\"]);\n\n for (const token of marked.lexer(markdown)) {\n if (!supported.has(token.type)) {\n issues.push({\n severity: \"warning\",\n message: `Unsupported Markdown ignored: \"${token.type}\".`,\n });\n }\n }\n}\n\nfunction headingText(block: HeadingBlock): string {\n return inlineText(block.children);\n}\n\nfunction inlineText(nodes: InlineNode[]): string {\n let out = \"\";\n for (const node of nodes) {\n switch (node.type) {\n case \"text\":\n out += node.text;\n break;\n case \"strong\":\n case \"emphasis\":\n out += inlineText(node.children);\n break;\n case \"link\":\n out += node.text;\n break;\n case \"image\":\n break;\n }\n }\n return out;\n}\n\nfunction flattenText(doc: MarkdownDocument): string[] {\n const out: string[] = [];\n for (const block of doc.blocks) {\n if (block.type === \"paragraph\") out.push(inlineText(block.children));\n else if (block.type === \"heading\") out.push(inlineText(block.children));\n }\n return out;\n}\n\nfunction flattenLinks(doc: MarkdownDocument): string[] {\n const out: string[] = [];\n const walk = (nodes: InlineNode[]): void => {\n for (const node of nodes) {\n if (node.type === \"link\") out.push(node.href);\n else if (node.type === \"strong\" || node.type === \"emphasis\") walk(node.children);\n }\n };\n for (const block of doc.blocks) {\n if (block.type === \"paragraph\") walk(block.children);\n else if (block.type === \"heading\") walk(block.children);\n }\n return out;\n}\n","import { marked, type Tokens, type Token } from \"marked\";\nimport type {\n InlineNode,\n ListBlock,\n ListItem,\n MarkdownBlock,\n MarkdownDocument,\n} from \"./types\";\n\nexport function parseMarkdown(markdown: string): MarkdownDocument {\n const tokens = marked.lexer(markdown);\n\n const blocks: MarkdownBlock[] = [];\n\n for (const token of tokens) {\n const block = parseBlock(token);\n\n if (block) {\n blocks.push(block);\n }\n }\n\n return { blocks };\n}\n\nfunction parseBlock(token: Token): MarkdownBlock | null {\n switch (token.type) {\n case \"heading\": {\n const heading = token as Tokens.Heading;\n const level = clampHeadingLevel(heading.depth);\n return { type: \"heading\", level, children: parseInline(heading.tokens) };\n }\n\n case \"paragraph\":\n return { type: \"paragraph\", children: parseInline(token.tokens ?? []) };\n\n case \"list\":\n return parseList(token as Tokens.List);\n\n case \"hr\":\n return { type: \"thematicBreak\" };\n\n case \"blockquote\":\n return parseBlockquote(token as Tokens.Blockquote);\n\n case \"space\":\n return null;\n\n default:\n return null;\n }\n}\n\nfunction parseBlockquote(token: Tokens.Blockquote): MarkdownBlock {\n const children: InlineNode[] = [];\n\n for (const block of token.tokens ?? []) {\n if (block.type === \"paragraph\") {\n if (children.length > 0) {\n children.push({ type: \"text\", text: \" \" });\n }\n children.push(...parseInline((block as Tokens.Paragraph).tokens ?? []));\n }\n }\n\n return { type: \"blockquote\", children };\n}\n\nfunction parseList(token: Tokens.List): ListBlock {\n return {\n type: \"list\",\n ordered: token.ordered,\n items: token.items.map(parseListItem),\n };\n}\n\nfunction parseListItem(item: Tokens.ListItem): ListItem {\n const children: InlineNode[] = [];\n const nested: ListBlock[] = [];\n\n for (const block of item.tokens ?? []) {\n if (block.type === \"list\") {\n nested.push(parseList(block as Tokens.List));\n } else if (\"tokens\" in block && block.tokens) {\n children.push(...parseInline(block.tokens));\n }\n }\n\n return { children, ...(nested.length > 0 ? { nested } : {}) };\n}\n\nfunction clampHeadingLevel(depth: number): 1 | 2 | 3 {\n return Math.min(Math.max(depth, 1), 3) as 1 | 2 | 3;\n}\n\nfunction parseInline(tokens: Token[]): InlineNode[] {\n const nodes: InlineNode[] = [];\n\n for (const token of tokens) {\n switch (token.type) {\n case \"text\": {\n const text = token as Tokens.Text;\n nodes.push({ type: \"text\", text: text.text });\n break;\n }\n\n case \"strong\": {\n const strong = token as Tokens.Strong;\n nodes.push({ type: \"strong\", children: parseInline(strong.tokens) });\n break;\n }\n\n case \"em\": {\n const em = token as Tokens.Em;\n nodes.push({ type: \"emphasis\", children: parseInline(em.tokens) });\n break;\n }\n\n case \"link\": {\n const link = token as Tokens.Link;\n nodes.push({ type: \"link\", text: link.text, href: link.href });\n break;\n }\n\n case \"image\": {\n const image = token as Tokens.Image;\n nodes.push({ type: \"image\", href: image.href });\n break;\n }\n\n case \"codespan\": {\n const codespan = token as Tokens.Codespan;\n nodes.push({ type: \"text\", text: codespan.text });\n break;\n }\n }\n }\n\n return nodes;\n}\n","import { PDFDocument } from \"pdf-lib\";\nimport { parseMarkdown } from \"../parser\";\nimport { createDefaultTheme, type Theme } from \"../theme\";\nimport { renderDocument, type FontSet, type ProfileImage } from \"./render\";\nimport type { PageConfig } from \"../page\";\n\nexport type RenderMarkdownOptions = {\n theme?: Theme;\n page?: PageConfig;\n profile?: ProfileImage;\n fonts?: FontSet;\n};\n\nexport async function renderMarkdownToPdf(\n markdown: string,\n opts: RenderMarkdownOptions = {},\n): Promise<Uint8Array> {\n const doc = await PDFDocument.create();\n const theme = opts.theme ?? createDefaultTheme();\n await renderDocument(doc, parseMarkdown(markdown), {\n theme,\n page: opts.page,\n profile: opts.profile,\n fonts: opts.fonts,\n });\n return doc.save();\n}\n\nexport { renderDocument } from \"./render\";\nexport type { FontSet, ProfileImage } from \"./render\";","import type { Theme } from \"./types\";\n\nexport function createDefaultTheme(): Theme {\n return {\n name: \"ats-simple\",\n text: {\n name: { family: \"Helvetica\", size: 22, weight: \"bold\" },\n section: { family: \"Helvetica\", size: 13, weight: \"bold\" },\n entryTitle: { family: \"Helvetica\", size: 11, weight: \"bold\" },\n body: { family: \"Helvetica\", size: 10, weight: \"normal\" },\n },\n color: {\n ink: { r: 0.1, g: 0.1, b: 0.1 },\n accent: { r: 0.05, g: 0.15, b: 0.4 },\n muted: { r: 0.35, g: 0.35, b: 0.35 },\n },\n spacing: {\n sectionGap: 6,\n entryGap: 6,\n entrySpacing: 8,\n lineHeight: 1.35,\n columnGap: 8,\n headerGap: 8,\n },\n };\n}\n","import { PDFDocument, PDFString, StandardFonts, rgb, type PDFFont, type PDFPage, type PDFImage } from \"pdf-lib\";\nimport fontkit from \"@pdf-lib/fontkit\";import type { MarkdownBlock, MarkdownDocument, InlineNode } from \"../parser/types\";\nimport { createA4, createFlow, type PageConfig, type Points, type Flow } from \"../page\";\nimport type { FontSpec, Theme, RGB } from \"../theme\";\nimport type { PdfStyle } from \"./fonts\";\nimport { measureWidth, wrapRuns, type Run } from \"./measure\";\nimport { sanitizeText } from \"./sanitize\";\n\nexport type ProfilePosition = \"left\" | \"center\" | \"right\";\n\nexport type ProfileImage = {\n /** PNG or JPEG bytes. */\n bytes: Uint8Array;\n /** Side length of the (square) photo in points. Default 96. */\n size?: number;\n /** Horizontal position within the header. Default \"right\". */\n position?: ProfilePosition;\n};\n\ntype Ctx = {\n doc: PDFDocument;\n flow: Flow;\n page: PageConfig;\n theme: Theme;\n font: PDFFont;\n bold: PDFFont;\n italic: PDFFont;\n profile: { image: PDFImage; size: number; position: ProfilePosition } | null;\n sanitize: boolean;\n};\n\nexport type FontSet = {\n /** Regular weight TTF bytes (e.g. Noto Sans CJK). */\n regular: Uint8Array;\n bold?: Uint8Array;\n italic?: Uint8Array;\n};\n\nexport type RenderOptions = {\n theme?: Theme;\n page?: PageConfig;\n profile?: ProfileImage;\n fonts?: FontSet;\n};\n\n/** Fraction of the em-size where the text baseline sits (Helvetica ascent). */\nconst ASCENT = 0.72;\n\ntype StandardFamily = {\n regular: StandardFonts;\n bold: StandardFonts;\n italic: StandardFonts;\n};\n\nconst STANDARD_FONTS: Record<string, StandardFamily> = {\n helvetica: {\n regular: StandardFonts.Helvetica,\n bold: StandardFonts.HelveticaBold,\n italic: StandardFonts.HelveticaOblique,\n },\n times: {\n regular: StandardFonts.TimesRoman,\n bold: StandardFonts.TimesRomanBold,\n italic: StandardFonts.TimesRomanItalic,\n },\n courier: {\n regular: StandardFonts.Courier,\n bold: StandardFonts.CourierBold,\n italic: StandardFonts.CourierOblique,\n },\n};\n\n/**\n * Map a theme `family` name to a set of bundled standard fonts, falling back\n * to Helvetica for unknown families.\n */\nfunction resolveStandardFamily(family: string | undefined): StandardFamily {\n const key = (family ?? \"helvetica\").toLowerCase();\n return STANDARD_FONTS[key] ?? STANDARD_FONTS.helvetica!;\n}\n\nexport async function renderDocument(doc: PDFDocument, document: MarkdownDocument, opts: RenderOptions = {}): Promise<void> {\n const theme = opts.theme;\n if (!theme) throw new Error(\"renderDocument requires a theme\");\n const page = opts.page ?? createA4({ top: 40, right: 40, bottom: 40, left: 40 });\n const flow = createFlow(page);\n createPage(doc, page);\n\n if (opts.fonts) doc.registerFontkit(fontkit);\n\n const standard = resolveStandardFamily(theme.text.body.family);\n\n const font = opts.fonts?.regular\n ? await doc.embedFont(opts.fonts.regular, { subset: true })\n : await doc.embedFont(standard.regular);\n const bold = opts.fonts?.bold\n ? await doc.embedFont(opts.fonts.bold, { subset: true })\n : opts.fonts?.regular\n ? font\n : await doc.embedFont(standard.bold);\n const italic = opts.fonts?.italic\n ? await doc.embedFont(opts.fonts.italic, { subset: true })\n : opts.fonts?.regular\n ? font\n : await doc.embedFont(standard.italic);\n\n const profile = opts.profile\n ? {\n image: await embedImage(doc, opts.profile.bytes),\n size: opts.profile.size ?? 96,\n position: opts.profile.position ?? \"right\",\n }\n : null;\n\n const sanitize = !opts.fonts?.regular;\n\n const ctx: Ctx = { doc, flow, page, theme, font, bold, italic, profile, sanitize };\n\n renderProfile(ctx);\n\n for (const block of document.blocks) {\n renderBlock(ctx, block);\n }\n}\n\nfunction renderBlock(ctx: Ctx, block: MarkdownBlock): void {\n switch (block.type) {\n case \"heading\":\n renderHeading(ctx, block);\n break;\n case \"paragraph\":\n renderParagraph(ctx, block.children, specToStyle(ctx, ctx.theme.text.body));\n break;\n case \"list\":\n renderList(ctx, block);\n break;\n case \"thematicBreak\":\n renderThematicBreak(ctx);\n break;\n case \"blockquote\":\n renderBlockquote(ctx, block);\n break;\n }\n}\n\nfunction specToStyle(ctx: Ctx, spec: FontSpec): PdfStyle {\n const weight = spec.weight === \"bold\" ? \"bold\" : \"normal\";\n return { font: weight === \"bold\" ? ctx.bold : ctx.font, size: spec.size };\n}\n\nfunction lineHeightFor(ctx: Ctx, size: Points): Points {\n return size * ctx.theme.spacing.lineHeight;\n}\n\n/** Advance the cursor down by `pts`, breaking to a new page when needed. */\nfunction gap(ctx: Ctx, pts: Points): void {\n if (pts <= 0) return;\n if (ctx.flow.needNewPage(pts)) ensurePage(ctx);\n ctx.flow.advance(pts);\n}\n\nfunction renderHeading(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"heading\" }>): void {\n const spacing = ctx.theme.spacing;\n\n // Space above the heading, based on its level.\n if (block.level === 2) gap(ctx, spacing.sectionGap);\n else if (block.level === 3) gap(ctx, spacing.entrySpacing);\n\n const spec =\n block.level === 1 ? ctx.theme.text.name : block.level === 2 ? ctx.theme.text.section : ctx.theme.text.entryTitle;\n const color = block.level === 2 ? ctx.theme.color.accent : ctx.theme.color.ink;\n const style = specToStyle(ctx, spec);\n renderLines(ctx, block.children, style, color);\n\n // Space below the heading before its content.\n if (block.level === 1) gap(ctx, spacing.headerGap);\n else gap(ctx, spacing.entryGap);\n}\n\nfunction renderParagraph(ctx: Ctx, nodes: InlineNode[], spec: PdfStyle): void {\n const spacing = ctx.theme.spacing;\n renderLines(ctx, nodes, spec, ctx.theme.color.ink);\n gap(ctx, spacing.entryGap);\n}\n\nfunction renderProfile(ctx: Ctx): void {\n if (!ctx.profile) return;\n\n const { image, size, position } = ctx.profile;\n const pdfPage = currentPdfPage(ctx);\n const contentWidth = ctx.flow.contentWidth();\n\n let x: number;\n if (position === \"left\") x = marginLeft(ctx);\n else if (position === \"center\") x = marginLeft(ctx) + (contentWidth - size) / 2;\n else x = marginLeft(ctx) + contentWidth - size;\n\n const y = yToPdf(ctx, size);\n pdfPage.drawImage(image, { x, y, width: size, height: size });\n}\n\nfunction renderBlockquote(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"blockquote\" }>): void {\n const spacing = ctx.theme.spacing;\n const spec = specToStyle(ctx, ctx.theme.text.body);\n renderLines(ctx, block.children, spec, ctx.theme.color.muted);\n gap(ctx, spacing.entryGap);\n}\n\nfunction renderLines(ctx: Ctx, nodes: InlineNode[], spec: PdfStyle, color: RGB): void {\n const runs = nodesToRuns(ctx, nodes, spec);\n const maxWidth = ctx.flow.contentWidth();\n const lines = wrapRuns(runs, maxWidth);\n const lineHeight = lineHeightFor(ctx, spec.size);\n\n for (const line of lines) {\n if (ctx.flow.needNewPage(lineHeight)) ensurePage(ctx);\n drawLine(ctx, line, color);\n ctx.flow.advance(lineHeight);\n }\n}\n\nfunction renderList(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"list\" }>): void {\n const spacing = ctx.theme.spacing;\n const spec = specToStyle(ctx, ctx.theme.text.body);\n const lineHeight = lineHeightFor(ctx, spec.size);\n const indent = spec.size * 1.2;\n\n for (const item of block.items) {\n const bullet = block.ordered ? `${block.items.indexOf(item) + 1}. ` : \"• \";\n const runs: Run[] = [{ text: bullet, style: spec }];\n const wrapped = wrapRuns([...runs, ...nodesToRuns(ctx, item.children, spec)], ctx.flow.contentWidth() - indent);\n\n wrapped.forEach((line, i) => {\n const padded = i === 0 ? line : padRuns(line, indent, spec);\n if (ctx.flow.needNewPage(lineHeight)) ensurePage(ctx);\n drawLine(ctx, padded, ctx.theme.color.ink);\n ctx.flow.advance(lineHeight);\n });\n\n if (item.nested) {\n for (const sub of item.nested) {\n renderNestedList(ctx, sub, indent);\n }\n }\n }\n\n gap(ctx, spacing.entryGap);\n}\n\nfunction renderNestedList(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"list\" }>, baseIndent: Points): void {\n const spec = specToStyle(ctx, ctx.theme.text.body);\n const lineHeight = lineHeightFor(ctx, spec.size);\n const indent = baseIndent + spec.size * 1.2;\n\n for (const item of block.items) {\n const bullet = block.ordered ? `${block.items.indexOf(item) + 1}. ` : \"• \";\n const runs: Run[] = [{ text: bullet, style: spec }];\n const wrapped = wrapRuns([...runs, ...nodesToRuns(ctx, item.children, spec)], ctx.flow.contentWidth() - indent);\n\n wrapped.forEach((line, i) => {\n const padded = i === 0 ? padRuns(line, baseIndent, spec) : padRuns(line, indent, spec);\n if (ctx.flow.needNewPage(lineHeight)) ensurePage(ctx);\n drawLine(ctx, padded, ctx.theme.color.ink);\n ctx.flow.advance(lineHeight);\n });\n\n if (item.nested) {\n for (const sub of item.nested) {\n renderNestedList(ctx, sub, indent);\n }\n }\n }\n}\n\nfunction renderThematicBreak(ctx: Ctx): void {\n const spacing = ctx.theme.spacing;\n const y = ctx.flow.cursor.y;\n const h = 0.75;\n if (ctx.flow.needNewPage(spacing.entryGap + h)) ensurePage(ctx);\n const pdfPage = currentPdfPage(ctx);\n pdfPage.drawRectangle({\n x: marginLeft(ctx),\n y: yToPdf(ctx, y + spacing.entryGap + h),\n width: ctx.flow.contentWidth(),\n height: h,\n color: toColor(ctx.theme.color.muted),\n });\n ctx.flow.advance(spacing.entryGap * 2 + h);\n}\n\nfunction nodesToRuns(ctx: Ctx, nodes: InlineNode[], base: PdfStyle): Run[] {\n const runs: Run[] = [];\n const walk = (list: InlineNode[], style: PdfStyle): void => {\n for (const node of list) {\n switch (node.type) {\n case \"text\":\n if (node.text) runs.push({ text: ctx.sanitize ? sanitizeText(node.text) : node.text, style });\n break;\n case \"strong\":\n walk(node.children, { font: ctx.bold, size: style.size });\n break;\n case \"emphasis\":\n walk(node.children, { font: ctx.italic, size: style.size });\n break;\n case \"link\":\n runs.push({ text: ctx.sanitize ? sanitizeText(node.text) : node.text, style, href: node.href });\n break;\n case \"image\":\n break;\n }\n }\n };\n walk(nodes, base);\n return runs;\n}\n\nfunction padRuns(runs: Run[], pad: Points, spec: PdfStyle): Run[] {\n if (pad <= 0) return runs;\n return [{ text: \" \".repeat(Math.max(1, Math.round(pad / (spec.size * 0.5)))), style: spec }, ...runs];\n}\n\nfunction drawLine(ctx: Ctx, line: Run[], color: RGB): void {\n const pdfPage = currentPdfPage(ctx);\n const size = Math.max(...line.map((r) => r.style.size));\n const baseline = ctx.flow.cursor.y + size * ASCENT;\n const baselinePdf = yToPdf(ctx, baseline);\n let x = marginLeft(ctx);\n for (const run of line) {\n const runColor = run.href ? ctx.theme.color.accent : color;\n pdfPage.drawText(run.text, {\n x,\n y: baselinePdf,\n size: run.style.size,\n font: run.style.font,\n color: toColor(runColor),\n });\n const width = measureWidth(run);\n if (run.href && width > 0) {\n addLinkAnnotation(\n ctx,\n pdfPage,\n run.href,\n x,\n baselinePdf - run.style.size * 0.25,\n x + width,\n baselinePdf + run.style.size * 0.85,\n );\n }\n x += width;\n }\n}\n\n/** Attach a clickable URI link annotation over a run's bounding box. */\nfunction addLinkAnnotation(ctx: Ctx, pdfPage: PDFPage, href: string, x1: number, y1: number, x2: number, y2: number): void {\n const context = ctx.doc.context;\n const annot = context.obj({\n Type: \"Annot\",\n Subtype: \"Link\",\n Rect: [x1, y1, x2, y2],\n Border: [0, 0, 0],\n A: { Type: \"Action\", S: \"URI\", URI: PDFString.of(href) },\n });\n pdfPage.node.addAnnot(context.register(annot));\n}\n\nfunction marginLeft(ctx: Ctx): number {\n return ctx.page.margin.left;\n}\n\n/** Convert flow Y (from content-area top) to pdf-lib Y (from page bottom). */\nfunction yToPdf(ctx: Ctx, y: Points): Points {\n return ctx.page.size.height - ctx.page.margin.top - y;\n}\n\nfunction toColor(c: RGB): ReturnType<typeof rgb> {\n return rgb(c.r, c.g, c.b);\n}\n\nfunction createPage(doc: PDFDocument, page: PageConfig): void {\n doc.addPage([page.size.width, page.size.height]);\n}\n\n/** Embed PNG or JPEG bytes into the document, auto-detecting the format. */\nasync function embedImage(doc: PDFDocument, bytes: Uint8Array): Promise<PDFImage> {\n const isPng = bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47;\n const isJpeg = bytes[0] === 0xff && bytes[1] === 0xd8;\n if (isPng) return doc.embedPng(bytes);\n if (isJpeg) return doc.embedJpg(bytes);\n throw new Error(\"Unsupported image format: only PNG and JPEG are supported.\");\n}\nfunction ensurePage(ctx: Ctx): void {\n ctx.flow.newPage();\n ctx.doc.addPage([ctx.page.size.width, ctx.page.size.height]);\n}\n\nfunction currentPdfPage(ctx: Ctx): PDFPage {\n return ctx.doc.getPage(ctx.flow.pageNumber);\n}\n","export type Points = number;\n\nexport type PageMargins = {\n top: Points;\n right: Points;\n bottom: Points;\n left: Points;\n};\n\nexport type PageSize = {\n width: Points;\n height: Points;\n};\n\nexport type PageConfig = {\n size: PageSize;\n margin: PageMargins;\n};\n\nconst A4 = Object.freeze({ width: 595.28, height: 841.89 });\n\nexport function contentWidth(page: PageConfig): Points {\n return page.size.width - page.margin.left - page.margin.right;\n}\n\nexport function contentHeight(page: PageConfig): Points {\n return page.size.height - page.margin.top - page.margin.bottom;\n}\n\nexport function createA4(margin: Partial<PageMargins> = {}): PageConfig {\n const marginValue: PageMargins = {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n ...margin,\n };\n\n return { size: { ...A4 }, margin: marginValue };\n}\n","import type { PageConfig, Points } from \"./config\";\nimport { contentHeight, contentWidth } from \"./config\";\n\nexport type Cursor = {\n x: Points;\n y: Points;\n};\n\n/**\n * Holds cursor state (relative to the current page's content area, origin at\n * the top-left) and coordinates page-breaks. Does not hardcode any dimension;\n * it asks the page config how much room is left.\n */\nexport type Flow = {\n readonly page: PageConfig;\n readonly cursor: Cursor;\n readonly pageNumber: number;\n contentWidth(): Points;\n contentHeight(): Points;\n isLastPageEmpty(): boolean;\n /** True when advancing `dy` would overflow the current content area. */\n needNewPage(dy: Points): boolean;\n /** Break to the next page, resetting the cursor to the top-left. */\n newPage(): void;\n /** Move the cursor down by `dy`. */\n advance(dy: Points): void;\n};\n\nexport function createFlow(page: PageConfig): Flow {\n const cursor: Cursor = { x: 0, y: 0 };\n let pageNumber = 0;\n\n return {\n get page() {\n return page;\n },\n get cursor() {\n return cursor;\n },\n get pageNumber() {\n return pageNumber;\n },\n contentWidth: () => contentWidth(page),\n contentHeight: () => contentHeight(page),\n\n isLastPageEmpty: () => cursor.y === 0,\n\n needNewPage: (dy) => cursor.y + dy >= contentHeight(page),\n\n newPage: () => {\n pageNumber += 1;\n cursor.x = 0;\n cursor.y = 0;\n },\n\n advance: (dy) => {\n cursor.y += dy;\n },\n };\n}\n","import type { PdfStyle } from \"./fonts\";\n\n/** A styled run of text. `href` marks the run as a clickable hyperlink. */\nexport type Run = {\n text: string;\n style: PdfStyle;\n href?: string;\n};\n\nexport function measureWidth(run: Run): number {\n return run.style.font.widthOfTextAtSize(run.text, run.style.size);\n}\n\nexport function measureLine(line: Run[]): number {\n return line.reduce((sum, run) => sum + measureWidth(run), 0);\n}\n\n/**\n * Greedy word-wrap: pack styled runs into lines that fit `maxWidth`.\n * Words are never split; a single word wider than the box wraps onto its own line.\n */\nexport function wrapRuns(runs: Run[], maxWidth: number): Run[][] {\n if (runs.length === 0) return [];\n\n const words: Run[] = [];\n for (const run of runs) {\n const parts = run.text.split(/(\\s+)/);\n for (const part of parts) {\n if (part === \"\" || /^\\s+$/.test(part)) continue;\n words.push({ text: part, style: run.style, href: run.href });\n }\n }\n\n const lines: Run[][] = [];\n let current: Run[] = [];\n let currentWidth = 0;\n\n for (const word of words) {\n const w = measureWidth(word);\n const gapWidth = current.length === 0 ? 0 : measureWidth({ text: \" \", style: word.style });\n\n if (current.length > 0 && currentWidth + gapWidth + w > maxWidth) {\n lines.push(current);\n current = [{ ...word }];\n currentWidth = w;\n continue;\n }\n\n if (current.length > 0) {\n current.push({ text: \" \", style: word.style });\n currentWidth += gapWidth;\n }\n current.push({ ...word });\n currentWidth += w;\n }\n\n if (current.length > 0) lines.push(current);\n return lines;\n}","// Characters representable by the WinAnsi (CP1252) encoding used by the\n// standard PDF fonts (Helvetica, Times, Courier). Anything outside this set\n// would make pdf-lib throw, so we sanitize text before measuring/drawing.\n//\n// WinAnsi covers: ASCII (0x20-0x7E), Latin-1 (0xA0-0xFF), plus a handful of\n// CP1252 specials (curly quotes, dashes, bullet, euro, etc.).\n\nconst CP1252_SPECIALS = new Set<number>([\n 0x20ac, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030,\n 0x0160, 0x2039, 0x0152, 0x017d, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022,\n 0x2013, 0x2014, 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x017e, 0x0178,\n]);\n\n/** Common unsupported punctuation mapped to a WinAnsi-safe equivalent. */\nconst FALLBACKS: Record<number, string> = {\n 0x30fb: \"\\u00b7\", // ・ -> ·\n 0x3001: \",\", // 、-> ,\n 0xff0c: \",\", // ,-> ,\n 0x3002: \".\", // 。-> .\n 0xff1f: \"?\", // ?-> ?\n 0xff01: \"!\", // !-> !\n 0x201c: '\"', // \" -> \"\n 0x201d: '\"', // \" -> \"\n 0x2018: \"'\", // ' -> '\n 0x2019: \"'\", // ' -> '\n};\n\nfunction isWinAnsi(code: number): boolean {\n if (code >= 0x20 && code <= 0x7e) return true;\n if (code >= 0xa0 && code <= 0xff) return true;\n return CP1252_SPECIALS.has(code);\n}\n\n/**\n * Replace characters that cannot be encoded by standard PDF fonts with a safe\n * equivalent, or drop them. This keeps CJK/kana text from crashing the render.\n */\nexport function sanitizeText(text: string): string {\n let out = \"\";\n for (const ch of text) {\n const code = ch.codePointAt(0)!;\n if (isWinAnsi(code)) {\n out += ch;\n continue;\n }\n const fallback = FALLBACKS[code];\n if (fallback) out += fallback;\n // otherwise: drop the character\n }\n return out;\n}\n"],"mappings":";;;AAAA,SAAS,YAAAA,iBAAgB;AACzB,SAAS,YAAY,oBAAoB;AACzC,SAAS,SAAS,eAAe;AACjC,SAAS,qBAAqB;;;ACH9B,SAAS,UAAU,iBAAiB;;;ACApC,SAAS,UAAAC,eAAc;;;ACAvB,SAAS,cAAuC;AASzC,SAAS,cAAc,UAAoC;AAChE,QAAM,SAAS,OAAO,MAAM,QAAQ;AAEpC,QAAM,SAA0B,CAAC;AAEjC,aAAW,SAAS,QAAQ;AAC1B,UAAM,QAAQ,WAAW,KAAK;AAE9B,QAAI,OAAO;AACT,aAAO,KAAK,KAAK;AAAA,IACnB;AAAA,EACF;AAEA,SAAO,EAAE,OAAO;AAClB;AAEA,SAAS,WAAW,OAAoC;AACtD,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK,WAAW;AACd,YAAM,UAAU;AAChB,YAAM,QAAQ,kBAAkB,QAAQ,KAAK;AAC7C,aAAO,EAAE,MAAM,WAAW,OAAO,UAAU,YAAY,QAAQ,MAAM,EAAE;AAAA,IACzE;AAAA,IAEA,KAAK;AACH,aAAO,EAAE,MAAM,aAAa,UAAU,YAAY,MAAM,UAAU,CAAC,CAAC,EAAE;AAAA,IAExE,KAAK;AACH,aAAO,UAAU,KAAoB;AAAA,IAEvC,KAAK;AACH,aAAO,EAAE,MAAM,gBAAgB;AAAA,IAEjC,KAAK;AACH,aAAO,gBAAgB,KAA0B;AAAA,IAEnD,KAAK;AACH,aAAO;AAAA,IAET;AACE,aAAO;AAAA,EACX;AACF;AAEA,SAAS,gBAAgB,OAAyC;AAChE,QAAM,WAAyB,CAAC;AAEhC,aAAW,SAAS,MAAM,UAAU,CAAC,GAAG;AACtC,QAAI,MAAM,SAAS,aAAa;AAC9B,UAAI,SAAS,SAAS,GAAG;AACvB,iBAAS,KAAK,EAAE,MAAM,QAAQ,MAAM,IAAI,CAAC;AAAA,MAC3C;AACA,eAAS,KAAK,GAAG,YAAa,MAA2B,UAAU,CAAC,CAAC,CAAC;AAAA,IACxE;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,cAAc,SAAS;AACxC;AAEA,SAAS,UAAU,OAA+B;AAChD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS,MAAM;AAAA,IACf,OAAO,MAAM,MAAM,IAAI,aAAa;AAAA,EACtC;AACF;AAEA,SAAS,cAAc,MAAiC;AACtD,QAAM,WAAyB,CAAC;AAChC,QAAM,SAAsB,CAAC;AAE7B,aAAW,SAAS,KAAK,UAAU,CAAC,GAAG;AACrC,QAAI,MAAM,SAAS,QAAQ;AACzB,aAAO,KAAK,UAAU,KAAoB,CAAC;AAAA,IAC7C,WAAW,YAAY,SAAS,MAAM,QAAQ;AAC5C,eAAS,KAAK,GAAG,YAAY,MAAM,MAAM,CAAC;AAAA,IAC5C;AAAA,EACF;AAEA,SAAO,EAAE,UAAU,GAAI,OAAO,SAAS,IAAI,EAAE,OAAO,IAAI,CAAC,EAAG;AAC9D;AAEA,SAAS,kBAAkB,OAA0B;AACnD,SAAO,KAAK,IAAI,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC;AACvC;AAEA,SAAS,YAAY,QAA+B;AAClD,QAAM,QAAsB,CAAC;AAE7B,aAAW,SAAS,QAAQ;AAC1B,YAAQ,MAAM,MAAM;AAAA,MAClB,KAAK,QAAQ;AACX,cAAM,OAAO;AACb,cAAM,KAAK,EAAE,MAAM,QAAQ,MAAM,KAAK,KAAK,CAAC;AAC5C;AAAA,MACF;AAAA,MAEA,KAAK,UAAU;AACb,cAAM,SAAS;AACf,cAAM,KAAK,EAAE,MAAM,UAAU,UAAU,YAAY,OAAO,MAAM,EAAE,CAAC;AACnE;AAAA,MACF;AAAA,MAEA,KAAK,MAAM;AACT,cAAM,KAAK;AACX,cAAM,KAAK,EAAE,MAAM,YAAY,UAAU,YAAY,GAAG,MAAM,EAAE,CAAC;AACjE;AAAA,MACF;AAAA,MAEA,KAAK,QAAQ;AACX,cAAM,OAAO;AACb,cAAM,KAAK,EAAE,MAAM,QAAQ,MAAM,KAAK,MAAM,MAAM,KAAK,KAAK,CAAC;AAC7D;AAAA,MACF;AAAA,MAEA,KAAK,SAAS;AACZ,cAAM,QAAQ;AACd,cAAM,KAAK,EAAE,MAAM,SAAS,MAAM,MAAM,KAAK,CAAC;AAC9C;AAAA,MACF;AAAA,MAEA,KAAK,YAAY;AACf,cAAM,WAAW;AACjB,cAAM,KAAK,EAAE,MAAM,QAAQ,MAAM,SAAS,KAAK,CAAC;AAChD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;;;ADrHA,IAAM,cAAc;AACpB,IAAM,kBAAkB;AAEjB,SAAS,WAAW,UAAoC;AAC7D,QAAM,MAAM,cAAc,QAAQ;AAElC,MAAI,IAAI,OAAO,WAAW,GAAG;AAC3B,WAAO,EAAE,QAAQ,CAAC,EAAE,UAAU,SAAS,SAAS,qBAAqB,CAAC,GAAG,OAAO,MAAM;AAAA,EACxF;AAEA,QAAM,SAAkB,CAAC;AAEzB,QAAM,UAAU,IAAI,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,UAAU,CAAC;AAC5E,MAAI,CAAC,SAAS;AACZ,WAAO,KAAK;AAAA,MACV,UAAU;AAAA,MACV,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AAEA,kBAAgB,KAAK,MAAM;AAC3B,oBAAkB,KAAK,MAAM;AAC7B,kBAAgB,KAAK,cAAc,MAAM;AACzC,kBAAgB,KAAK,aAAa,MAAM;AACxC,oBAAkB,UAAU,MAAM;AAElC,SAAO,EAAE,QAAQ,OAAO,OAAO,MAAM,CAAC,MAAM,EAAE,aAAa,OAAO,EAAE;AACtE;AAEA,SAAS,gBAAgB,KAAuB,QAAuB;AACrE,QAAM,QAAQ,YAAY,GAAG;AAC7B,QAAM,QAAQ,aAAa,GAAG;AAE9B,QAAM,SAAS,oBAAI,IAAY;AAC/B,aAAW,KAAK,OAAO;AACrB,UAAM,IAAI,EAAE,MAAM,eAAe;AACjC,QAAI,EAAG,QAAO,IAAI,EAAE,CAAC,CAAC;AAAA,EACxB;AACA,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,WAAW,SAAS,EAAG,QAAO,IAAI,KAAK,MAAM,UAAU,MAAM,EAAE,MAAM,GAAG,EAAE,CAAC,KAAK,EAAE;AAAA,EAC7F;AAEA,QAAM,cAAc,CAAC,GAAG,MAAM,EAAE,OAAO,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC;AACjE,QAAM,iBAAiB,MAAM,KAAK,CAAC,SAAS;AAC1C,UAAM,QAAQ,KAAK,YAAY;AAC/B,WAAO,sEAAsE,KAAK,KAAK;AAAA,EACzF,CAAC;AAED,MAAI,YAAY,WAAW,KAAK,CAAC,gBAAgB;AAC/C,WAAO,KAAK;AAAA,MACV,UAAU;AAAA,MACV,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AAEA,MAAI,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,YAAY,KAAK,CAAC,CAAC,GAAG;AACjD,WAAO,KAAK;AAAA,MACV,UAAU;AAAA,MACV,SAAS,iCAAiC,CAAC,GAAG,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,KAAK,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,IACtG,CAAC;AAAA,EACH;AACF;AAEA,SAAS,kBAAkB,KAAuB,QAAuB;AACvE,MAAI,YAAY;AAEhB,MAAI,OAAO,QAAQ,CAAC,OAAO,UAAU;AACnC,QAAI,MAAM,SAAS,UAAW;AAC9B,QAAI,cAAc,KAAK,MAAM,QAAQ,YAAY,GAAG;AAClD,aAAO,KAAK;AAAA,QACV,UAAU;AAAA,QACV,YAAY;AAAA,QACZ,SAAS,+BAA+B,SAAS,QAAQ,MAAM,KAAK;AAAA,MACtE,CAAC;AAAA,IACH;AACA,gBAAY,MAAM;AAAA,EACpB,CAAC;AACH;AAEA,SAAS,gBAAgB,KAAuB,aAAqB,QAAuB;AAC1F,QAAM,WAAqD,CAAC;AAC5D,MAAI,OAAO,QAAQ,CAAC,OAAO,UAAU;AACnC,QAAI,MAAM,SAAS,UAAW,UAAS,KAAK,EAAE,OAAO,MAAM,CAAC;AAAA,EAC9D,CAAC;AAED,QAAM,SAAS,SAAS;AAAA,IACtB,CAAC,EAAE,MAAM,MAAM,MAAM,UAAU,KAAK,YAAY,KAAK,EAAE,YAAY,MAAM;AAAA,EAC3E;AAEA,MAAI,CAAC,QAAQ;AACX,WAAO,KAAK;AAAA,MACV,UAAU;AAAA,MACV,SAAS,YAAY,WAAW,wBAAwB,WAAW;AAAA,IACrE,CAAC;AACD;AAAA,EACF;AAEA,QAAM,OAAkD,CAAC;AACzD,QAAM,cAAc,IAAI,OACrB,MAAM,OAAO,QAAQ,CAAC,EACtB,UAAU,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,UAAU,CAAC;AACzD,QAAM,MAAM,gBAAgB,KAAK,IAAI,OAAO,SAAS,OAAO,QAAQ,IAAI;AAExE,WAAS,IAAI,OAAO,QAAQ,GAAG,IAAI,KAAK,KAAK;AAC3C,UAAM,QAAQ,IAAI,OAAO,CAAC;AAC1B,QAAI,MAAO,MAAK,KAAK,EAAE,OAAO,OAAO,EAAE,CAAC;AAAA,EAC1C;AAEA,QAAM,UAAU,KAAK,KAAK,CAAC,EAAE,MAAM,MAAM,MAAM,SAAS,aAAa,MAAM,SAAS,eAAe;AACnG,MAAI,CAAC,SAAS;AACZ,WAAO,KAAK;AAAA,MACV,UAAU;AAAA,MACV,YAAY,OAAO;AAAA,MACnB,SAAS,YAAY,WAAW;AAAA,IAClC,CAAC;AACD;AAAA,EACF;AAEA,QAAM,UAAU,KAAK;AAAA,IACnB,CAAC,MACC,EAAE,MAAM,SAAS,aAAa,EAAE,MAAM,UAAU;AAAA,EACpD;AAEA,UAAQ,QAAQ,CAAC,EAAE,OAAO,MAAM,MAAM;AACpC,UAAM,aAAa,KAChB,OAAO,CAAC,MAAM,EAAE,QAAQ,KAAK,EAC7B,KAAK,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,aAAa,EAAE,SAAS,eAAe;AAC5E,QAAI,CAAC,YAAY;AACf,aAAO,KAAK;AAAA,QACV,UAAU;AAAA,QACV,YAAY;AAAA,QACZ,SAAS,UAAU,YAAY,KAAK,CAAC,SAAS,WAAW;AAAA,MAC3D,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACH;AAEA,SAAS,kBAAkB,UAAkB,QAAuB;AAClE,QAAM,YAAY,oBAAI,IAAI,CAAC,SAAS,WAAW,aAAa,QAAQ,MAAM,YAAY,CAAC;AAEvF,aAAW,SAASC,QAAO,MAAM,QAAQ,GAAG;AAC1C,QAAI,CAAC,UAAU,IAAI,MAAM,IAAI,GAAG;AAC9B,aAAO,KAAK;AAAA,QACV,UAAU;AAAA,QACV,SAAS,kCAAkC,MAAM,IAAI;AAAA,MACvD,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,SAAS,YAAY,OAA6B;AAChD,SAAO,WAAW,MAAM,QAAQ;AAClC;AAEA,SAAS,WAAW,OAA6B;AAC/C,MAAI,MAAM;AACV,aAAW,QAAQ,OAAO;AACxB,YAAQ,KAAK,MAAM;AAAA,MACjB,KAAK;AACH,eAAO,KAAK;AACZ;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH,eAAO,WAAW,KAAK,QAAQ;AAC/B;AAAA,MACF,KAAK;AACH,eAAO,KAAK;AACZ;AAAA,MACF,KAAK;AACH;AAAA,IACJ;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,YAAY,KAAiC;AACpD,QAAM,MAAgB,CAAC;AACvB,aAAW,SAAS,IAAI,QAAQ;AAC9B,QAAI,MAAM,SAAS,YAAa,KAAI,KAAK,WAAW,MAAM,QAAQ,CAAC;AAAA,aAC1D,MAAM,SAAS,UAAW,KAAI,KAAK,WAAW,MAAM,QAAQ,CAAC;AAAA,EACxE;AACA,SAAO;AACT;AAEA,SAAS,aAAa,KAAiC;AACrD,QAAM,MAAgB,CAAC;AACvB,QAAM,OAAO,CAAC,UAA8B;AAC1C,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,SAAS,OAAQ,KAAI,KAAK,KAAK,IAAI;AAAA,eACnC,KAAK,SAAS,YAAY,KAAK,SAAS,WAAY,MAAK,KAAK,QAAQ;AAAA,IACjF;AAAA,EACF;AACA,aAAW,SAAS,IAAI,QAAQ;AAC9B,QAAI,MAAM,SAAS,YAAa,MAAK,MAAM,QAAQ;AAAA,aAC1C,MAAM,SAAS,UAAW,MAAK,MAAM,QAAQ;AAAA,EACxD;AACA,SAAO;AACT;;;AE3NA,SAAS,eAAAC,oBAAmB;;;ACErB,SAAS,qBAA4B;AAC1C,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,MAAM,EAAE,QAAQ,aAAa,MAAM,IAAI,QAAQ,OAAO;AAAA,MACtD,SAAS,EAAE,QAAQ,aAAa,MAAM,IAAI,QAAQ,OAAO;AAAA,MACzD,YAAY,EAAE,QAAQ,aAAa,MAAM,IAAI,QAAQ,OAAO;AAAA,MAC5D,MAAM,EAAE,QAAQ,aAAa,MAAM,IAAI,QAAQ,SAAS;AAAA,IAC1D;AAAA,IACA,OAAO;AAAA,MACL,KAAK,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA,MAC9B,QAAQ,EAAE,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI;AAAA,MACnC,OAAO,EAAE,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK;AAAA,IACrC;AAAA,IACA,SAAS;AAAA,MACP,YAAY;AAAA,MACZ,UAAU;AAAA,MACV,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,WAAW;AAAA,IACb;AAAA,EACF;AACF;;;ACzBA,SAAsB,WAAW,eAAe,WAAsD;AACtG,OAAO,aAAa;;;ACkBpB,IAAM,KAAK,OAAO,OAAO,EAAE,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAEnD,SAAS,aAAa,MAA0B;AACrD,SAAO,KAAK,KAAK,QAAQ,KAAK,OAAO,OAAO,KAAK,OAAO;AAC1D;AAEO,SAAS,cAAc,MAA0B;AACtD,SAAO,KAAK,KAAK,SAAS,KAAK,OAAO,MAAM,KAAK,OAAO;AAC1D;AAEO,SAAS,SAAS,SAA+B,CAAC,GAAe;AACtE,QAAM,cAA2B;AAAA,IAC/B,KAAK;AAAA,IACL,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,GAAG;AAAA,EACL;AAEA,SAAO,EAAE,MAAM,EAAE,GAAG,GAAG,GAAG,QAAQ,YAAY;AAChD;;;ACXO,SAAS,WAAW,MAAwB;AACjD,QAAM,SAAiB,EAAE,GAAG,GAAG,GAAG,EAAE;AACpC,MAAI,aAAa;AAEjB,SAAO;AAAA,IACL,IAAI,OAAO;AACT,aAAO;AAAA,IACT;AAAA,IACA,IAAI,SAAS;AACX,aAAO;AAAA,IACT;AAAA,IACA,IAAI,aAAa;AACf,aAAO;AAAA,IACT;AAAA,IACA,cAAc,MAAM,aAAa,IAAI;AAAA,IACrC,eAAe,MAAM,cAAc,IAAI;AAAA,IAEvC,iBAAiB,MAAM,OAAO,MAAM;AAAA,IAEpC,aAAa,CAAC,OAAO,OAAO,IAAI,MAAM,cAAc,IAAI;AAAA,IAExD,SAAS,MAAM;AACb,oBAAc;AACd,aAAO,IAAI;AACX,aAAO,IAAI;AAAA,IACb;AAAA,IAEA,SAAS,CAAC,OAAO;AACf,aAAO,KAAK;AAAA,IACd;AAAA,EACF;AACF;;;AClDO,SAAS,aAAa,KAAkB;AAC7C,SAAO,IAAI,MAAM,KAAK,kBAAkB,IAAI,MAAM,IAAI,MAAM,IAAI;AAClE;AAUO,SAAS,SAAS,MAAa,UAA2B;AAC/D,MAAI,KAAK,WAAW,EAAG,QAAO,CAAC;AAE/B,QAAM,QAAe,CAAC;AACtB,aAAW,OAAO,MAAM;AACtB,UAAM,QAAQ,IAAI,KAAK,MAAM,OAAO;AACpC,eAAW,QAAQ,OAAO;AACxB,UAAI,SAAS,MAAM,QAAQ,KAAK,IAAI,EAAG;AACvC,YAAM,KAAK,EAAE,MAAM,MAAM,OAAO,IAAI,OAAO,MAAM,IAAI,KAAK,CAAC;AAAA,IAC7D;AAAA,EACF;AAEA,QAAM,QAAiB,CAAC;AACxB,MAAI,UAAiB,CAAC;AACtB,MAAI,eAAe;AAEnB,aAAW,QAAQ,OAAO;AACxB,UAAM,IAAI,aAAa,IAAI;AAC3B,UAAM,WAAW,QAAQ,WAAW,IAAI,IAAI,aAAa,EAAE,MAAM,KAAK,OAAO,KAAK,MAAM,CAAC;AAEzF,QAAI,QAAQ,SAAS,KAAK,eAAe,WAAW,IAAI,UAAU;AAChE,YAAM,KAAK,OAAO;AAClB,gBAAU,CAAC,EAAE,GAAG,KAAK,CAAC;AACtB,qBAAe;AACf;AAAA,IACF;AAEA,QAAI,QAAQ,SAAS,GAAG;AACtB,cAAQ,KAAK,EAAE,MAAM,KAAK,OAAO,KAAK,MAAM,CAAC;AAC7C,sBAAgB;AAAA,IAClB;AACA,YAAQ,KAAK,EAAE,GAAG,KAAK,CAAC;AACxB,oBAAgB;AAAA,EAClB;AAEA,MAAI,QAAQ,SAAS,EAAG,OAAM,KAAK,OAAO;AAC1C,SAAO;AACT;;;ACnDA,IAAM,kBAAkB,oBAAI,IAAY;AAAA,EACtC;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAChE;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAChE;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAClE,CAAC;AAGD,IAAM,YAAoC;AAAA,EACxC,OAAQ;AAAA;AAAA,EACR,OAAQ;AAAA;AAAA,EACR,OAAQ;AAAA;AAAA,EACR,OAAQ;AAAA;AAAA,EACR,OAAQ;AAAA;AAAA,EACR,OAAQ;AAAA;AAAA,EACR,MAAQ;AAAA;AAAA,EACR,MAAQ;AAAA;AAAA,EACR,MAAQ;AAAA;AAAA,EACR,MAAQ;AAAA;AACV;AAEA,SAAS,UAAU,MAAuB;AACxC,MAAI,QAAQ,MAAQ,QAAQ,IAAM,QAAO;AACzC,MAAI,QAAQ,OAAQ,QAAQ,IAAM,QAAO;AACzC,SAAO,gBAAgB,IAAI,IAAI;AACjC;AAMO,SAAS,aAAa,MAAsB;AACjD,MAAI,MAAM;AACV,aAAW,MAAM,MAAM;AACrB,UAAM,OAAO,GAAG,YAAY,CAAC;AAC7B,QAAI,UAAU,IAAI,GAAG;AACnB,aAAO;AACP;AAAA,IACF;AACA,UAAM,WAAW,UAAU,IAAI;AAC/B,QAAI,SAAU,QAAO;AAAA,EAEvB;AACA,SAAO;AACT;;;AJJA,IAAM,SAAS;AAQf,IAAM,iBAAiD;AAAA,EACrD,WAAW;AAAA,IACT,SAAS,cAAc;AAAA,IACvB,MAAM,cAAc;AAAA,IACpB,QAAQ,cAAc;AAAA,EACxB;AAAA,EACA,OAAO;AAAA,IACL,SAAS,cAAc;AAAA,IACvB,MAAM,cAAc;AAAA,IACpB,QAAQ,cAAc;AAAA,EACxB;AAAA,EACA,SAAS;AAAA,IACP,SAAS,cAAc;AAAA,IACvB,MAAM,cAAc;AAAA,IACpB,QAAQ,cAAc;AAAA,EACxB;AACF;AAMA,SAAS,sBAAsB,QAA4C;AACzE,QAAM,OAAO,UAAU,aAAa,YAAY;AAChD,SAAO,eAAe,GAAG,KAAK,eAAe;AAC/C;AAEA,eAAsB,eAAe,KAAkB,UAA4B,OAAsB,CAAC,GAAkB;AAC1H,QAAM,QAAQ,KAAK;AACnB,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,iCAAiC;AAC7D,QAAM,OAAO,KAAK,QAAQ,SAAS,EAAE,KAAK,IAAI,OAAO,IAAI,QAAQ,IAAI,MAAM,GAAG,CAAC;AAC/E,QAAM,OAAO,WAAW,IAAI;AAC5B,aAAW,KAAK,IAAI;AAEpB,MAAI,KAAK,MAAO,KAAI,gBAAgB,OAAO;AAE3C,QAAM,WAAW,sBAAsB,MAAM,KAAK,KAAK,MAAM;AAE7D,QAAM,OAAO,KAAK,OAAO,UACrB,MAAM,IAAI,UAAU,KAAK,MAAM,SAAS,EAAE,QAAQ,KAAK,CAAC,IACxD,MAAM,IAAI,UAAU,SAAS,OAAO;AACxC,QAAM,OAAO,KAAK,OAAO,OACrB,MAAM,IAAI,UAAU,KAAK,MAAM,MAAM,EAAE,QAAQ,KAAK,CAAC,IACrD,KAAK,OAAO,UACV,OACA,MAAM,IAAI,UAAU,SAAS,IAAI;AACvC,QAAM,SAAS,KAAK,OAAO,SACvB,MAAM,IAAI,UAAU,KAAK,MAAM,QAAQ,EAAE,QAAQ,KAAK,CAAC,IACvD,KAAK,OAAO,UACV,OACA,MAAM,IAAI,UAAU,SAAS,MAAM;AAEzC,QAAM,UAAU,KAAK,UACjB;AAAA,IACE,OAAO,MAAM,WAAW,KAAK,KAAK,QAAQ,KAAK;AAAA,IAC/C,MAAM,KAAK,QAAQ,QAAQ;AAAA,IAC3B,UAAU,KAAK,QAAQ,YAAY;AAAA,EACrC,IACA;AAEJ,QAAM,WAAW,CAAC,KAAK,OAAO;AAE9B,QAAM,MAAW,EAAE,KAAK,MAAM,MAAM,OAAO,MAAM,MAAM,QAAQ,SAAS,SAAS;AAEjF,gBAAc,GAAG;AAEjB,aAAW,SAAS,SAAS,QAAQ;AACnC,gBAAY,KAAK,KAAK;AAAA,EACxB;AACF;AAEA,SAAS,YAAY,KAAU,OAA4B;AACzD,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,oBAAc,KAAK,KAAK;AACxB;AAAA,IACF,KAAK;AACH,sBAAgB,KAAK,MAAM,UAAU,YAAY,KAAK,IAAI,MAAM,KAAK,IAAI,CAAC;AAC1E;AAAA,IACF,KAAK;AACH,iBAAW,KAAK,KAAK;AACrB;AAAA,IACF,KAAK;AACH,0BAAoB,GAAG;AACvB;AAAA,IACF,KAAK;AACH,uBAAiB,KAAK,KAAK;AAC3B;AAAA,EACJ;AACF;AAEA,SAAS,YAAY,KAAU,MAA0B;AACvD,QAAM,SAAS,KAAK,WAAW,SAAS,SAAS;AACjD,SAAO,EAAE,MAAM,WAAW,SAAS,IAAI,OAAO,IAAI,MAAM,MAAM,KAAK,KAAK;AAC1E;AAEA,SAAS,cAAc,KAAU,MAAsB;AACrD,SAAO,OAAO,IAAI,MAAM,QAAQ;AAClC;AAGA,SAAS,IAAI,KAAU,KAAmB;AACxC,MAAI,OAAO,EAAG;AACd,MAAI,IAAI,KAAK,YAAY,GAAG,EAAG,YAAW,GAAG;AAC7C,MAAI,KAAK,QAAQ,GAAG;AACtB;AAEA,SAAS,cAAc,KAAU,OAA0D;AACzF,QAAM,UAAU,IAAI,MAAM;AAG1B,MAAI,MAAM,UAAU,EAAG,KAAI,KAAK,QAAQ,UAAU;AAAA,WACzC,MAAM,UAAU,EAAG,KAAI,KAAK,QAAQ,YAAY;AAEzD,QAAM,OACJ,MAAM,UAAU,IAAI,IAAI,MAAM,KAAK,OAAO,MAAM,UAAU,IAAI,IAAI,MAAM,KAAK,UAAU,IAAI,MAAM,KAAK;AACxG,QAAM,QAAQ,MAAM,UAAU,IAAI,IAAI,MAAM,MAAM,SAAS,IAAI,MAAM,MAAM;AAC3E,QAAM,QAAQ,YAAY,KAAK,IAAI;AACnC,cAAY,KAAK,MAAM,UAAU,OAAO,KAAK;AAG7C,MAAI,MAAM,UAAU,EAAG,KAAI,KAAK,QAAQ,SAAS;AAAA,MAC5C,KAAI,KAAK,QAAQ,QAAQ;AAChC;AAEA,SAAS,gBAAgB,KAAU,OAAqB,MAAsB;AAC5E,QAAM,UAAU,IAAI,MAAM;AAC1B,cAAY,KAAK,OAAO,MAAM,IAAI,MAAM,MAAM,GAAG;AACjD,MAAI,KAAK,QAAQ,QAAQ;AAC3B;AAEA,SAAS,cAAc,KAAgB;AACrC,MAAI,CAAC,IAAI,QAAS;AAElB,QAAM,EAAE,OAAO,MAAM,SAAS,IAAI,IAAI;AACtC,QAAM,UAAU,eAAe,GAAG;AAClC,QAAMC,gBAAe,IAAI,KAAK,aAAa;AAE3C,MAAI;AACJ,MAAI,aAAa,OAAQ,KAAI,WAAW,GAAG;AAAA,WAClC,aAAa,SAAU,KAAI,WAAW,GAAG,KAAKA,gBAAe,QAAQ;AAAA,MACzE,KAAI,WAAW,GAAG,IAAIA,gBAAe;AAE1C,QAAM,IAAI,OAAO,KAAK,IAAI;AAC1B,UAAQ,UAAU,OAAO,EAAE,GAAG,GAAG,OAAO,MAAM,QAAQ,KAAK,CAAC;AAC9D;AAEA,SAAS,iBAAiB,KAAU,OAA6D;AAC/F,QAAM,UAAU,IAAI,MAAM;AAC1B,QAAM,OAAO,YAAY,KAAK,IAAI,MAAM,KAAK,IAAI;AACjD,cAAY,KAAK,MAAM,UAAU,MAAM,IAAI,MAAM,MAAM,KAAK;AAC5D,MAAI,KAAK,QAAQ,QAAQ;AAC3B;AAEA,SAAS,YAAY,KAAU,OAAqB,MAAgB,OAAkB;AACpF,QAAM,OAAO,YAAY,KAAK,OAAO,IAAI;AACzC,QAAM,WAAW,IAAI,KAAK,aAAa;AACvC,QAAM,QAAQ,SAAS,MAAM,QAAQ;AACrC,QAAM,aAAa,cAAc,KAAK,KAAK,IAAI;AAE/C,aAAW,QAAQ,OAAO;AACxB,QAAI,IAAI,KAAK,YAAY,UAAU,EAAG,YAAW,GAAG;AACpD,aAAS,KAAK,MAAM,KAAK;AACzB,QAAI,KAAK,QAAQ,UAAU;AAAA,EAC7B;AACF;AAEA,SAAS,WAAW,KAAU,OAAuD;AACnF,QAAM,UAAU,IAAI,MAAM;AAC1B,QAAM,OAAO,YAAY,KAAK,IAAI,MAAM,KAAK,IAAI;AACjD,QAAM,aAAa,cAAc,KAAK,KAAK,IAAI;AAC/C,QAAM,SAAS,KAAK,OAAO;AAE3B,aAAW,QAAQ,MAAM,OAAO;AAC9B,UAAM,SAAS,MAAM,UAAU,GAAG,MAAM,MAAM,QAAQ,IAAI,IAAI,CAAC,OAAO;AACtE,UAAM,OAAc,CAAC,EAAE,MAAM,QAAQ,OAAO,KAAK,CAAC;AAClD,UAAM,UAAU,SAAS,CAAC,GAAG,MAAM,GAAG,YAAY,KAAK,KAAK,UAAU,IAAI,CAAC,GAAG,IAAI,KAAK,aAAa,IAAI,MAAM;AAE9G,YAAQ,QAAQ,CAAC,MAAM,MAAM;AAC3B,YAAM,SAAS,MAAM,IAAI,OAAO,QAAQ,MAAM,QAAQ,IAAI;AAC1D,UAAI,IAAI,KAAK,YAAY,UAAU,EAAG,YAAW,GAAG;AACpD,eAAS,KAAK,QAAQ,IAAI,MAAM,MAAM,GAAG;AACzC,UAAI,KAAK,QAAQ,UAAU;AAAA,IAC7B,CAAC;AAED,QAAI,KAAK,QAAQ;AACf,iBAAW,OAAO,KAAK,QAAQ;AAC7B,yBAAiB,KAAK,KAAK,MAAM;AAAA,MACnC;AAAA,IACF;AAAA,EACF;AAEA,MAAI,KAAK,QAAQ,QAAQ;AAC3B;AAEA,SAAS,iBAAiB,KAAU,OAAiD,YAA0B;AAC7G,QAAM,OAAO,YAAY,KAAK,IAAI,MAAM,KAAK,IAAI;AACjD,QAAM,aAAa,cAAc,KAAK,KAAK,IAAI;AAC/C,QAAM,SAAS,aAAa,KAAK,OAAO;AAExC,aAAW,QAAQ,MAAM,OAAO;AAC9B,UAAM,SAAS,MAAM,UAAU,GAAG,MAAM,MAAM,QAAQ,IAAI,IAAI,CAAC,OAAO;AACtE,UAAM,OAAc,CAAC,EAAE,MAAM,QAAQ,OAAO,KAAK,CAAC;AAClD,UAAM,UAAU,SAAS,CAAC,GAAG,MAAM,GAAG,YAAY,KAAK,KAAK,UAAU,IAAI,CAAC,GAAG,IAAI,KAAK,aAAa,IAAI,MAAM;AAE9G,YAAQ,QAAQ,CAAC,MAAM,MAAM;AAC3B,YAAM,SAAS,MAAM,IAAI,QAAQ,MAAM,YAAY,IAAI,IAAI,QAAQ,MAAM,QAAQ,IAAI;AACrF,UAAI,IAAI,KAAK,YAAY,UAAU,EAAG,YAAW,GAAG;AACpD,eAAS,KAAK,QAAQ,IAAI,MAAM,MAAM,GAAG;AACzC,UAAI,KAAK,QAAQ,UAAU;AAAA,IAC7B,CAAC;AAED,QAAI,KAAK,QAAQ;AACf,iBAAW,OAAO,KAAK,QAAQ;AAC7B,yBAAiB,KAAK,KAAK,MAAM;AAAA,MACnC;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,oBAAoB,KAAgB;AAC3C,QAAM,UAAU,IAAI,MAAM;AAC1B,QAAM,IAAI,IAAI,KAAK,OAAO;AAC1B,QAAM,IAAI;AACV,MAAI,IAAI,KAAK,YAAY,QAAQ,WAAW,CAAC,EAAG,YAAW,GAAG;AAC9D,QAAM,UAAU,eAAe,GAAG;AAClC,UAAQ,cAAc;AAAA,IACpB,GAAG,WAAW,GAAG;AAAA,IACjB,GAAG,OAAO,KAAK,IAAI,QAAQ,WAAW,CAAC;AAAA,IACvC,OAAO,IAAI,KAAK,aAAa;AAAA,IAC7B,QAAQ;AAAA,IACR,OAAO,QAAQ,IAAI,MAAM,MAAM,KAAK;AAAA,EACtC,CAAC;AACD,MAAI,KAAK,QAAQ,QAAQ,WAAW,IAAI,CAAC;AAC3C;AAEA,SAAS,YAAY,KAAU,OAAqB,MAAuB;AACzE,QAAM,OAAc,CAAC;AACrB,QAAM,OAAO,CAAC,MAAoB,UAA0B;AAC1D,eAAW,QAAQ,MAAM;AACvB,cAAQ,KAAK,MAAM;AAAA,QACjB,KAAK;AACH,cAAI,KAAK,KAAM,MAAK,KAAK,EAAE,MAAM,IAAI,WAAW,aAAa,KAAK,IAAI,IAAI,KAAK,MAAM,MAAM,CAAC;AAC5F;AAAA,QACF,KAAK;AACH,eAAK,KAAK,UAAU,EAAE,MAAM,IAAI,MAAM,MAAM,MAAM,KAAK,CAAC;AACxD;AAAA,QACF,KAAK;AACH,eAAK,KAAK,UAAU,EAAE,MAAM,IAAI,QAAQ,MAAM,MAAM,KAAK,CAAC;AAC1D;AAAA,QACF,KAAK;AACH,eAAK,KAAK,EAAE,MAAM,IAAI,WAAW,aAAa,KAAK,IAAI,IAAI,KAAK,MAAM,OAAO,MAAM,KAAK,KAAK,CAAC;AAC9F;AAAA,QACF,KAAK;AACH;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AACA,OAAK,OAAO,IAAI;AAChB,SAAO;AACT;AAEA,SAAS,QAAQ,MAAa,KAAa,MAAuB;AAChE,MAAI,OAAO,EAAG,QAAO;AACrB,SAAO,CAAC,EAAE,MAAM,IAAI,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,KAAK,OAAO,IAAI,CAAC,CAAC,GAAG,OAAO,KAAK,GAAG,GAAG,IAAI;AACtG;AAEA,SAAS,SAAS,KAAU,MAAa,OAAkB;AACzD,QAAM,UAAU,eAAe,GAAG;AAClC,QAAM,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC;AACtD,QAAM,WAAW,IAAI,KAAK,OAAO,IAAI,OAAO;AAC5C,QAAM,cAAc,OAAO,KAAK,QAAQ;AACxC,MAAI,IAAI,WAAW,GAAG;AACtB,aAAW,OAAO,MAAM;AACtB,UAAM,WAAW,IAAI,OAAO,IAAI,MAAM,MAAM,SAAS;AACrD,YAAQ,SAAS,IAAI,MAAM;AAAA,MACzB;AAAA,MACA,GAAG;AAAA,MACH,MAAM,IAAI,MAAM;AAAA,MAChB,MAAM,IAAI,MAAM;AAAA,MAChB,OAAO,QAAQ,QAAQ;AAAA,IACzB,CAAC;AACD,UAAM,QAAQ,aAAa,GAAG;AAC9B,QAAI,IAAI,QAAQ,QAAQ,GAAG;AACzB;AAAA,QACE;AAAA,QACA;AAAA,QACA,IAAI;AAAA,QACJ;AAAA,QACA,cAAc,IAAI,MAAM,OAAO;AAAA,QAC/B,IAAI;AAAA,QACJ,cAAc,IAAI,MAAM,OAAO;AAAA,MACjC;AAAA,IACF;AACA,SAAK;AAAA,EACP;AACF;AAGA,SAAS,kBAAkB,KAAU,SAAkB,MAAc,IAAY,IAAY,IAAY,IAAkB;AACzH,QAAM,UAAU,IAAI,IAAI;AACxB,QAAM,QAAQ,QAAQ,IAAI;AAAA,IACxB,MAAM;AAAA,IACN,SAAS;AAAA,IACT,MAAM,CAAC,IAAI,IAAI,IAAI,EAAE;AAAA,IACrB,QAAQ,CAAC,GAAG,GAAG,CAAC;AAAA,IAChB,GAAG,EAAE,MAAM,UAAU,GAAG,OAAO,KAAK,UAAU,GAAG,IAAI,EAAE;AAAA,EACzD,CAAC;AACD,UAAQ,KAAK,SAAS,QAAQ,SAAS,KAAK,CAAC;AAC/C;AAEA,SAAS,WAAW,KAAkB;AACpC,SAAO,IAAI,KAAK,OAAO;AACzB;AAGA,SAAS,OAAO,KAAU,GAAmB;AAC3C,SAAO,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,OAAO,MAAM;AACtD;AAEA,SAAS,QAAQ,GAAgC;AAC/C,SAAO,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAC1B;AAEA,SAAS,WAAW,KAAkB,MAAwB;AAC5D,MAAI,QAAQ,CAAC,KAAK,KAAK,OAAO,KAAK,KAAK,MAAM,CAAC;AACjD;AAGA,eAAe,WAAW,KAAkB,OAAsC;AAChF,QAAM,QAAQ,MAAM,CAAC,MAAM,OAAQ,MAAM,CAAC,MAAM,MAAQ,MAAM,CAAC,MAAM,MAAQ,MAAM,CAAC,MAAM;AAC1F,QAAM,SAAS,MAAM,CAAC,MAAM,OAAQ,MAAM,CAAC,MAAM;AACjD,MAAI,MAAO,QAAO,IAAI,SAAS,KAAK;AACpC,MAAI,OAAQ,QAAO,IAAI,SAAS,KAAK;AACrC,QAAM,IAAI,MAAM,4DAA4D;AAC9E;AACA,SAAS,WAAW,KAAgB;AAClC,MAAI,KAAK,QAAQ;AACjB,MAAI,IAAI,QAAQ,CAAC,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,MAAM,CAAC;AAC7D;AAEA,SAAS,eAAe,KAAmB;AACzC,SAAO,IAAI,IAAI,QAAQ,IAAI,KAAK,UAAU;AAC5C;;;AFhYA,eAAsB,oBACpB,UACA,OAA8B,CAAC,GACV;AACrB,QAAM,MAAM,MAAMC,aAAY,OAAO;AACrC,QAAM,QAAQ,KAAK,SAAS,mBAAmB;AAC/C,QAAM,eAAe,KAAK,cAAc,QAAQ,GAAG;AAAA,IACjD;AAAA,IACA,MAAM,KAAK;AAAA,IACX,SAAS,KAAK;AAAA,IACd,OAAO,KAAK;AAAA,EACd,CAAC;AACD,SAAO,IAAI,KAAK;AAClB;;;AHAA,eAAsB,WAAW,SAAuD;AACtF,QAAM,WAAW,MAAM,SAAS,QAAQ,OAAO,MAAM;AAErD,QAAM,aAAa,WAAW,QAAQ;AACtC,QAAM,SAAS,WAAW,OAAO,OAAO,CAAC,MAAM,EAAE,aAAa,OAAO;AAErE,MAAI,OAAO,SAAS,GAAG;AACrB,UAAM,UAAU,OAAO,IAAI,CAAC,MAAM,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI;AAC7D,UAAM,IAAI,MAAM;AAAA,EAA0B,OAAO,EAAE;AAAA,EACrD;AAEA,QAAM,QAAQ,QAAQ,SAAS,mBAAmB;AAClD,QAAM,QAAQ,MAAM,oBAAoB,UAAU;AAAA,IAChD;AAAA,IACA,MAAM,QAAQ;AAAA,IACd,SAAS,QAAQ;AAAA,IACjB,OAAO,QAAQ;AAAA,EACjB,CAAC;AAED,QAAM,UAAU,QAAQ,QAAQ,KAAK;AAErC,SAAO;AAAA,IACL,QAAQ,WAAW;AAAA,IACnB,QAAQ,QAAQ;AAAA,EAClB;AACF;;;AD/BA,IAAM,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBN,SAAS,UAAU,MAA4B;AACpD,QAAM,OAAmB,EAAE,MAAM,OAAO,SAAS,MAAM;AAEvD,QAAM,QAAQ,CAAC,MAAc,MAAsB;AACjD,UAAM,IAAI,KAAK,IAAI,CAAC;AACpB,QAAI,MAAM,UAAa,EAAE,WAAW,GAAG,GAAG;AACxC,YAAM,IAAI,MAAM,qBAAqB,IAAI,EAAE;AAAA,IAC7C;AACA,WAAO;AAAA,EACT;AAEA,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAElB,YAAQ,KAAK;AAAA,MACX,KAAK;AAAA,MACL,KAAK;AACH,aAAK,OAAO;AACZ;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH,aAAK,UAAU;AACf;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH,aAAK,SAAS,MAAM,KAAK,CAAC;AAC1B;AACA;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH,aAAK,UAAU,MAAM,KAAK,CAAC;AAC3B;AACA;AAAA,MACF,KAAK,kBAAkB;AACrB,cAAM,MAAM,MAAM,KAAK,CAAC;AACxB;AACA,cAAM,IAAI,OAAO,GAAG;AACpB,YAAI,CAAC,OAAO,SAAS,CAAC,KAAK,KAAK,EAAG,OAAM,IAAI,MAAM,4BAA4B,GAAG,GAAG;AACrF,aAAK,cAAc;AACnB;AAAA,MACF;AAAA,MACA,KAAK,sBAAsB;AACzB,cAAM,MAAM,MAAM,KAAK,CAAC;AACxB;AACA,YAAI,QAAQ,UAAU,QAAQ,YAAY,QAAQ,SAAS;AACzD,gBAAM,IAAI,MAAM,gCAAgC,GAAG,gCAAgC;AAAA,QACrF;AACA,aAAK,kBAAkB;AACvB;AAAA,MACF;AAAA,MACA,KAAK;AACH,aAAK,cAAc,MAAM,KAAK,CAAC;AAC/B;AACA;AAAA,MACF,KAAK;AACH,aAAK,WAAW,MAAM,KAAK,CAAC;AAC5B;AACA;AAAA,MACF,KAAK;AACH,aAAK,aAAa,MAAM,KAAK,CAAC;AAC9B;AACA;AAAA,MACF;AACE,YAAI,IAAI,WAAW,GAAG,EAAG,OAAM,IAAI,MAAM,mBAAmB,GAAG,EAAE;AACjE,YAAI,KAAK,UAAU,OAAW,OAAM,IAAI,MAAM,wBAAwB,GAAG,EAAE;AAC3E,aAAK,QAAQ;AAAA,IACjB;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAe,cAA+B;AAC5C,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,mBAAmB,YAAY,GAAG;AACtD,UAAM,MAAM,KAAK,MAAM,MAAMC,UAAS,KAAK,MAAM,CAAC;AAClD,WAAO,IAAI,WAAW;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,OAAO,MAAiC;AAC5D,MAAI;AACJ,MAAI;AACF,WAAO,UAAU,IAAI;AAAA,EACvB,SAAS,KAAK;AACZ,YAAQ,MAAM,UAAW,IAAc,OAAO,EAAE;AAChD,YAAQ,MAAM,kCAAkC;AAChD,WAAO;AAAA,EACT;AAEA,MAAI,KAAK,MAAM;AACb,YAAQ,IAAI,IAAI;AAChB,WAAO;AAAA,EACT;AAEA,MAAI,KAAK,SAAS;AAChB,YAAQ,IAAI,MAAM,YAAY,CAAC;AAC/B,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,KAAK,OAAO;AACf,YAAQ,MAAM,qCAAqC;AACnD,YAAQ,MAAM,kCAAkC;AAChD,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,WAAW,KAAK,KAAK,GAAG;AAC3B,YAAQ,MAAM,gCAAgC,KAAK,KAAK,EAAE;AAC1D,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,KAAK,UAAU,GAAG,KAAK,MAAM,MAAM,GAAG,KAAK,MAAM,SAAS,QAAQ,KAAK,KAAK,EAAE,MAAM,CAAC;AAEpG,OAAK,KAAK,YAAY,KAAK,eAAe,CAAC,KAAK,aAAa;AAC3D,YAAQ,MAAM,iEAAiE;AAC/E,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,UAAU,KAAK,UACjB;AAAA,MACE,OAAO,MAAMA,UAAS,KAAK,OAAO;AAAA,MAClC,GAAI,KAAK,gBAAgB,SAAY,EAAE,MAAM,KAAK,YAAY,IAAI,CAAC;AAAA,MACnE,GAAI,KAAK,oBAAoB,SAAY,EAAE,UAAU,KAAK,gBAAgB,IAAI,CAAC;AAAA,IACjF,IACA;AAEJ,UAAM,QAA6B,KAAK,cACpC;AAAA,MACE,SAAS,MAAMA,UAAS,KAAK,WAAW;AAAA,MACxC,GAAI,KAAK,WAAW,EAAE,MAAM,MAAMA,UAAS,KAAK,QAAQ,EAAE,IAAI,CAAC;AAAA,MAC/D,GAAI,KAAK,aAAa,EAAE,QAAQ,MAAMA,UAAS,KAAK,UAAU,EAAE,IAAI,CAAC;AAAA,IACvE,IACA;AAEJ,UAAM,SAAS,MAAM,WAAW;AAAA,MAC9B,OAAO,KAAK;AAAA,MACZ;AAAA,MACA,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;AAAA,MAC7B,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,IAC3B,CAAC;AAED,eAAW,SAAS,OAAO,QAAQ;AACjC,cAAQ,KAAK,YAAY,MAAM,OAAO,EAAE;AAAA,IAC1C;AACA,YAAQ,IAAI,aAAa,QAAQ,OAAO,MAAM,CAAC,EAAE;AACjD,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,YAAQ,MAAM,UAAW,IAAc,OAAO,EAAE;AAChD,WAAO;AAAA,EACT;AACF;AAEA,IAAM,QAAQ,QAAQ,KAAK,CAAC;AAC5B,IAAM,SACJ,UAAU,UACV,WAAW,KAAK,KAChB,aAAa,cAAc,YAAY,GAAG,CAAC,MAAM,aAAa,KAAK;AAErE,IAAI,QAAQ;AACV,OAAK,OAAO,QAAQ,KAAK,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,SAAS;AAChD,YAAQ,WAAW;AAAA,EACrB,CAAC;AACH;","names":["readFile","marked","marked","PDFDocument","contentWidth","PDFDocument","readFile"]}
|
package/dist/index.cjs
CHANGED
|
@@ -411,7 +411,7 @@ function wrapRuns(runs, maxWidth) {
|
|
|
411
411
|
const parts = run.text.split(/(\s+)/);
|
|
412
412
|
for (const part of parts) {
|
|
413
413
|
if (part === "" || /^\s+$/.test(part)) continue;
|
|
414
|
-
words.push({ text: part, style: run.style });
|
|
414
|
+
words.push({ text: part, style: run.style, href: run.href });
|
|
415
415
|
}
|
|
416
416
|
}
|
|
417
417
|
const lines = [];
|
|
@@ -704,7 +704,7 @@ function nodesToRuns(ctx, nodes, base) {
|
|
|
704
704
|
walk(node.children, { font: ctx.italic, size: style.size });
|
|
705
705
|
break;
|
|
706
706
|
case "link":
|
|
707
|
-
runs.push({ text: ctx.sanitize ? sanitizeText(node.text) : node.text, style });
|
|
707
|
+
runs.push({ text: ctx.sanitize ? sanitizeText(node.text) : node.text, style, href: node.href });
|
|
708
708
|
break;
|
|
709
709
|
case "image":
|
|
710
710
|
break;
|
|
@@ -722,18 +722,43 @@ function drawLine(ctx, line, color) {
|
|
|
722
722
|
const pdfPage = currentPdfPage(ctx);
|
|
723
723
|
const size = Math.max(...line.map((r) => r.style.size));
|
|
724
724
|
const baseline = ctx.flow.cursor.y + size * ASCENT;
|
|
725
|
+
const baselinePdf = yToPdf(ctx, baseline);
|
|
725
726
|
let x = marginLeft(ctx);
|
|
726
727
|
for (const run of line) {
|
|
728
|
+
const runColor = run.href ? ctx.theme.color.accent : color;
|
|
727
729
|
pdfPage.drawText(run.text, {
|
|
728
730
|
x,
|
|
729
|
-
y:
|
|
731
|
+
y: baselinePdf,
|
|
730
732
|
size: run.style.size,
|
|
731
733
|
font: run.style.font,
|
|
732
|
-
color: toColor(
|
|
734
|
+
color: toColor(runColor)
|
|
733
735
|
});
|
|
734
|
-
|
|
736
|
+
const width = measureWidth(run);
|
|
737
|
+
if (run.href && width > 0) {
|
|
738
|
+
addLinkAnnotation(
|
|
739
|
+
ctx,
|
|
740
|
+
pdfPage,
|
|
741
|
+
run.href,
|
|
742
|
+
x,
|
|
743
|
+
baselinePdf - run.style.size * 0.25,
|
|
744
|
+
x + width,
|
|
745
|
+
baselinePdf + run.style.size * 0.85
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
x += width;
|
|
735
749
|
}
|
|
736
750
|
}
|
|
751
|
+
function addLinkAnnotation(ctx, pdfPage, href, x1, y1, x2, y2) {
|
|
752
|
+
const context = ctx.doc.context;
|
|
753
|
+
const annot = context.obj({
|
|
754
|
+
Type: "Annot",
|
|
755
|
+
Subtype: "Link",
|
|
756
|
+
Rect: [x1, y1, x2, y2],
|
|
757
|
+
Border: [0, 0, 0],
|
|
758
|
+
A: { Type: "Action", S: "URI", URI: import_pdf_lib.PDFString.of(href) }
|
|
759
|
+
});
|
|
760
|
+
pdfPage.node.addAnnot(context.register(annot));
|
|
761
|
+
}
|
|
737
762
|
function marginLeft(ctx) {
|
|
738
763
|
return ctx.page.margin.left;
|
|
739
764
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/page/config.ts","../src/page/flow.ts","../src/parser/index.ts","../src/validator/index.ts","../src/theme/default.ts","../src/renderer/index.ts","../src/renderer/render.ts","../src/renderer/measure.ts","../src/renderer/sanitize.ts","../src/generate.ts"],"sourcesContent":["export * from './page';\nexport * from './parser';\nexport * from './validator';\nexport * from './theme';\nexport * from './renderer';\nexport * from './generate';\n","export type Points = number;\n\nexport type PageMargins = {\n top: Points;\n right: Points;\n bottom: Points;\n left: Points;\n};\n\nexport type PageSize = {\n width: Points;\n height: Points;\n};\n\nexport type PageConfig = {\n size: PageSize;\n margin: PageMargins;\n};\n\nconst A4 = Object.freeze({ width: 595.28, height: 841.89 });\n\nexport function contentWidth(page: PageConfig): Points {\n return page.size.width - page.margin.left - page.margin.right;\n}\n\nexport function contentHeight(page: PageConfig): Points {\n return page.size.height - page.margin.top - page.margin.bottom;\n}\n\nexport function createA4(margin: Partial<PageMargins> = {}): PageConfig {\n const marginValue: PageMargins = {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n ...margin,\n };\n\n return { size: { ...A4 }, margin: marginValue };\n}\n","import type { PageConfig, Points } from \"./config\";\nimport { contentHeight, contentWidth } from \"./config\";\n\nexport type Cursor = {\n x: Points;\n y: Points;\n};\n\n/**\n * Holds cursor state (relative to the current page's content area, origin at\n * the top-left) and coordinates page-breaks. Does not hardcode any dimension;\n * it asks the page config how much room is left.\n */\nexport type Flow = {\n readonly page: PageConfig;\n readonly cursor: Cursor;\n readonly pageNumber: number;\n contentWidth(): Points;\n contentHeight(): Points;\n isLastPageEmpty(): boolean;\n /** True when advancing `dy` would overflow the current content area. */\n needNewPage(dy: Points): boolean;\n /** Break to the next page, resetting the cursor to the top-left. */\n newPage(): void;\n /** Move the cursor down by `dy`. */\n advance(dy: Points): void;\n};\n\nexport function createFlow(page: PageConfig): Flow {\n const cursor: Cursor = { x: 0, y: 0 };\n let pageNumber = 0;\n\n return {\n get page() {\n return page;\n },\n get cursor() {\n return cursor;\n },\n get pageNumber() {\n return pageNumber;\n },\n contentWidth: () => contentWidth(page),\n contentHeight: () => contentHeight(page),\n\n isLastPageEmpty: () => cursor.y === 0,\n\n needNewPage: (dy) => cursor.y + dy >= contentHeight(page),\n\n newPage: () => {\n pageNumber += 1;\n cursor.x = 0;\n cursor.y = 0;\n },\n\n advance: (dy) => {\n cursor.y += dy;\n },\n };\n}\n","import { marked, type Tokens, type Token } from \"marked\";\nimport type {\n InlineNode,\n ListBlock,\n ListItem,\n MarkdownBlock,\n MarkdownDocument,\n} from \"./types\";\n\nexport function parseMarkdown(markdown: string): MarkdownDocument {\n const tokens = marked.lexer(markdown);\n\n const blocks: MarkdownBlock[] = [];\n\n for (const token of tokens) {\n const block = parseBlock(token);\n\n if (block) {\n blocks.push(block);\n }\n }\n\n return { blocks };\n}\n\nfunction parseBlock(token: Token): MarkdownBlock | null {\n switch (token.type) {\n case \"heading\": {\n const heading = token as Tokens.Heading;\n const level = clampHeadingLevel(heading.depth);\n return { type: \"heading\", level, children: parseInline(heading.tokens) };\n }\n\n case \"paragraph\":\n return { type: \"paragraph\", children: parseInline(token.tokens ?? []) };\n\n case \"list\":\n return parseList(token as Tokens.List);\n\n case \"hr\":\n return { type: \"thematicBreak\" };\n\n case \"blockquote\":\n return parseBlockquote(token as Tokens.Blockquote);\n\n case \"space\":\n return null;\n\n default:\n return null;\n }\n}\n\nfunction parseBlockquote(token: Tokens.Blockquote): MarkdownBlock {\n const children: InlineNode[] = [];\n\n for (const block of token.tokens ?? []) {\n if (block.type === \"paragraph\") {\n if (children.length > 0) {\n children.push({ type: \"text\", text: \" \" });\n }\n children.push(...parseInline((block as Tokens.Paragraph).tokens ?? []));\n }\n }\n\n return { type: \"blockquote\", children };\n}\n\nfunction parseList(token: Tokens.List): ListBlock {\n return {\n type: \"list\",\n ordered: token.ordered,\n items: token.items.map(parseListItem),\n };\n}\n\nfunction parseListItem(item: Tokens.ListItem): ListItem {\n const children: InlineNode[] = [];\n const nested: ListBlock[] = [];\n\n for (const block of item.tokens ?? []) {\n if (block.type === \"list\") {\n nested.push(parseList(block as Tokens.List));\n } else if (\"tokens\" in block && block.tokens) {\n children.push(...parseInline(block.tokens));\n }\n }\n\n return { children, ...(nested.length > 0 ? { nested } : {}) };\n}\n\nfunction clampHeadingLevel(depth: number): 1 | 2 | 3 {\n return Math.min(Math.max(depth, 1), 3) as 1 | 2 | 3;\n}\n\nfunction parseInline(tokens: Token[]): InlineNode[] {\n const nodes: InlineNode[] = [];\n\n for (const token of tokens) {\n switch (token.type) {\n case \"text\": {\n const text = token as Tokens.Text;\n nodes.push({ type: \"text\", text: text.text });\n break;\n }\n\n case \"strong\": {\n const strong = token as Tokens.Strong;\n nodes.push({ type: \"strong\", children: parseInline(strong.tokens) });\n break;\n }\n\n case \"em\": {\n const em = token as Tokens.Em;\n nodes.push({ type: \"emphasis\", children: parseInline(em.tokens) });\n break;\n }\n\n case \"link\": {\n const link = token as Tokens.Link;\n nodes.push({ type: \"link\", text: link.text, href: link.href });\n break;\n }\n\n case \"image\": {\n const image = token as Tokens.Image;\n nodes.push({ type: \"image\", href: image.href });\n break;\n }\n\n case \"codespan\": {\n const codespan = token as Tokens.Codespan;\n nodes.push({ type: \"text\", text: codespan.text });\n break;\n }\n }\n }\n\n return nodes;\n}\n","import { marked } from \"marked\";\nimport { parseMarkdown } from \"../parser\";\nimport type {\n HeadingBlock,\n InlineNode,\n MarkdownBlock,\n MarkdownDocument,\n} from \"../parser/types\";\n\nexport type Severity = \"error\" | \"warning\";\n\nexport type Issue = {\n severity: Severity;\n blockIndex?: number;\n message: string;\n};\n\nexport type ValidationResult = {\n issues: Issue[];\n valid: boolean;\n};\n\nconst EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$/i;\nconst RAW_EMAIL_REGEX = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/i;\n\nexport function validateCV(markdown: string): ValidationResult {\n const doc = parseMarkdown(markdown);\n\n if (doc.blocks.length === 0) {\n return { issues: [{ severity: \"error\", message: \"Document is empty.\" }], valid: false };\n }\n\n const issues: Issue[] = [];\n\n const hasName = doc.blocks.some((b) => b.type === \"heading\" && b.level === 1);\n if (!hasName) {\n issues.push({\n severity: \"error\",\n message: 'Missing a level-1 heading. Add \"# Your Name\" for the candidate name.',\n });\n }\n\n validateContact(doc, issues);\n validateHierarchy(doc, issues);\n validateSection(doc, \"experience\", issues);\n validateSection(doc, \"education\", issues);\n detectUnsupported(markdown, issues);\n\n return { issues, valid: issues.every((i) => i.severity !== \"error\") };\n}\n\nfunction validateContact(doc: MarkdownDocument, issues: Issue[]): void {\n const texts = flattenText(doc);\n const links = flattenLinks(doc);\n\n const emails = new Set<string>();\n for (const t of texts) {\n const m = t.match(RAW_EMAIL_REGEX);\n if (m) emails.add(m[0]);\n }\n for (const href of links) {\n if (href.startsWith(\"mailto:\")) emails.add(href.slice(\"mailto:\".length).split(\"?\")[0] ?? \"\");\n }\n\n const validEmails = [...emails].filter((e) => EMAIL_REGEX.test(e));\n const hasContactLink = links.some((href) => {\n const lower = href.toLowerCase();\n return /mailto:|github\\.com|linkedin\\.com|twitter\\.com|x\\.com|dev\\.to|t\\.me/.test(lower);\n });\n\n if (validEmails.length === 0 && !hasContactLink) {\n issues.push({\n severity: \"warning\",\n message: 'No contact info found. Add an email or a contact link (e.g. mailto:, github, linkedin).',\n });\n }\n\n if ([...emails].some((e) => !EMAIL_REGEX.test(e))) {\n issues.push({\n severity: \"warning\",\n message: `Invalid email address found: \"${[...emails].filter((e) => !EMAIL_REGEX.test(e)).join(\", \")}\".`,\n });\n }\n}\n\nfunction validateHierarchy(doc: MarkdownDocument, issues: Issue[]): void {\n let prevLevel = 0;\n\n doc.blocks.forEach((block, index) => {\n if (block.type !== \"heading\") return;\n if (prevLevel !== 0 && block.level > prevLevel + 1) {\n issues.push({\n severity: \"warning\",\n blockIndex: index,\n message: `Heading level skipped from H${prevLevel} to H${block.level}. Prefer H1 → H2 → H3 order.`,\n });\n }\n prevLevel = block.level;\n });\n}\n\nfunction validateSection(doc: MarkdownDocument, sectionName: string, issues: Issue[]): void {\n const headings: { block: HeadingBlock; index: number }[] = [];\n doc.blocks.forEach((block, index) => {\n if (block.type === \"heading\") headings.push({ block, index });\n });\n\n const target = headings.find(\n ({ block }) => block.level === 2 && headingText(block).toLowerCase() === sectionName,\n );\n\n if (!target) {\n issues.push({\n severity: \"warning\",\n message: `Section \"${sectionName}\" not found. Add \"## ${sectionName}\" heading.`,\n });\n return;\n }\n\n const body: { block: MarkdownBlock; index: number }[] = [];\n const nextSection = doc.blocks\n .slice(target.index + 1)\n .findIndex((b) => b.type === \"heading\" && b.level === 2);\n const end = nextSection === -1 ? doc.blocks.length : target.index + 1 + nextSection;\n\n for (let k = target.index + 1; k < end; k++) {\n const block = doc.blocks[k];\n if (block) body.push({ block, index: k });\n }\n\n const hasBody = body.some(({ block }) => block.type !== \"heading\" && block.type !== \"thematicBreak\");\n if (!hasBody) {\n issues.push({\n severity: \"warning\",\n blockIndex: target.index,\n message: `Section \"${sectionName}\" has no content below its heading.`,\n });\n return;\n }\n\n const entries = body.filter(\n (s): s is { block: HeadingBlock; index: number } =>\n s.block.type === \"heading\" && s.block.level === 3,\n );\n\n entries.forEach(({ block, index }) => {\n const hasContent = body\n .filter((s) => s.index > index)\n .some(({ block: b }) => b.type !== \"heading\" && b.type !== \"thematicBreak\");\n if (!hasContent) {\n issues.push({\n severity: \"warning\",\n blockIndex: index,\n message: `Entry \"${headingText(block)}\" in \"${sectionName}\" has no content below it.`,\n });\n }\n });\n}\n\nfunction detectUnsupported(markdown: string, issues: Issue[]): void {\n const supported = new Set([\"space\", \"heading\", \"paragraph\", \"list\", \"hr\", \"blockquote\"]);\n\n for (const token of marked.lexer(markdown)) {\n if (!supported.has(token.type)) {\n issues.push({\n severity: \"warning\",\n message: `Unsupported Markdown ignored: \"${token.type}\".`,\n });\n }\n }\n}\n\nfunction headingText(block: HeadingBlock): string {\n return inlineText(block.children);\n}\n\nfunction inlineText(nodes: InlineNode[]): string {\n let out = \"\";\n for (const node of nodes) {\n switch (node.type) {\n case \"text\":\n out += node.text;\n break;\n case \"strong\":\n case \"emphasis\":\n out += inlineText(node.children);\n break;\n case \"link\":\n out += node.text;\n break;\n case \"image\":\n break;\n }\n }\n return out;\n}\n\nfunction flattenText(doc: MarkdownDocument): string[] {\n const out: string[] = [];\n for (const block of doc.blocks) {\n if (block.type === \"paragraph\") out.push(inlineText(block.children));\n else if (block.type === \"heading\") out.push(inlineText(block.children));\n }\n return out;\n}\n\nfunction flattenLinks(doc: MarkdownDocument): string[] {\n const out: string[] = [];\n const walk = (nodes: InlineNode[]): void => {\n for (const node of nodes) {\n if (node.type === \"link\") out.push(node.href);\n else if (node.type === \"strong\" || node.type === \"emphasis\") walk(node.children);\n }\n };\n for (const block of doc.blocks) {\n if (block.type === \"paragraph\") walk(block.children);\n else if (block.type === \"heading\") walk(block.children);\n }\n return out;\n}\n","import type { Theme } from \"./types\";\n\nexport function createDefaultTheme(): Theme {\n return {\n name: \"ats-simple\",\n text: {\n name: { family: \"Helvetica\", size: 22, weight: \"bold\" },\n section: { family: \"Helvetica\", size: 13, weight: \"bold\" },\n entryTitle: { family: \"Helvetica\", size: 11, weight: \"bold\" },\n body: { family: \"Helvetica\", size: 10, weight: \"normal\" },\n },\n color: {\n ink: { r: 0.1, g: 0.1, b: 0.1 },\n accent: { r: 0.05, g: 0.15, b: 0.4 },\n muted: { r: 0.35, g: 0.35, b: 0.35 },\n },\n spacing: {\n sectionGap: 6,\n entryGap: 6,\n entrySpacing: 8,\n lineHeight: 1.35,\n columnGap: 8,\n headerGap: 8,\n },\n };\n}\n","import { PDFDocument } from \"pdf-lib\";\nimport { parseMarkdown } from \"../parser\";\nimport { createDefaultTheme, type Theme } from \"../theme\";\nimport { renderDocument, type FontSet, type ProfileImage } from \"./render\";\nimport type { PageConfig } from \"../page\";\n\nexport type RenderMarkdownOptions = {\n theme?: Theme;\n page?: PageConfig;\n profile?: ProfileImage;\n fonts?: FontSet;\n};\n\nexport async function renderMarkdownToPdf(\n markdown: string,\n opts: RenderMarkdownOptions = {},\n): Promise<Uint8Array> {\n const doc = await PDFDocument.create();\n const theme = opts.theme ?? createDefaultTheme();\n await renderDocument(doc, parseMarkdown(markdown), {\n theme,\n page: opts.page,\n profile: opts.profile,\n fonts: opts.fonts,\n });\n return doc.save();\n}\n\nexport { renderDocument } from \"./render\";\nexport type { FontSet, ProfileImage } from \"./render\";","import { PDFDocument, StandardFonts, rgb, type PDFFont, type PDFPage, type PDFImage } from \"pdf-lib\";\nimport fontkit from \"@pdf-lib/fontkit\";import type { MarkdownBlock, MarkdownDocument, InlineNode } from \"../parser/types\";\nimport { createA4, createFlow, type PageConfig, type Points, type Flow } from \"../page\";\nimport type { FontSpec, Theme, RGB } from \"../theme\";\nimport type { PdfStyle } from \"./fonts\";\nimport { measureWidth, wrapRuns, type Run } from \"./measure\";\nimport { sanitizeText } from \"./sanitize\";\n\nexport type ProfilePosition = \"left\" | \"center\" | \"right\";\n\nexport type ProfileImage = {\n /** PNG or JPEG bytes. */\n bytes: Uint8Array;\n /** Side length of the (square) photo in points. Default 96. */\n size?: number;\n /** Horizontal position within the header. Default \"right\". */\n position?: ProfilePosition;\n};\n\ntype Ctx = {\n doc: PDFDocument;\n flow: Flow;\n page: PageConfig;\n theme: Theme;\n font: PDFFont;\n bold: PDFFont;\n italic: PDFFont;\n profile: { image: PDFImage; size: number; position: ProfilePosition } | null;\n sanitize: boolean;\n};\n\nexport type FontSet = {\n /** Regular weight TTF bytes (e.g. Noto Sans CJK). */\n regular: Uint8Array;\n bold?: Uint8Array;\n italic?: Uint8Array;\n};\n\nexport type RenderOptions = {\n theme?: Theme;\n page?: PageConfig;\n profile?: ProfileImage;\n fonts?: FontSet;\n};\n\n/** Fraction of the em-size where the text baseline sits (Helvetica ascent). */\nconst ASCENT = 0.72;\n\ntype StandardFamily = {\n regular: StandardFonts;\n bold: StandardFonts;\n italic: StandardFonts;\n};\n\nconst STANDARD_FONTS: Record<string, StandardFamily> = {\n helvetica: {\n regular: StandardFonts.Helvetica,\n bold: StandardFonts.HelveticaBold,\n italic: StandardFonts.HelveticaOblique,\n },\n times: {\n regular: StandardFonts.TimesRoman,\n bold: StandardFonts.TimesRomanBold,\n italic: StandardFonts.TimesRomanItalic,\n },\n courier: {\n regular: StandardFonts.Courier,\n bold: StandardFonts.CourierBold,\n italic: StandardFonts.CourierOblique,\n },\n};\n\n/**\n * Map a theme `family` name to a set of bundled standard fonts, falling back\n * to Helvetica for unknown families.\n */\nfunction resolveStandardFamily(family: string | undefined): StandardFamily {\n const key = (family ?? \"helvetica\").toLowerCase();\n return STANDARD_FONTS[key] ?? STANDARD_FONTS.helvetica!;\n}\n\nexport async function renderDocument(doc: PDFDocument, document: MarkdownDocument, opts: RenderOptions = {}): Promise<void> {\n const theme = opts.theme;\n if (!theme) throw new Error(\"renderDocument requires a theme\");\n const page = opts.page ?? createA4({ top: 40, right: 40, bottom: 40, left: 40 });\n const flow = createFlow(page);\n createPage(doc, page);\n\n if (opts.fonts) doc.registerFontkit(fontkit);\n\n const standard = resolveStandardFamily(theme.text.body.family);\n\n const font = opts.fonts?.regular\n ? await doc.embedFont(opts.fonts.regular, { subset: true })\n : await doc.embedFont(standard.regular);\n const bold = opts.fonts?.bold\n ? await doc.embedFont(opts.fonts.bold, { subset: true })\n : opts.fonts?.regular\n ? font\n : await doc.embedFont(standard.bold);\n const italic = opts.fonts?.italic\n ? await doc.embedFont(opts.fonts.italic, { subset: true })\n : opts.fonts?.regular\n ? font\n : await doc.embedFont(standard.italic);\n\n const profile = opts.profile\n ? {\n image: await embedImage(doc, opts.profile.bytes),\n size: opts.profile.size ?? 96,\n position: opts.profile.position ?? \"right\",\n }\n : null;\n\n const sanitize = !opts.fonts?.regular;\n\n const ctx: Ctx = { doc, flow, page, theme, font, bold, italic, profile, sanitize };\n\n renderProfile(ctx);\n\n for (const block of document.blocks) {\n renderBlock(ctx, block);\n }\n}\n\nfunction renderBlock(ctx: Ctx, block: MarkdownBlock): void {\n switch (block.type) {\n case \"heading\":\n renderHeading(ctx, block);\n break;\n case \"paragraph\":\n renderParagraph(ctx, block.children, specToStyle(ctx, ctx.theme.text.body));\n break;\n case \"list\":\n renderList(ctx, block);\n break;\n case \"thematicBreak\":\n renderThematicBreak(ctx);\n break;\n case \"blockquote\":\n renderBlockquote(ctx, block);\n break;\n }\n}\n\nfunction specToStyle(ctx: Ctx, spec: FontSpec): PdfStyle {\n const weight = spec.weight === \"bold\" ? \"bold\" : \"normal\";\n return { font: weight === \"bold\" ? ctx.bold : ctx.font, size: spec.size };\n}\n\nfunction lineHeightFor(ctx: Ctx, size: Points): Points {\n return size * ctx.theme.spacing.lineHeight;\n}\n\n/** Advance the cursor down by `pts`, breaking to a new page when needed. */\nfunction gap(ctx: Ctx, pts: Points): void {\n if (pts <= 0) return;\n if (ctx.flow.needNewPage(pts)) ensurePage(ctx);\n ctx.flow.advance(pts);\n}\n\nfunction renderHeading(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"heading\" }>): void {\n const spacing = ctx.theme.spacing;\n\n // Space above the heading, based on its level.\n if (block.level === 2) gap(ctx, spacing.sectionGap);\n else if (block.level === 3) gap(ctx, spacing.entrySpacing);\n\n const spec =\n block.level === 1 ? ctx.theme.text.name : block.level === 2 ? ctx.theme.text.section : ctx.theme.text.entryTitle;\n const color = block.level === 2 ? ctx.theme.color.accent : ctx.theme.color.ink;\n const style = specToStyle(ctx, spec);\n renderLines(ctx, block.children, style, color);\n\n // Space below the heading before its content.\n if (block.level === 1) gap(ctx, spacing.headerGap);\n else gap(ctx, spacing.entryGap);\n}\n\nfunction renderParagraph(ctx: Ctx, nodes: InlineNode[], spec: PdfStyle): void {\n const spacing = ctx.theme.spacing;\n renderLines(ctx, nodes, spec, ctx.theme.color.ink);\n gap(ctx, spacing.entryGap);\n}\n\nfunction renderProfile(ctx: Ctx): void {\n if (!ctx.profile) return;\n\n const { image, size, position } = ctx.profile;\n const pdfPage = currentPdfPage(ctx);\n const contentWidth = ctx.flow.contentWidth();\n\n let x: number;\n if (position === \"left\") x = marginLeft(ctx);\n else if (position === \"center\") x = marginLeft(ctx) + (contentWidth - size) / 2;\n else x = marginLeft(ctx) + contentWidth - size;\n\n const y = yToPdf(ctx, size);\n pdfPage.drawImage(image, { x, y, width: size, height: size });\n}\n\nfunction renderBlockquote(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"blockquote\" }>): void {\n const spacing = ctx.theme.spacing;\n const spec = specToStyle(ctx, ctx.theme.text.body);\n renderLines(ctx, block.children, spec, ctx.theme.color.muted);\n gap(ctx, spacing.entryGap);\n}\n\nfunction renderLines(ctx: Ctx, nodes: InlineNode[], spec: PdfStyle, color: RGB): void {\n const runs = nodesToRuns(ctx, nodes, spec);\n const maxWidth = ctx.flow.contentWidth();\n const lines = wrapRuns(runs, maxWidth);\n const lineHeight = lineHeightFor(ctx, spec.size);\n\n for (const line of lines) {\n if (ctx.flow.needNewPage(lineHeight)) ensurePage(ctx);\n drawLine(ctx, line, color);\n ctx.flow.advance(lineHeight);\n }\n}\n\nfunction renderList(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"list\" }>): void {\n const spacing = ctx.theme.spacing;\n const spec = specToStyle(ctx, ctx.theme.text.body);\n const lineHeight = lineHeightFor(ctx, spec.size);\n const indent = spec.size * 1.2;\n\n for (const item of block.items) {\n const bullet = block.ordered ? `${block.items.indexOf(item) + 1}. ` : \"• \";\n const runs: Run[] = [{ text: bullet, style: spec }];\n const wrapped = wrapRuns([...runs, ...nodesToRuns(ctx, item.children, spec)], ctx.flow.contentWidth() - indent);\n\n wrapped.forEach((line, i) => {\n const padded = i === 0 ? line : padRuns(line, indent, spec);\n if (ctx.flow.needNewPage(lineHeight)) ensurePage(ctx);\n drawLine(ctx, padded, ctx.theme.color.ink);\n ctx.flow.advance(lineHeight);\n });\n\n if (item.nested) {\n for (const sub of item.nested) {\n renderNestedList(ctx, sub, indent);\n }\n }\n }\n\n gap(ctx, spacing.entryGap);\n}\n\nfunction renderNestedList(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"list\" }>, baseIndent: Points): void {\n const spec = specToStyle(ctx, ctx.theme.text.body);\n const lineHeight = lineHeightFor(ctx, spec.size);\n const indent = baseIndent + spec.size * 1.2;\n\n for (const item of block.items) {\n const bullet = block.ordered ? `${block.items.indexOf(item) + 1}. ` : \"• \";\n const runs: Run[] = [{ text: bullet, style: spec }];\n const wrapped = wrapRuns([...runs, ...nodesToRuns(ctx, item.children, spec)], ctx.flow.contentWidth() - indent);\n\n wrapped.forEach((line, i) => {\n const padded = i === 0 ? padRuns(line, baseIndent, spec) : padRuns(line, indent, spec);\n if (ctx.flow.needNewPage(lineHeight)) ensurePage(ctx);\n drawLine(ctx, padded, ctx.theme.color.ink);\n ctx.flow.advance(lineHeight);\n });\n\n if (item.nested) {\n for (const sub of item.nested) {\n renderNestedList(ctx, sub, indent);\n }\n }\n }\n}\n\nfunction renderThematicBreak(ctx: Ctx): void {\n const spacing = ctx.theme.spacing;\n const y = ctx.flow.cursor.y;\n const h = 0.75;\n if (ctx.flow.needNewPage(spacing.entryGap + h)) ensurePage(ctx);\n const pdfPage = currentPdfPage(ctx);\n pdfPage.drawRectangle({\n x: marginLeft(ctx),\n y: yToPdf(ctx, y + spacing.entryGap + h),\n width: ctx.flow.contentWidth(),\n height: h,\n color: toColor(ctx.theme.color.muted),\n });\n ctx.flow.advance(spacing.entryGap * 2 + h);\n}\n\nfunction nodesToRuns(ctx: Ctx, nodes: InlineNode[], base: PdfStyle): Run[] {\n const runs: Run[] = [];\n const walk = (list: InlineNode[], style: PdfStyle): void => {\n for (const node of list) {\n switch (node.type) {\n case \"text\":\n if (node.text) runs.push({ text: ctx.sanitize ? sanitizeText(node.text) : node.text, style });\n break;\n case \"strong\":\n walk(node.children, { font: ctx.bold, size: style.size });\n break;\n case \"emphasis\":\n walk(node.children, { font: ctx.italic, size: style.size });\n break;\n case \"link\":\n runs.push({ text: ctx.sanitize ? sanitizeText(node.text) : node.text, style });\n break;\n case \"image\":\n break;\n }\n }\n };\n walk(nodes, base);\n return runs;\n}\n\nfunction padRuns(runs: Run[], pad: Points, spec: PdfStyle): Run[] {\n if (pad <= 0) return runs;\n return [{ text: \" \".repeat(Math.max(1, Math.round(pad / (spec.size * 0.5)))), style: spec }, ...runs];\n}\n\nfunction drawLine(ctx: Ctx, line: Run[], color: RGB): void {\n const pdfPage = currentPdfPage(ctx);\n const size = Math.max(...line.map((r) => r.style.size));\n const baseline = ctx.flow.cursor.y + size * ASCENT;\n let x = marginLeft(ctx);\n for (const run of line) {\n pdfPage.drawText(run.text, {\n x,\n y: yToPdf(ctx, baseline),\n size: run.style.size,\n font: run.style.font,\n color: toColor(color),\n });\n x += measureWidth(run);\n }\n}\n\nfunction marginLeft(ctx: Ctx): number {\n return ctx.page.margin.left;\n}\n\n/** Convert flow Y (from content-area top) to pdf-lib Y (from page bottom). */\nfunction yToPdf(ctx: Ctx, y: Points): Points {\n return ctx.page.size.height - ctx.page.margin.top - y;\n}\n\nfunction toColor(c: RGB): ReturnType<typeof rgb> {\n return rgb(c.r, c.g, c.b);\n}\n\nfunction createPage(doc: PDFDocument, page: PageConfig): void {\n doc.addPage([page.size.width, page.size.height]);\n}\n\n/** Embed PNG or JPEG bytes into the document, auto-detecting the format. */\nasync function embedImage(doc: PDFDocument, bytes: Uint8Array): Promise<PDFImage> {\n const isPng = bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47;\n const isJpeg = bytes[0] === 0xff && bytes[1] === 0xd8;\n if (isPng) return doc.embedPng(bytes);\n if (isJpeg) return doc.embedJpg(bytes);\n throw new Error(\"Unsupported image format: only PNG and JPEG are supported.\");\n}\nfunction ensurePage(ctx: Ctx): void {\n ctx.flow.newPage();\n ctx.doc.addPage([ctx.page.size.width, ctx.page.size.height]);\n}\n\nfunction currentPdfPage(ctx: Ctx): PDFPage {\n return ctx.doc.getPage(ctx.flow.pageNumber);\n}\n","import type { PdfStyle } from \"./fonts\";\n\n/** A styled run of text. */\nexport type Run = {\n text: string;\n style: PdfStyle;\n};\n\nexport function measureWidth(run: Run): number {\n return run.style.font.widthOfTextAtSize(run.text, run.style.size);\n}\n\nexport function measureLine(line: Run[]): number {\n return line.reduce((sum, run) => sum + measureWidth(run), 0);\n}\n\n/**\n * Greedy word-wrap: pack styled runs into lines that fit `maxWidth`.\n * Words are never split; a single word wider than the box wraps onto its own line.\n */\nexport function wrapRuns(runs: Run[], maxWidth: number): Run[][] {\n if (runs.length === 0) return [];\n\n const words: { text: string; style: PdfStyle }[] = [];\n for (const run of runs) {\n const parts = run.text.split(/(\\s+)/);\n for (const part of parts) {\n if (part === \"\" || /^\\s+$/.test(part)) continue;\n words.push({ text: part, style: run.style });\n }\n }\n\n const lines: Run[][] = [];\n let current: Run[] = [];\n let currentWidth = 0;\n\n for (const word of words) {\n const w = measureWidth(word);\n const gapWidth = current.length === 0 ? 0 : measureWidth({ text: \" \", style: word.style });\n\n if (current.length > 0 && currentWidth + gapWidth + w > maxWidth) {\n lines.push(current);\n current = [{ ...word }];\n currentWidth = w;\n continue;\n }\n\n if (current.length > 0) {\n current.push({ text: \" \", style: word.style });\n currentWidth += gapWidth;\n }\n current.push({ ...word });\n currentWidth += w;\n }\n\n if (current.length > 0) lines.push(current);\n return lines;\n}","// Characters representable by the WinAnsi (CP1252) encoding used by the\n// standard PDF fonts (Helvetica, Times, Courier). Anything outside this set\n// would make pdf-lib throw, so we sanitize text before measuring/drawing.\n//\n// WinAnsi covers: ASCII (0x20-0x7E), Latin-1 (0xA0-0xFF), plus a handful of\n// CP1252 specials (curly quotes, dashes, bullet, euro, etc.).\n\nconst CP1252_SPECIALS = new Set<number>([\n 0x20ac, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030,\n 0x0160, 0x2039, 0x0152, 0x017d, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022,\n 0x2013, 0x2014, 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x017e, 0x0178,\n]);\n\n/** Common unsupported punctuation mapped to a WinAnsi-safe equivalent. */\nconst FALLBACKS: Record<number, string> = {\n 0x30fb: \"\\u00b7\", // ・ -> ·\n 0x3001: \",\", // 、-> ,\n 0xff0c: \",\", // ,-> ,\n 0x3002: \".\", // 。-> .\n 0xff1f: \"?\", // ?-> ?\n 0xff01: \"!\", // !-> !\n 0x201c: '\"', // \" -> \"\n 0x201d: '\"', // \" -> \"\n 0x2018: \"'\", // ' -> '\n 0x2019: \"'\", // ' -> '\n};\n\nfunction isWinAnsi(code: number): boolean {\n if (code >= 0x20 && code <= 0x7e) return true;\n if (code >= 0xa0 && code <= 0xff) return true;\n return CP1252_SPECIALS.has(code);\n}\n\n/**\n * Replace characters that cannot be encoded by standard PDF fonts with a safe\n * equivalent, or drop them. This keeps CJK/kana text from crashing the render.\n */\nexport function sanitizeText(text: string): string {\n let out = \"\";\n for (const ch of text) {\n const code = ch.codePointAt(0)!;\n if (isWinAnsi(code)) {\n out += ch;\n continue;\n }\n const fallback = FALLBACKS[code];\n if (fallback) out += fallback;\n // otherwise: drop the character\n }\n return out;\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { validateCV, type Issue } from \"./validator\";\nimport { renderMarkdownToPdf, type FontSet, type ProfileImage } from \"./renderer\";\nimport { createDefaultTheme, type Theme } from \"./theme\";\nimport type { PageConfig } from \"./page\";\n\nexport type GenerateCVOptions = {\n /** Path to the source Markdown file. */\n input: string;\n /** Path where the generated PDF will be written. */\n output: string;\n /** Profile photo to render in the header. */\n profile?: ProfileImage;\n /** Custom TTF fonts for non-Latin text (e.g. Noto Sans CJK). */\n fonts?: FontSet;\n theme?: Theme;\n page?: PageConfig;\n};\n\nexport type GenerateCVResult = {\n /** All validation issues found (warnings only; errors throw). */\n issues: Issue[];\n /** Path the PDF was written to. */\n output: string;\n};\n\nexport async function generateCV(options: GenerateCVOptions): Promise<GenerateCVResult> {\n const markdown = await readFile(options.input, \"utf8\");\n\n const validation = validateCV(markdown);\n const errors = validation.issues.filter((i) => i.severity === \"error\");\n\n if (errors.length > 0) {\n const summary = errors.map((e) => `- ${e.message}`).join(\"\\n\");\n throw new Error(`CV validation failed:\\n${summary}`);\n }\n\n const theme = options.theme ?? createDefaultTheme();\n const bytes = await renderMarkdownToPdf(markdown, {\n theme,\n page: options.page,\n profile: options.profile,\n fonts: options.fonts,\n });\n\n await writeFile(options.output, bytes);\n\n return {\n issues: validation.issues,\n output: options.output,\n };\n}\n"],"mappings":"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","names":["import_marked","import_pdf_lib","fontkit","contentWidth"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/page/config.ts","../src/page/flow.ts","../src/parser/index.ts","../src/validator/index.ts","../src/theme/default.ts","../src/renderer/index.ts","../src/renderer/render.ts","../src/renderer/measure.ts","../src/renderer/sanitize.ts","../src/generate.ts"],"sourcesContent":["export * from './page';\nexport * from './parser';\nexport * from './validator';\nexport * from './theme';\nexport * from './renderer';\nexport * from './generate';\n","export type Points = number;\n\nexport type PageMargins = {\n top: Points;\n right: Points;\n bottom: Points;\n left: Points;\n};\n\nexport type PageSize = {\n width: Points;\n height: Points;\n};\n\nexport type PageConfig = {\n size: PageSize;\n margin: PageMargins;\n};\n\nconst A4 = Object.freeze({ width: 595.28, height: 841.89 });\n\nexport function contentWidth(page: PageConfig): Points {\n return page.size.width - page.margin.left - page.margin.right;\n}\n\nexport function contentHeight(page: PageConfig): Points {\n return page.size.height - page.margin.top - page.margin.bottom;\n}\n\nexport function createA4(margin: Partial<PageMargins> = {}): PageConfig {\n const marginValue: PageMargins = {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n ...margin,\n };\n\n return { size: { ...A4 }, margin: marginValue };\n}\n","import type { PageConfig, Points } from \"./config\";\nimport { contentHeight, contentWidth } from \"./config\";\n\nexport type Cursor = {\n x: Points;\n y: Points;\n};\n\n/**\n * Holds cursor state (relative to the current page's content area, origin at\n * the top-left) and coordinates page-breaks. Does not hardcode any dimension;\n * it asks the page config how much room is left.\n */\nexport type Flow = {\n readonly page: PageConfig;\n readonly cursor: Cursor;\n readonly pageNumber: number;\n contentWidth(): Points;\n contentHeight(): Points;\n isLastPageEmpty(): boolean;\n /** True when advancing `dy` would overflow the current content area. */\n needNewPage(dy: Points): boolean;\n /** Break to the next page, resetting the cursor to the top-left. */\n newPage(): void;\n /** Move the cursor down by `dy`. */\n advance(dy: Points): void;\n};\n\nexport function createFlow(page: PageConfig): Flow {\n const cursor: Cursor = { x: 0, y: 0 };\n let pageNumber = 0;\n\n return {\n get page() {\n return page;\n },\n get cursor() {\n return cursor;\n },\n get pageNumber() {\n return pageNumber;\n },\n contentWidth: () => contentWidth(page),\n contentHeight: () => contentHeight(page),\n\n isLastPageEmpty: () => cursor.y === 0,\n\n needNewPage: (dy) => cursor.y + dy >= contentHeight(page),\n\n newPage: () => {\n pageNumber += 1;\n cursor.x = 0;\n cursor.y = 0;\n },\n\n advance: (dy) => {\n cursor.y += dy;\n },\n };\n}\n","import { marked, type Tokens, type Token } from \"marked\";\nimport type {\n InlineNode,\n ListBlock,\n ListItem,\n MarkdownBlock,\n MarkdownDocument,\n} from \"./types\";\n\nexport function parseMarkdown(markdown: string): MarkdownDocument {\n const tokens = marked.lexer(markdown);\n\n const blocks: MarkdownBlock[] = [];\n\n for (const token of tokens) {\n const block = parseBlock(token);\n\n if (block) {\n blocks.push(block);\n }\n }\n\n return { blocks };\n}\n\nfunction parseBlock(token: Token): MarkdownBlock | null {\n switch (token.type) {\n case \"heading\": {\n const heading = token as Tokens.Heading;\n const level = clampHeadingLevel(heading.depth);\n return { type: \"heading\", level, children: parseInline(heading.tokens) };\n }\n\n case \"paragraph\":\n return { type: \"paragraph\", children: parseInline(token.tokens ?? []) };\n\n case \"list\":\n return parseList(token as Tokens.List);\n\n case \"hr\":\n return { type: \"thematicBreak\" };\n\n case \"blockquote\":\n return parseBlockquote(token as Tokens.Blockquote);\n\n case \"space\":\n return null;\n\n default:\n return null;\n }\n}\n\nfunction parseBlockquote(token: Tokens.Blockquote): MarkdownBlock {\n const children: InlineNode[] = [];\n\n for (const block of token.tokens ?? []) {\n if (block.type === \"paragraph\") {\n if (children.length > 0) {\n children.push({ type: \"text\", text: \" \" });\n }\n children.push(...parseInline((block as Tokens.Paragraph).tokens ?? []));\n }\n }\n\n return { type: \"blockquote\", children };\n}\n\nfunction parseList(token: Tokens.List): ListBlock {\n return {\n type: \"list\",\n ordered: token.ordered,\n items: token.items.map(parseListItem),\n };\n}\n\nfunction parseListItem(item: Tokens.ListItem): ListItem {\n const children: InlineNode[] = [];\n const nested: ListBlock[] = [];\n\n for (const block of item.tokens ?? []) {\n if (block.type === \"list\") {\n nested.push(parseList(block as Tokens.List));\n } else if (\"tokens\" in block && block.tokens) {\n children.push(...parseInline(block.tokens));\n }\n }\n\n return { children, ...(nested.length > 0 ? { nested } : {}) };\n}\n\nfunction clampHeadingLevel(depth: number): 1 | 2 | 3 {\n return Math.min(Math.max(depth, 1), 3) as 1 | 2 | 3;\n}\n\nfunction parseInline(tokens: Token[]): InlineNode[] {\n const nodes: InlineNode[] = [];\n\n for (const token of tokens) {\n switch (token.type) {\n case \"text\": {\n const text = token as Tokens.Text;\n nodes.push({ type: \"text\", text: text.text });\n break;\n }\n\n case \"strong\": {\n const strong = token as Tokens.Strong;\n nodes.push({ type: \"strong\", children: parseInline(strong.tokens) });\n break;\n }\n\n case \"em\": {\n const em = token as Tokens.Em;\n nodes.push({ type: \"emphasis\", children: parseInline(em.tokens) });\n break;\n }\n\n case \"link\": {\n const link = token as Tokens.Link;\n nodes.push({ type: \"link\", text: link.text, href: link.href });\n break;\n }\n\n case \"image\": {\n const image = token as Tokens.Image;\n nodes.push({ type: \"image\", href: image.href });\n break;\n }\n\n case \"codespan\": {\n const codespan = token as Tokens.Codespan;\n nodes.push({ type: \"text\", text: codespan.text });\n break;\n }\n }\n }\n\n return nodes;\n}\n","import { marked } from \"marked\";\nimport { parseMarkdown } from \"../parser\";\nimport type {\n HeadingBlock,\n InlineNode,\n MarkdownBlock,\n MarkdownDocument,\n} from \"../parser/types\";\n\nexport type Severity = \"error\" | \"warning\";\n\nexport type Issue = {\n severity: Severity;\n blockIndex?: number;\n message: string;\n};\n\nexport type ValidationResult = {\n issues: Issue[];\n valid: boolean;\n};\n\nconst EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$/i;\nconst RAW_EMAIL_REGEX = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/i;\n\nexport function validateCV(markdown: string): ValidationResult {\n const doc = parseMarkdown(markdown);\n\n if (doc.blocks.length === 0) {\n return { issues: [{ severity: \"error\", message: \"Document is empty.\" }], valid: false };\n }\n\n const issues: Issue[] = [];\n\n const hasName = doc.blocks.some((b) => b.type === \"heading\" && b.level === 1);\n if (!hasName) {\n issues.push({\n severity: \"error\",\n message: 'Missing a level-1 heading. Add \"# Your Name\" for the candidate name.',\n });\n }\n\n validateContact(doc, issues);\n validateHierarchy(doc, issues);\n validateSection(doc, \"experience\", issues);\n validateSection(doc, \"education\", issues);\n detectUnsupported(markdown, issues);\n\n return { issues, valid: issues.every((i) => i.severity !== \"error\") };\n}\n\nfunction validateContact(doc: MarkdownDocument, issues: Issue[]): void {\n const texts = flattenText(doc);\n const links = flattenLinks(doc);\n\n const emails = new Set<string>();\n for (const t of texts) {\n const m = t.match(RAW_EMAIL_REGEX);\n if (m) emails.add(m[0]);\n }\n for (const href of links) {\n if (href.startsWith(\"mailto:\")) emails.add(href.slice(\"mailto:\".length).split(\"?\")[0] ?? \"\");\n }\n\n const validEmails = [...emails].filter((e) => EMAIL_REGEX.test(e));\n const hasContactLink = links.some((href) => {\n const lower = href.toLowerCase();\n return /mailto:|github\\.com|linkedin\\.com|twitter\\.com|x\\.com|dev\\.to|t\\.me/.test(lower);\n });\n\n if (validEmails.length === 0 && !hasContactLink) {\n issues.push({\n severity: \"warning\",\n message: 'No contact info found. Add an email or a contact link (e.g. mailto:, github, linkedin).',\n });\n }\n\n if ([...emails].some((e) => !EMAIL_REGEX.test(e))) {\n issues.push({\n severity: \"warning\",\n message: `Invalid email address found: \"${[...emails].filter((e) => !EMAIL_REGEX.test(e)).join(\", \")}\".`,\n });\n }\n}\n\nfunction validateHierarchy(doc: MarkdownDocument, issues: Issue[]): void {\n let prevLevel = 0;\n\n doc.blocks.forEach((block, index) => {\n if (block.type !== \"heading\") return;\n if (prevLevel !== 0 && block.level > prevLevel + 1) {\n issues.push({\n severity: \"warning\",\n blockIndex: index,\n message: `Heading level skipped from H${prevLevel} to H${block.level}. Prefer H1 → H2 → H3 order.`,\n });\n }\n prevLevel = block.level;\n });\n}\n\nfunction validateSection(doc: MarkdownDocument, sectionName: string, issues: Issue[]): void {\n const headings: { block: HeadingBlock; index: number }[] = [];\n doc.blocks.forEach((block, index) => {\n if (block.type === \"heading\") headings.push({ block, index });\n });\n\n const target = headings.find(\n ({ block }) => block.level === 2 && headingText(block).toLowerCase() === sectionName,\n );\n\n if (!target) {\n issues.push({\n severity: \"warning\",\n message: `Section \"${sectionName}\" not found. Add \"## ${sectionName}\" heading.`,\n });\n return;\n }\n\n const body: { block: MarkdownBlock; index: number }[] = [];\n const nextSection = doc.blocks\n .slice(target.index + 1)\n .findIndex((b) => b.type === \"heading\" && b.level === 2);\n const end = nextSection === -1 ? doc.blocks.length : target.index + 1 + nextSection;\n\n for (let k = target.index + 1; k < end; k++) {\n const block = doc.blocks[k];\n if (block) body.push({ block, index: k });\n }\n\n const hasBody = body.some(({ block }) => block.type !== \"heading\" && block.type !== \"thematicBreak\");\n if (!hasBody) {\n issues.push({\n severity: \"warning\",\n blockIndex: target.index,\n message: `Section \"${sectionName}\" has no content below its heading.`,\n });\n return;\n }\n\n const entries = body.filter(\n (s): s is { block: HeadingBlock; index: number } =>\n s.block.type === \"heading\" && s.block.level === 3,\n );\n\n entries.forEach(({ block, index }) => {\n const hasContent = body\n .filter((s) => s.index > index)\n .some(({ block: b }) => b.type !== \"heading\" && b.type !== \"thematicBreak\");\n if (!hasContent) {\n issues.push({\n severity: \"warning\",\n blockIndex: index,\n message: `Entry \"${headingText(block)}\" in \"${sectionName}\" has no content below it.`,\n });\n }\n });\n}\n\nfunction detectUnsupported(markdown: string, issues: Issue[]): void {\n const supported = new Set([\"space\", \"heading\", \"paragraph\", \"list\", \"hr\", \"blockquote\"]);\n\n for (const token of marked.lexer(markdown)) {\n if (!supported.has(token.type)) {\n issues.push({\n severity: \"warning\",\n message: `Unsupported Markdown ignored: \"${token.type}\".`,\n });\n }\n }\n}\n\nfunction headingText(block: HeadingBlock): string {\n return inlineText(block.children);\n}\n\nfunction inlineText(nodes: InlineNode[]): string {\n let out = \"\";\n for (const node of nodes) {\n switch (node.type) {\n case \"text\":\n out += node.text;\n break;\n case \"strong\":\n case \"emphasis\":\n out += inlineText(node.children);\n break;\n case \"link\":\n out += node.text;\n break;\n case \"image\":\n break;\n }\n }\n return out;\n}\n\nfunction flattenText(doc: MarkdownDocument): string[] {\n const out: string[] = [];\n for (const block of doc.blocks) {\n if (block.type === \"paragraph\") out.push(inlineText(block.children));\n else if (block.type === \"heading\") out.push(inlineText(block.children));\n }\n return out;\n}\n\nfunction flattenLinks(doc: MarkdownDocument): string[] {\n const out: string[] = [];\n const walk = (nodes: InlineNode[]): void => {\n for (const node of nodes) {\n if (node.type === \"link\") out.push(node.href);\n else if (node.type === \"strong\" || node.type === \"emphasis\") walk(node.children);\n }\n };\n for (const block of doc.blocks) {\n if (block.type === \"paragraph\") walk(block.children);\n else if (block.type === \"heading\") walk(block.children);\n }\n return out;\n}\n","import type { Theme } from \"./types\";\n\nexport function createDefaultTheme(): Theme {\n return {\n name: \"ats-simple\",\n text: {\n name: { family: \"Helvetica\", size: 22, weight: \"bold\" },\n section: { family: \"Helvetica\", size: 13, weight: \"bold\" },\n entryTitle: { family: \"Helvetica\", size: 11, weight: \"bold\" },\n body: { family: \"Helvetica\", size: 10, weight: \"normal\" },\n },\n color: {\n ink: { r: 0.1, g: 0.1, b: 0.1 },\n accent: { r: 0.05, g: 0.15, b: 0.4 },\n muted: { r: 0.35, g: 0.35, b: 0.35 },\n },\n spacing: {\n sectionGap: 6,\n entryGap: 6,\n entrySpacing: 8,\n lineHeight: 1.35,\n columnGap: 8,\n headerGap: 8,\n },\n };\n}\n","import { PDFDocument } from \"pdf-lib\";\nimport { parseMarkdown } from \"../parser\";\nimport { createDefaultTheme, type Theme } from \"../theme\";\nimport { renderDocument, type FontSet, type ProfileImage } from \"./render\";\nimport type { PageConfig } from \"../page\";\n\nexport type RenderMarkdownOptions = {\n theme?: Theme;\n page?: PageConfig;\n profile?: ProfileImage;\n fonts?: FontSet;\n};\n\nexport async function renderMarkdownToPdf(\n markdown: string,\n opts: RenderMarkdownOptions = {},\n): Promise<Uint8Array> {\n const doc = await PDFDocument.create();\n const theme = opts.theme ?? createDefaultTheme();\n await renderDocument(doc, parseMarkdown(markdown), {\n theme,\n page: opts.page,\n profile: opts.profile,\n fonts: opts.fonts,\n });\n return doc.save();\n}\n\nexport { renderDocument } from \"./render\";\nexport type { FontSet, ProfileImage } from \"./render\";","import { PDFDocument, PDFString, StandardFonts, rgb, type PDFFont, type PDFPage, type PDFImage } from \"pdf-lib\";\nimport fontkit from \"@pdf-lib/fontkit\";import type { MarkdownBlock, MarkdownDocument, InlineNode } from \"../parser/types\";\nimport { createA4, createFlow, type PageConfig, type Points, type Flow } from \"../page\";\nimport type { FontSpec, Theme, RGB } from \"../theme\";\nimport type { PdfStyle } from \"./fonts\";\nimport { measureWidth, wrapRuns, type Run } from \"./measure\";\nimport { sanitizeText } from \"./sanitize\";\n\nexport type ProfilePosition = \"left\" | \"center\" | \"right\";\n\nexport type ProfileImage = {\n /** PNG or JPEG bytes. */\n bytes: Uint8Array;\n /** Side length of the (square) photo in points. Default 96. */\n size?: number;\n /** Horizontal position within the header. Default \"right\". */\n position?: ProfilePosition;\n};\n\ntype Ctx = {\n doc: PDFDocument;\n flow: Flow;\n page: PageConfig;\n theme: Theme;\n font: PDFFont;\n bold: PDFFont;\n italic: PDFFont;\n profile: { image: PDFImage; size: number; position: ProfilePosition } | null;\n sanitize: boolean;\n};\n\nexport type FontSet = {\n /** Regular weight TTF bytes (e.g. Noto Sans CJK). */\n regular: Uint8Array;\n bold?: Uint8Array;\n italic?: Uint8Array;\n};\n\nexport type RenderOptions = {\n theme?: Theme;\n page?: PageConfig;\n profile?: ProfileImage;\n fonts?: FontSet;\n};\n\n/** Fraction of the em-size where the text baseline sits (Helvetica ascent). */\nconst ASCENT = 0.72;\n\ntype StandardFamily = {\n regular: StandardFonts;\n bold: StandardFonts;\n italic: StandardFonts;\n};\n\nconst STANDARD_FONTS: Record<string, StandardFamily> = {\n helvetica: {\n regular: StandardFonts.Helvetica,\n bold: StandardFonts.HelveticaBold,\n italic: StandardFonts.HelveticaOblique,\n },\n times: {\n regular: StandardFonts.TimesRoman,\n bold: StandardFonts.TimesRomanBold,\n italic: StandardFonts.TimesRomanItalic,\n },\n courier: {\n regular: StandardFonts.Courier,\n bold: StandardFonts.CourierBold,\n italic: StandardFonts.CourierOblique,\n },\n};\n\n/**\n * Map a theme `family` name to a set of bundled standard fonts, falling back\n * to Helvetica for unknown families.\n */\nfunction resolveStandardFamily(family: string | undefined): StandardFamily {\n const key = (family ?? \"helvetica\").toLowerCase();\n return STANDARD_FONTS[key] ?? STANDARD_FONTS.helvetica!;\n}\n\nexport async function renderDocument(doc: PDFDocument, document: MarkdownDocument, opts: RenderOptions = {}): Promise<void> {\n const theme = opts.theme;\n if (!theme) throw new Error(\"renderDocument requires a theme\");\n const page = opts.page ?? createA4({ top: 40, right: 40, bottom: 40, left: 40 });\n const flow = createFlow(page);\n createPage(doc, page);\n\n if (opts.fonts) doc.registerFontkit(fontkit);\n\n const standard = resolveStandardFamily(theme.text.body.family);\n\n const font = opts.fonts?.regular\n ? await doc.embedFont(opts.fonts.regular, { subset: true })\n : await doc.embedFont(standard.regular);\n const bold = opts.fonts?.bold\n ? await doc.embedFont(opts.fonts.bold, { subset: true })\n : opts.fonts?.regular\n ? font\n : await doc.embedFont(standard.bold);\n const italic = opts.fonts?.italic\n ? await doc.embedFont(opts.fonts.italic, { subset: true })\n : opts.fonts?.regular\n ? font\n : await doc.embedFont(standard.italic);\n\n const profile = opts.profile\n ? {\n image: await embedImage(doc, opts.profile.bytes),\n size: opts.profile.size ?? 96,\n position: opts.profile.position ?? \"right\",\n }\n : null;\n\n const sanitize = !opts.fonts?.regular;\n\n const ctx: Ctx = { doc, flow, page, theme, font, bold, italic, profile, sanitize };\n\n renderProfile(ctx);\n\n for (const block of document.blocks) {\n renderBlock(ctx, block);\n }\n}\n\nfunction renderBlock(ctx: Ctx, block: MarkdownBlock): void {\n switch (block.type) {\n case \"heading\":\n renderHeading(ctx, block);\n break;\n case \"paragraph\":\n renderParagraph(ctx, block.children, specToStyle(ctx, ctx.theme.text.body));\n break;\n case \"list\":\n renderList(ctx, block);\n break;\n case \"thematicBreak\":\n renderThematicBreak(ctx);\n break;\n case \"blockquote\":\n renderBlockquote(ctx, block);\n break;\n }\n}\n\nfunction specToStyle(ctx: Ctx, spec: FontSpec): PdfStyle {\n const weight = spec.weight === \"bold\" ? \"bold\" : \"normal\";\n return { font: weight === \"bold\" ? ctx.bold : ctx.font, size: spec.size };\n}\n\nfunction lineHeightFor(ctx: Ctx, size: Points): Points {\n return size * ctx.theme.spacing.lineHeight;\n}\n\n/** Advance the cursor down by `pts`, breaking to a new page when needed. */\nfunction gap(ctx: Ctx, pts: Points): void {\n if (pts <= 0) return;\n if (ctx.flow.needNewPage(pts)) ensurePage(ctx);\n ctx.flow.advance(pts);\n}\n\nfunction renderHeading(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"heading\" }>): void {\n const spacing = ctx.theme.spacing;\n\n // Space above the heading, based on its level.\n if (block.level === 2) gap(ctx, spacing.sectionGap);\n else if (block.level === 3) gap(ctx, spacing.entrySpacing);\n\n const spec =\n block.level === 1 ? ctx.theme.text.name : block.level === 2 ? ctx.theme.text.section : ctx.theme.text.entryTitle;\n const color = block.level === 2 ? ctx.theme.color.accent : ctx.theme.color.ink;\n const style = specToStyle(ctx, spec);\n renderLines(ctx, block.children, style, color);\n\n // Space below the heading before its content.\n if (block.level === 1) gap(ctx, spacing.headerGap);\n else gap(ctx, spacing.entryGap);\n}\n\nfunction renderParagraph(ctx: Ctx, nodes: InlineNode[], spec: PdfStyle): void {\n const spacing = ctx.theme.spacing;\n renderLines(ctx, nodes, spec, ctx.theme.color.ink);\n gap(ctx, spacing.entryGap);\n}\n\nfunction renderProfile(ctx: Ctx): void {\n if (!ctx.profile) return;\n\n const { image, size, position } = ctx.profile;\n const pdfPage = currentPdfPage(ctx);\n const contentWidth = ctx.flow.contentWidth();\n\n let x: number;\n if (position === \"left\") x = marginLeft(ctx);\n else if (position === \"center\") x = marginLeft(ctx) + (contentWidth - size) / 2;\n else x = marginLeft(ctx) + contentWidth - size;\n\n const y = yToPdf(ctx, size);\n pdfPage.drawImage(image, { x, y, width: size, height: size });\n}\n\nfunction renderBlockquote(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"blockquote\" }>): void {\n const spacing = ctx.theme.spacing;\n const spec = specToStyle(ctx, ctx.theme.text.body);\n renderLines(ctx, block.children, spec, ctx.theme.color.muted);\n gap(ctx, spacing.entryGap);\n}\n\nfunction renderLines(ctx: Ctx, nodes: InlineNode[], spec: PdfStyle, color: RGB): void {\n const runs = nodesToRuns(ctx, nodes, spec);\n const maxWidth = ctx.flow.contentWidth();\n const lines = wrapRuns(runs, maxWidth);\n const lineHeight = lineHeightFor(ctx, spec.size);\n\n for (const line of lines) {\n if (ctx.flow.needNewPage(lineHeight)) ensurePage(ctx);\n drawLine(ctx, line, color);\n ctx.flow.advance(lineHeight);\n }\n}\n\nfunction renderList(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"list\" }>): void {\n const spacing = ctx.theme.spacing;\n const spec = specToStyle(ctx, ctx.theme.text.body);\n const lineHeight = lineHeightFor(ctx, spec.size);\n const indent = spec.size * 1.2;\n\n for (const item of block.items) {\n const bullet = block.ordered ? `${block.items.indexOf(item) + 1}. ` : \"• \";\n const runs: Run[] = [{ text: bullet, style: spec }];\n const wrapped = wrapRuns([...runs, ...nodesToRuns(ctx, item.children, spec)], ctx.flow.contentWidth() - indent);\n\n wrapped.forEach((line, i) => {\n const padded = i === 0 ? line : padRuns(line, indent, spec);\n if (ctx.flow.needNewPage(lineHeight)) ensurePage(ctx);\n drawLine(ctx, padded, ctx.theme.color.ink);\n ctx.flow.advance(lineHeight);\n });\n\n if (item.nested) {\n for (const sub of item.nested) {\n renderNestedList(ctx, sub, indent);\n }\n }\n }\n\n gap(ctx, spacing.entryGap);\n}\n\nfunction renderNestedList(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"list\" }>, baseIndent: Points): void {\n const spec = specToStyle(ctx, ctx.theme.text.body);\n const lineHeight = lineHeightFor(ctx, spec.size);\n const indent = baseIndent + spec.size * 1.2;\n\n for (const item of block.items) {\n const bullet = block.ordered ? `${block.items.indexOf(item) + 1}. ` : \"• \";\n const runs: Run[] = [{ text: bullet, style: spec }];\n const wrapped = wrapRuns([...runs, ...nodesToRuns(ctx, item.children, spec)], ctx.flow.contentWidth() - indent);\n\n wrapped.forEach((line, i) => {\n const padded = i === 0 ? padRuns(line, baseIndent, spec) : padRuns(line, indent, spec);\n if (ctx.flow.needNewPage(lineHeight)) ensurePage(ctx);\n drawLine(ctx, padded, ctx.theme.color.ink);\n ctx.flow.advance(lineHeight);\n });\n\n if (item.nested) {\n for (const sub of item.nested) {\n renderNestedList(ctx, sub, indent);\n }\n }\n }\n}\n\nfunction renderThematicBreak(ctx: Ctx): void {\n const spacing = ctx.theme.spacing;\n const y = ctx.flow.cursor.y;\n const h = 0.75;\n if (ctx.flow.needNewPage(spacing.entryGap + h)) ensurePage(ctx);\n const pdfPage = currentPdfPage(ctx);\n pdfPage.drawRectangle({\n x: marginLeft(ctx),\n y: yToPdf(ctx, y + spacing.entryGap + h),\n width: ctx.flow.contentWidth(),\n height: h,\n color: toColor(ctx.theme.color.muted),\n });\n ctx.flow.advance(spacing.entryGap * 2 + h);\n}\n\nfunction nodesToRuns(ctx: Ctx, nodes: InlineNode[], base: PdfStyle): Run[] {\n const runs: Run[] = [];\n const walk = (list: InlineNode[], style: PdfStyle): void => {\n for (const node of list) {\n switch (node.type) {\n case \"text\":\n if (node.text) runs.push({ text: ctx.sanitize ? sanitizeText(node.text) : node.text, style });\n break;\n case \"strong\":\n walk(node.children, { font: ctx.bold, size: style.size });\n break;\n case \"emphasis\":\n walk(node.children, { font: ctx.italic, size: style.size });\n break;\n case \"link\":\n runs.push({ text: ctx.sanitize ? sanitizeText(node.text) : node.text, style, href: node.href });\n break;\n case \"image\":\n break;\n }\n }\n };\n walk(nodes, base);\n return runs;\n}\n\nfunction padRuns(runs: Run[], pad: Points, spec: PdfStyle): Run[] {\n if (pad <= 0) return runs;\n return [{ text: \" \".repeat(Math.max(1, Math.round(pad / (spec.size * 0.5)))), style: spec }, ...runs];\n}\n\nfunction drawLine(ctx: Ctx, line: Run[], color: RGB): void {\n const pdfPage = currentPdfPage(ctx);\n const size = Math.max(...line.map((r) => r.style.size));\n const baseline = ctx.flow.cursor.y + size * ASCENT;\n const baselinePdf = yToPdf(ctx, baseline);\n let x = marginLeft(ctx);\n for (const run of line) {\n const runColor = run.href ? ctx.theme.color.accent : color;\n pdfPage.drawText(run.text, {\n x,\n y: baselinePdf,\n size: run.style.size,\n font: run.style.font,\n color: toColor(runColor),\n });\n const width = measureWidth(run);\n if (run.href && width > 0) {\n addLinkAnnotation(\n ctx,\n pdfPage,\n run.href,\n x,\n baselinePdf - run.style.size * 0.25,\n x + width,\n baselinePdf + run.style.size * 0.85,\n );\n }\n x += width;\n }\n}\n\n/** Attach a clickable URI link annotation over a run's bounding box. */\nfunction addLinkAnnotation(ctx: Ctx, pdfPage: PDFPage, href: string, x1: number, y1: number, x2: number, y2: number): void {\n const context = ctx.doc.context;\n const annot = context.obj({\n Type: \"Annot\",\n Subtype: \"Link\",\n Rect: [x1, y1, x2, y2],\n Border: [0, 0, 0],\n A: { Type: \"Action\", S: \"URI\", URI: PDFString.of(href) },\n });\n pdfPage.node.addAnnot(context.register(annot));\n}\n\nfunction marginLeft(ctx: Ctx): number {\n return ctx.page.margin.left;\n}\n\n/** Convert flow Y (from content-area top) to pdf-lib Y (from page bottom). */\nfunction yToPdf(ctx: Ctx, y: Points): Points {\n return ctx.page.size.height - ctx.page.margin.top - y;\n}\n\nfunction toColor(c: RGB): ReturnType<typeof rgb> {\n return rgb(c.r, c.g, c.b);\n}\n\nfunction createPage(doc: PDFDocument, page: PageConfig): void {\n doc.addPage([page.size.width, page.size.height]);\n}\n\n/** Embed PNG or JPEG bytes into the document, auto-detecting the format. */\nasync function embedImage(doc: PDFDocument, bytes: Uint8Array): Promise<PDFImage> {\n const isPng = bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47;\n const isJpeg = bytes[0] === 0xff && bytes[1] === 0xd8;\n if (isPng) return doc.embedPng(bytes);\n if (isJpeg) return doc.embedJpg(bytes);\n throw new Error(\"Unsupported image format: only PNG and JPEG are supported.\");\n}\nfunction ensurePage(ctx: Ctx): void {\n ctx.flow.newPage();\n ctx.doc.addPage([ctx.page.size.width, ctx.page.size.height]);\n}\n\nfunction currentPdfPage(ctx: Ctx): PDFPage {\n return ctx.doc.getPage(ctx.flow.pageNumber);\n}\n","import type { PdfStyle } from \"./fonts\";\n\n/** A styled run of text. `href` marks the run as a clickable hyperlink. */\nexport type Run = {\n text: string;\n style: PdfStyle;\n href?: string;\n};\n\nexport function measureWidth(run: Run): number {\n return run.style.font.widthOfTextAtSize(run.text, run.style.size);\n}\n\nexport function measureLine(line: Run[]): number {\n return line.reduce((sum, run) => sum + measureWidth(run), 0);\n}\n\n/**\n * Greedy word-wrap: pack styled runs into lines that fit `maxWidth`.\n * Words are never split; a single word wider than the box wraps onto its own line.\n */\nexport function wrapRuns(runs: Run[], maxWidth: number): Run[][] {\n if (runs.length === 0) return [];\n\n const words: Run[] = [];\n for (const run of runs) {\n const parts = run.text.split(/(\\s+)/);\n for (const part of parts) {\n if (part === \"\" || /^\\s+$/.test(part)) continue;\n words.push({ text: part, style: run.style, href: run.href });\n }\n }\n\n const lines: Run[][] = [];\n let current: Run[] = [];\n let currentWidth = 0;\n\n for (const word of words) {\n const w = measureWidth(word);\n const gapWidth = current.length === 0 ? 0 : measureWidth({ text: \" \", style: word.style });\n\n if (current.length > 0 && currentWidth + gapWidth + w > maxWidth) {\n lines.push(current);\n current = [{ ...word }];\n currentWidth = w;\n continue;\n }\n\n if (current.length > 0) {\n current.push({ text: \" \", style: word.style });\n currentWidth += gapWidth;\n }\n current.push({ ...word });\n currentWidth += w;\n }\n\n if (current.length > 0) lines.push(current);\n return lines;\n}","// Characters representable by the WinAnsi (CP1252) encoding used by the\n// standard PDF fonts (Helvetica, Times, Courier). Anything outside this set\n// would make pdf-lib throw, so we sanitize text before measuring/drawing.\n//\n// WinAnsi covers: ASCII (0x20-0x7E), Latin-1 (0xA0-0xFF), plus a handful of\n// CP1252 specials (curly quotes, dashes, bullet, euro, etc.).\n\nconst CP1252_SPECIALS = new Set<number>([\n 0x20ac, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030,\n 0x0160, 0x2039, 0x0152, 0x017d, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022,\n 0x2013, 0x2014, 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x017e, 0x0178,\n]);\n\n/** Common unsupported punctuation mapped to a WinAnsi-safe equivalent. */\nconst FALLBACKS: Record<number, string> = {\n 0x30fb: \"\\u00b7\", // ・ -> ·\n 0x3001: \",\", // 、-> ,\n 0xff0c: \",\", // ,-> ,\n 0x3002: \".\", // 。-> .\n 0xff1f: \"?\", // ?-> ?\n 0xff01: \"!\", // !-> !\n 0x201c: '\"', // \" -> \"\n 0x201d: '\"', // \" -> \"\n 0x2018: \"'\", // ' -> '\n 0x2019: \"'\", // ' -> '\n};\n\nfunction isWinAnsi(code: number): boolean {\n if (code >= 0x20 && code <= 0x7e) return true;\n if (code >= 0xa0 && code <= 0xff) return true;\n return CP1252_SPECIALS.has(code);\n}\n\n/**\n * Replace characters that cannot be encoded by standard PDF fonts with a safe\n * equivalent, or drop them. This keeps CJK/kana text from crashing the render.\n */\nexport function sanitizeText(text: string): string {\n let out = \"\";\n for (const ch of text) {\n const code = ch.codePointAt(0)!;\n if (isWinAnsi(code)) {\n out += ch;\n continue;\n }\n const fallback = FALLBACKS[code];\n if (fallback) out += fallback;\n // otherwise: drop the character\n }\n return out;\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { validateCV, type Issue } from \"./validator\";\nimport { renderMarkdownToPdf, type FontSet, type ProfileImage } from \"./renderer\";\nimport { createDefaultTheme, type Theme } from \"./theme\";\nimport type { PageConfig } from \"./page\";\n\nexport type GenerateCVOptions = {\n /** Path to the source Markdown file. */\n input: string;\n /** Path where the generated PDF will be written. */\n output: string;\n /** Profile photo to render in the header. */\n profile?: ProfileImage;\n /** Custom TTF fonts for non-Latin text (e.g. Noto Sans CJK). */\n fonts?: FontSet;\n theme?: Theme;\n page?: PageConfig;\n};\n\nexport type GenerateCVResult = {\n /** All validation issues found (warnings only; errors throw). */\n issues: Issue[];\n /** Path the PDF was written to. */\n output: string;\n};\n\nexport async function generateCV(options: GenerateCVOptions): Promise<GenerateCVResult> {\n const markdown = await readFile(options.input, \"utf8\");\n\n const validation = validateCV(markdown);\n const errors = validation.issues.filter((i) => i.severity === \"error\");\n\n if (errors.length > 0) {\n const summary = errors.map((e) => `- ${e.message}`).join(\"\\n\");\n throw new Error(`CV validation failed:\\n${summary}`);\n }\n\n const theme = options.theme ?? createDefaultTheme();\n const bytes = await renderMarkdownToPdf(markdown, {\n theme,\n page: options.page,\n profile: options.profile,\n fonts: options.fonts,\n });\n\n await writeFile(options.output, bytes);\n\n return {\n issues: validation.issues,\n output: options.output,\n };\n}\n"],"mappings":"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","names":["import_marked","import_pdf_lib","fontkit","contentWidth"]}
|
package/dist/index.js
CHANGED
|
@@ -352,7 +352,7 @@ function createDefaultTheme() {
|
|
|
352
352
|
import { PDFDocument as PDFDocument2 } from "pdf-lib";
|
|
353
353
|
|
|
354
354
|
// src/renderer/render.ts
|
|
355
|
-
import { StandardFonts, rgb } from "pdf-lib";
|
|
355
|
+
import { PDFString, StandardFonts, rgb } from "pdf-lib";
|
|
356
356
|
import fontkit from "@pdf-lib/fontkit";
|
|
357
357
|
|
|
358
358
|
// src/renderer/measure.ts
|
|
@@ -366,7 +366,7 @@ function wrapRuns(runs, maxWidth) {
|
|
|
366
366
|
const parts = run.text.split(/(\s+)/);
|
|
367
367
|
for (const part of parts) {
|
|
368
368
|
if (part === "" || /^\s+$/.test(part)) continue;
|
|
369
|
-
words.push({ text: part, style: run.style });
|
|
369
|
+
words.push({ text: part, style: run.style, href: run.href });
|
|
370
370
|
}
|
|
371
371
|
}
|
|
372
372
|
const lines = [];
|
|
@@ -659,7 +659,7 @@ function nodesToRuns(ctx, nodes, base) {
|
|
|
659
659
|
walk(node.children, { font: ctx.italic, size: style.size });
|
|
660
660
|
break;
|
|
661
661
|
case "link":
|
|
662
|
-
runs.push({ text: ctx.sanitize ? sanitizeText(node.text) : node.text, style });
|
|
662
|
+
runs.push({ text: ctx.sanitize ? sanitizeText(node.text) : node.text, style, href: node.href });
|
|
663
663
|
break;
|
|
664
664
|
case "image":
|
|
665
665
|
break;
|
|
@@ -677,18 +677,43 @@ function drawLine(ctx, line, color) {
|
|
|
677
677
|
const pdfPage = currentPdfPage(ctx);
|
|
678
678
|
const size = Math.max(...line.map((r) => r.style.size));
|
|
679
679
|
const baseline = ctx.flow.cursor.y + size * ASCENT;
|
|
680
|
+
const baselinePdf = yToPdf(ctx, baseline);
|
|
680
681
|
let x = marginLeft(ctx);
|
|
681
682
|
for (const run of line) {
|
|
683
|
+
const runColor = run.href ? ctx.theme.color.accent : color;
|
|
682
684
|
pdfPage.drawText(run.text, {
|
|
683
685
|
x,
|
|
684
|
-
y:
|
|
686
|
+
y: baselinePdf,
|
|
685
687
|
size: run.style.size,
|
|
686
688
|
font: run.style.font,
|
|
687
|
-
color: toColor(
|
|
689
|
+
color: toColor(runColor)
|
|
688
690
|
});
|
|
689
|
-
|
|
691
|
+
const width = measureWidth(run);
|
|
692
|
+
if (run.href && width > 0) {
|
|
693
|
+
addLinkAnnotation(
|
|
694
|
+
ctx,
|
|
695
|
+
pdfPage,
|
|
696
|
+
run.href,
|
|
697
|
+
x,
|
|
698
|
+
baselinePdf - run.style.size * 0.25,
|
|
699
|
+
x + width,
|
|
700
|
+
baselinePdf + run.style.size * 0.85
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
x += width;
|
|
690
704
|
}
|
|
691
705
|
}
|
|
706
|
+
function addLinkAnnotation(ctx, pdfPage, href, x1, y1, x2, y2) {
|
|
707
|
+
const context = ctx.doc.context;
|
|
708
|
+
const annot = context.obj({
|
|
709
|
+
Type: "Annot",
|
|
710
|
+
Subtype: "Link",
|
|
711
|
+
Rect: [x1, y1, x2, y2],
|
|
712
|
+
Border: [0, 0, 0],
|
|
713
|
+
A: { Type: "Action", S: "URI", URI: PDFString.of(href) }
|
|
714
|
+
});
|
|
715
|
+
pdfPage.node.addAnnot(context.register(annot));
|
|
716
|
+
}
|
|
692
717
|
function marginLeft(ctx) {
|
|
693
718
|
return ctx.page.margin.left;
|
|
694
719
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/page/config.ts","../src/page/flow.ts","../src/parser/index.ts","../src/validator/index.ts","../src/theme/default.ts","../src/renderer/index.ts","../src/renderer/render.ts","../src/renderer/measure.ts","../src/renderer/sanitize.ts","../src/generate.ts"],"sourcesContent":["export type Points = number;\n\nexport type PageMargins = {\n top: Points;\n right: Points;\n bottom: Points;\n left: Points;\n};\n\nexport type PageSize = {\n width: Points;\n height: Points;\n};\n\nexport type PageConfig = {\n size: PageSize;\n margin: PageMargins;\n};\n\nconst A4 = Object.freeze({ width: 595.28, height: 841.89 });\n\nexport function contentWidth(page: PageConfig): Points {\n return page.size.width - page.margin.left - page.margin.right;\n}\n\nexport function contentHeight(page: PageConfig): Points {\n return page.size.height - page.margin.top - page.margin.bottom;\n}\n\nexport function createA4(margin: Partial<PageMargins> = {}): PageConfig {\n const marginValue: PageMargins = {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n ...margin,\n };\n\n return { size: { ...A4 }, margin: marginValue };\n}\n","import type { PageConfig, Points } from \"./config\";\nimport { contentHeight, contentWidth } from \"./config\";\n\nexport type Cursor = {\n x: Points;\n y: Points;\n};\n\n/**\n * Holds cursor state (relative to the current page's content area, origin at\n * the top-left) and coordinates page-breaks. Does not hardcode any dimension;\n * it asks the page config how much room is left.\n */\nexport type Flow = {\n readonly page: PageConfig;\n readonly cursor: Cursor;\n readonly pageNumber: number;\n contentWidth(): Points;\n contentHeight(): Points;\n isLastPageEmpty(): boolean;\n /** True when advancing `dy` would overflow the current content area. */\n needNewPage(dy: Points): boolean;\n /** Break to the next page, resetting the cursor to the top-left. */\n newPage(): void;\n /** Move the cursor down by `dy`. */\n advance(dy: Points): void;\n};\n\nexport function createFlow(page: PageConfig): Flow {\n const cursor: Cursor = { x: 0, y: 0 };\n let pageNumber = 0;\n\n return {\n get page() {\n return page;\n },\n get cursor() {\n return cursor;\n },\n get pageNumber() {\n return pageNumber;\n },\n contentWidth: () => contentWidth(page),\n contentHeight: () => contentHeight(page),\n\n isLastPageEmpty: () => cursor.y === 0,\n\n needNewPage: (dy) => cursor.y + dy >= contentHeight(page),\n\n newPage: () => {\n pageNumber += 1;\n cursor.x = 0;\n cursor.y = 0;\n },\n\n advance: (dy) => {\n cursor.y += dy;\n },\n };\n}\n","import { marked, type Tokens, type Token } from \"marked\";\nimport type {\n InlineNode,\n ListBlock,\n ListItem,\n MarkdownBlock,\n MarkdownDocument,\n} from \"./types\";\n\nexport function parseMarkdown(markdown: string): MarkdownDocument {\n const tokens = marked.lexer(markdown);\n\n const blocks: MarkdownBlock[] = [];\n\n for (const token of tokens) {\n const block = parseBlock(token);\n\n if (block) {\n blocks.push(block);\n }\n }\n\n return { blocks };\n}\n\nfunction parseBlock(token: Token): MarkdownBlock | null {\n switch (token.type) {\n case \"heading\": {\n const heading = token as Tokens.Heading;\n const level = clampHeadingLevel(heading.depth);\n return { type: \"heading\", level, children: parseInline(heading.tokens) };\n }\n\n case \"paragraph\":\n return { type: \"paragraph\", children: parseInline(token.tokens ?? []) };\n\n case \"list\":\n return parseList(token as Tokens.List);\n\n case \"hr\":\n return { type: \"thematicBreak\" };\n\n case \"blockquote\":\n return parseBlockquote(token as Tokens.Blockquote);\n\n case \"space\":\n return null;\n\n default:\n return null;\n }\n}\n\nfunction parseBlockquote(token: Tokens.Blockquote): MarkdownBlock {\n const children: InlineNode[] = [];\n\n for (const block of token.tokens ?? []) {\n if (block.type === \"paragraph\") {\n if (children.length > 0) {\n children.push({ type: \"text\", text: \" \" });\n }\n children.push(...parseInline((block as Tokens.Paragraph).tokens ?? []));\n }\n }\n\n return { type: \"blockquote\", children };\n}\n\nfunction parseList(token: Tokens.List): ListBlock {\n return {\n type: \"list\",\n ordered: token.ordered,\n items: token.items.map(parseListItem),\n };\n}\n\nfunction parseListItem(item: Tokens.ListItem): ListItem {\n const children: InlineNode[] = [];\n const nested: ListBlock[] = [];\n\n for (const block of item.tokens ?? []) {\n if (block.type === \"list\") {\n nested.push(parseList(block as Tokens.List));\n } else if (\"tokens\" in block && block.tokens) {\n children.push(...parseInline(block.tokens));\n }\n }\n\n return { children, ...(nested.length > 0 ? { nested } : {}) };\n}\n\nfunction clampHeadingLevel(depth: number): 1 | 2 | 3 {\n return Math.min(Math.max(depth, 1), 3) as 1 | 2 | 3;\n}\n\nfunction parseInline(tokens: Token[]): InlineNode[] {\n const nodes: InlineNode[] = [];\n\n for (const token of tokens) {\n switch (token.type) {\n case \"text\": {\n const text = token as Tokens.Text;\n nodes.push({ type: \"text\", text: text.text });\n break;\n }\n\n case \"strong\": {\n const strong = token as Tokens.Strong;\n nodes.push({ type: \"strong\", children: parseInline(strong.tokens) });\n break;\n }\n\n case \"em\": {\n const em = token as Tokens.Em;\n nodes.push({ type: \"emphasis\", children: parseInline(em.tokens) });\n break;\n }\n\n case \"link\": {\n const link = token as Tokens.Link;\n nodes.push({ type: \"link\", text: link.text, href: link.href });\n break;\n }\n\n case \"image\": {\n const image = token as Tokens.Image;\n nodes.push({ type: \"image\", href: image.href });\n break;\n }\n\n case \"codespan\": {\n const codespan = token as Tokens.Codespan;\n nodes.push({ type: \"text\", text: codespan.text });\n break;\n }\n }\n }\n\n return nodes;\n}\n","import { marked } from \"marked\";\nimport { parseMarkdown } from \"../parser\";\nimport type {\n HeadingBlock,\n InlineNode,\n MarkdownBlock,\n MarkdownDocument,\n} from \"../parser/types\";\n\nexport type Severity = \"error\" | \"warning\";\n\nexport type Issue = {\n severity: Severity;\n blockIndex?: number;\n message: string;\n};\n\nexport type ValidationResult = {\n issues: Issue[];\n valid: boolean;\n};\n\nconst EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$/i;\nconst RAW_EMAIL_REGEX = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/i;\n\nexport function validateCV(markdown: string): ValidationResult {\n const doc = parseMarkdown(markdown);\n\n if (doc.blocks.length === 0) {\n return { issues: [{ severity: \"error\", message: \"Document is empty.\" }], valid: false };\n }\n\n const issues: Issue[] = [];\n\n const hasName = doc.blocks.some((b) => b.type === \"heading\" && b.level === 1);\n if (!hasName) {\n issues.push({\n severity: \"error\",\n message: 'Missing a level-1 heading. Add \"# Your Name\" for the candidate name.',\n });\n }\n\n validateContact(doc, issues);\n validateHierarchy(doc, issues);\n validateSection(doc, \"experience\", issues);\n validateSection(doc, \"education\", issues);\n detectUnsupported(markdown, issues);\n\n return { issues, valid: issues.every((i) => i.severity !== \"error\") };\n}\n\nfunction validateContact(doc: MarkdownDocument, issues: Issue[]): void {\n const texts = flattenText(doc);\n const links = flattenLinks(doc);\n\n const emails = new Set<string>();\n for (const t of texts) {\n const m = t.match(RAW_EMAIL_REGEX);\n if (m) emails.add(m[0]);\n }\n for (const href of links) {\n if (href.startsWith(\"mailto:\")) emails.add(href.slice(\"mailto:\".length).split(\"?\")[0] ?? \"\");\n }\n\n const validEmails = [...emails].filter((e) => EMAIL_REGEX.test(e));\n const hasContactLink = links.some((href) => {\n const lower = href.toLowerCase();\n return /mailto:|github\\.com|linkedin\\.com|twitter\\.com|x\\.com|dev\\.to|t\\.me/.test(lower);\n });\n\n if (validEmails.length === 0 && !hasContactLink) {\n issues.push({\n severity: \"warning\",\n message: 'No contact info found. Add an email or a contact link (e.g. mailto:, github, linkedin).',\n });\n }\n\n if ([...emails].some((e) => !EMAIL_REGEX.test(e))) {\n issues.push({\n severity: \"warning\",\n message: `Invalid email address found: \"${[...emails].filter((e) => !EMAIL_REGEX.test(e)).join(\", \")}\".`,\n });\n }\n}\n\nfunction validateHierarchy(doc: MarkdownDocument, issues: Issue[]): void {\n let prevLevel = 0;\n\n doc.blocks.forEach((block, index) => {\n if (block.type !== \"heading\") return;\n if (prevLevel !== 0 && block.level > prevLevel + 1) {\n issues.push({\n severity: \"warning\",\n blockIndex: index,\n message: `Heading level skipped from H${prevLevel} to H${block.level}. Prefer H1 → H2 → H3 order.`,\n });\n }\n prevLevel = block.level;\n });\n}\n\nfunction validateSection(doc: MarkdownDocument, sectionName: string, issues: Issue[]): void {\n const headings: { block: HeadingBlock; index: number }[] = [];\n doc.blocks.forEach((block, index) => {\n if (block.type === \"heading\") headings.push({ block, index });\n });\n\n const target = headings.find(\n ({ block }) => block.level === 2 && headingText(block).toLowerCase() === sectionName,\n );\n\n if (!target) {\n issues.push({\n severity: \"warning\",\n message: `Section \"${sectionName}\" not found. Add \"## ${sectionName}\" heading.`,\n });\n return;\n }\n\n const body: { block: MarkdownBlock; index: number }[] = [];\n const nextSection = doc.blocks\n .slice(target.index + 1)\n .findIndex((b) => b.type === \"heading\" && b.level === 2);\n const end = nextSection === -1 ? doc.blocks.length : target.index + 1 + nextSection;\n\n for (let k = target.index + 1; k < end; k++) {\n const block = doc.blocks[k];\n if (block) body.push({ block, index: k });\n }\n\n const hasBody = body.some(({ block }) => block.type !== \"heading\" && block.type !== \"thematicBreak\");\n if (!hasBody) {\n issues.push({\n severity: \"warning\",\n blockIndex: target.index,\n message: `Section \"${sectionName}\" has no content below its heading.`,\n });\n return;\n }\n\n const entries = body.filter(\n (s): s is { block: HeadingBlock; index: number } =>\n s.block.type === \"heading\" && s.block.level === 3,\n );\n\n entries.forEach(({ block, index }) => {\n const hasContent = body\n .filter((s) => s.index > index)\n .some(({ block: b }) => b.type !== \"heading\" && b.type !== \"thematicBreak\");\n if (!hasContent) {\n issues.push({\n severity: \"warning\",\n blockIndex: index,\n message: `Entry \"${headingText(block)}\" in \"${sectionName}\" has no content below it.`,\n });\n }\n });\n}\n\nfunction detectUnsupported(markdown: string, issues: Issue[]): void {\n const supported = new Set([\"space\", \"heading\", \"paragraph\", \"list\", \"hr\", \"blockquote\"]);\n\n for (const token of marked.lexer(markdown)) {\n if (!supported.has(token.type)) {\n issues.push({\n severity: \"warning\",\n message: `Unsupported Markdown ignored: \"${token.type}\".`,\n });\n }\n }\n}\n\nfunction headingText(block: HeadingBlock): string {\n return inlineText(block.children);\n}\n\nfunction inlineText(nodes: InlineNode[]): string {\n let out = \"\";\n for (const node of nodes) {\n switch (node.type) {\n case \"text\":\n out += node.text;\n break;\n case \"strong\":\n case \"emphasis\":\n out += inlineText(node.children);\n break;\n case \"link\":\n out += node.text;\n break;\n case \"image\":\n break;\n }\n }\n return out;\n}\n\nfunction flattenText(doc: MarkdownDocument): string[] {\n const out: string[] = [];\n for (const block of doc.blocks) {\n if (block.type === \"paragraph\") out.push(inlineText(block.children));\n else if (block.type === \"heading\") out.push(inlineText(block.children));\n }\n return out;\n}\n\nfunction flattenLinks(doc: MarkdownDocument): string[] {\n const out: string[] = [];\n const walk = (nodes: InlineNode[]): void => {\n for (const node of nodes) {\n if (node.type === \"link\") out.push(node.href);\n else if (node.type === \"strong\" || node.type === \"emphasis\") walk(node.children);\n }\n };\n for (const block of doc.blocks) {\n if (block.type === \"paragraph\") walk(block.children);\n else if (block.type === \"heading\") walk(block.children);\n }\n return out;\n}\n","import type { Theme } from \"./types\";\n\nexport function createDefaultTheme(): Theme {\n return {\n name: \"ats-simple\",\n text: {\n name: { family: \"Helvetica\", size: 22, weight: \"bold\" },\n section: { family: \"Helvetica\", size: 13, weight: \"bold\" },\n entryTitle: { family: \"Helvetica\", size: 11, weight: \"bold\" },\n body: { family: \"Helvetica\", size: 10, weight: \"normal\" },\n },\n color: {\n ink: { r: 0.1, g: 0.1, b: 0.1 },\n accent: { r: 0.05, g: 0.15, b: 0.4 },\n muted: { r: 0.35, g: 0.35, b: 0.35 },\n },\n spacing: {\n sectionGap: 6,\n entryGap: 6,\n entrySpacing: 8,\n lineHeight: 1.35,\n columnGap: 8,\n headerGap: 8,\n },\n };\n}\n","import { PDFDocument } from \"pdf-lib\";\nimport { parseMarkdown } from \"../parser\";\nimport { createDefaultTheme, type Theme } from \"../theme\";\nimport { renderDocument, type FontSet, type ProfileImage } from \"./render\";\nimport type { PageConfig } from \"../page\";\n\nexport type RenderMarkdownOptions = {\n theme?: Theme;\n page?: PageConfig;\n profile?: ProfileImage;\n fonts?: FontSet;\n};\n\nexport async function renderMarkdownToPdf(\n markdown: string,\n opts: RenderMarkdownOptions = {},\n): Promise<Uint8Array> {\n const doc = await PDFDocument.create();\n const theme = opts.theme ?? createDefaultTheme();\n await renderDocument(doc, parseMarkdown(markdown), {\n theme,\n page: opts.page,\n profile: opts.profile,\n fonts: opts.fonts,\n });\n return doc.save();\n}\n\nexport { renderDocument } from \"./render\";\nexport type { FontSet, ProfileImage } from \"./render\";","import { PDFDocument, StandardFonts, rgb, type PDFFont, type PDFPage, type PDFImage } from \"pdf-lib\";\nimport fontkit from \"@pdf-lib/fontkit\";import type { MarkdownBlock, MarkdownDocument, InlineNode } from \"../parser/types\";\nimport { createA4, createFlow, type PageConfig, type Points, type Flow } from \"../page\";\nimport type { FontSpec, Theme, RGB } from \"../theme\";\nimport type { PdfStyle } from \"./fonts\";\nimport { measureWidth, wrapRuns, type Run } from \"./measure\";\nimport { sanitizeText } from \"./sanitize\";\n\nexport type ProfilePosition = \"left\" | \"center\" | \"right\";\n\nexport type ProfileImage = {\n /** PNG or JPEG bytes. */\n bytes: Uint8Array;\n /** Side length of the (square) photo in points. Default 96. */\n size?: number;\n /** Horizontal position within the header. Default \"right\". */\n position?: ProfilePosition;\n};\n\ntype Ctx = {\n doc: PDFDocument;\n flow: Flow;\n page: PageConfig;\n theme: Theme;\n font: PDFFont;\n bold: PDFFont;\n italic: PDFFont;\n profile: { image: PDFImage; size: number; position: ProfilePosition } | null;\n sanitize: boolean;\n};\n\nexport type FontSet = {\n /** Regular weight TTF bytes (e.g. Noto Sans CJK). */\n regular: Uint8Array;\n bold?: Uint8Array;\n italic?: Uint8Array;\n};\n\nexport type RenderOptions = {\n theme?: Theme;\n page?: PageConfig;\n profile?: ProfileImage;\n fonts?: FontSet;\n};\n\n/** Fraction of the em-size where the text baseline sits (Helvetica ascent). */\nconst ASCENT = 0.72;\n\ntype StandardFamily = {\n regular: StandardFonts;\n bold: StandardFonts;\n italic: StandardFonts;\n};\n\nconst STANDARD_FONTS: Record<string, StandardFamily> = {\n helvetica: {\n regular: StandardFonts.Helvetica,\n bold: StandardFonts.HelveticaBold,\n italic: StandardFonts.HelveticaOblique,\n },\n times: {\n regular: StandardFonts.TimesRoman,\n bold: StandardFonts.TimesRomanBold,\n italic: StandardFonts.TimesRomanItalic,\n },\n courier: {\n regular: StandardFonts.Courier,\n bold: StandardFonts.CourierBold,\n italic: StandardFonts.CourierOblique,\n },\n};\n\n/**\n * Map a theme `family` name to a set of bundled standard fonts, falling back\n * to Helvetica for unknown families.\n */\nfunction resolveStandardFamily(family: string | undefined): StandardFamily {\n const key = (family ?? \"helvetica\").toLowerCase();\n return STANDARD_FONTS[key] ?? STANDARD_FONTS.helvetica!;\n}\n\nexport async function renderDocument(doc: PDFDocument, document: MarkdownDocument, opts: RenderOptions = {}): Promise<void> {\n const theme = opts.theme;\n if (!theme) throw new Error(\"renderDocument requires a theme\");\n const page = opts.page ?? createA4({ top: 40, right: 40, bottom: 40, left: 40 });\n const flow = createFlow(page);\n createPage(doc, page);\n\n if (opts.fonts) doc.registerFontkit(fontkit);\n\n const standard = resolveStandardFamily(theme.text.body.family);\n\n const font = opts.fonts?.regular\n ? await doc.embedFont(opts.fonts.regular, { subset: true })\n : await doc.embedFont(standard.regular);\n const bold = opts.fonts?.bold\n ? await doc.embedFont(opts.fonts.bold, { subset: true })\n : opts.fonts?.regular\n ? font\n : await doc.embedFont(standard.bold);\n const italic = opts.fonts?.italic\n ? await doc.embedFont(opts.fonts.italic, { subset: true })\n : opts.fonts?.regular\n ? font\n : await doc.embedFont(standard.italic);\n\n const profile = opts.profile\n ? {\n image: await embedImage(doc, opts.profile.bytes),\n size: opts.profile.size ?? 96,\n position: opts.profile.position ?? \"right\",\n }\n : null;\n\n const sanitize = !opts.fonts?.regular;\n\n const ctx: Ctx = { doc, flow, page, theme, font, bold, italic, profile, sanitize };\n\n renderProfile(ctx);\n\n for (const block of document.blocks) {\n renderBlock(ctx, block);\n }\n}\n\nfunction renderBlock(ctx: Ctx, block: MarkdownBlock): void {\n switch (block.type) {\n case \"heading\":\n renderHeading(ctx, block);\n break;\n case \"paragraph\":\n renderParagraph(ctx, block.children, specToStyle(ctx, ctx.theme.text.body));\n break;\n case \"list\":\n renderList(ctx, block);\n break;\n case \"thematicBreak\":\n renderThematicBreak(ctx);\n break;\n case \"blockquote\":\n renderBlockquote(ctx, block);\n break;\n }\n}\n\nfunction specToStyle(ctx: Ctx, spec: FontSpec): PdfStyle {\n const weight = spec.weight === \"bold\" ? \"bold\" : \"normal\";\n return { font: weight === \"bold\" ? ctx.bold : ctx.font, size: spec.size };\n}\n\nfunction lineHeightFor(ctx: Ctx, size: Points): Points {\n return size * ctx.theme.spacing.lineHeight;\n}\n\n/** Advance the cursor down by `pts`, breaking to a new page when needed. */\nfunction gap(ctx: Ctx, pts: Points): void {\n if (pts <= 0) return;\n if (ctx.flow.needNewPage(pts)) ensurePage(ctx);\n ctx.flow.advance(pts);\n}\n\nfunction renderHeading(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"heading\" }>): void {\n const spacing = ctx.theme.spacing;\n\n // Space above the heading, based on its level.\n if (block.level === 2) gap(ctx, spacing.sectionGap);\n else if (block.level === 3) gap(ctx, spacing.entrySpacing);\n\n const spec =\n block.level === 1 ? ctx.theme.text.name : block.level === 2 ? ctx.theme.text.section : ctx.theme.text.entryTitle;\n const color = block.level === 2 ? ctx.theme.color.accent : ctx.theme.color.ink;\n const style = specToStyle(ctx, spec);\n renderLines(ctx, block.children, style, color);\n\n // Space below the heading before its content.\n if (block.level === 1) gap(ctx, spacing.headerGap);\n else gap(ctx, spacing.entryGap);\n}\n\nfunction renderParagraph(ctx: Ctx, nodes: InlineNode[], spec: PdfStyle): void {\n const spacing = ctx.theme.spacing;\n renderLines(ctx, nodes, spec, ctx.theme.color.ink);\n gap(ctx, spacing.entryGap);\n}\n\nfunction renderProfile(ctx: Ctx): void {\n if (!ctx.profile) return;\n\n const { image, size, position } = ctx.profile;\n const pdfPage = currentPdfPage(ctx);\n const contentWidth = ctx.flow.contentWidth();\n\n let x: number;\n if (position === \"left\") x = marginLeft(ctx);\n else if (position === \"center\") x = marginLeft(ctx) + (contentWidth - size) / 2;\n else x = marginLeft(ctx) + contentWidth - size;\n\n const y = yToPdf(ctx, size);\n pdfPage.drawImage(image, { x, y, width: size, height: size });\n}\n\nfunction renderBlockquote(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"blockquote\" }>): void {\n const spacing = ctx.theme.spacing;\n const spec = specToStyle(ctx, ctx.theme.text.body);\n renderLines(ctx, block.children, spec, ctx.theme.color.muted);\n gap(ctx, spacing.entryGap);\n}\n\nfunction renderLines(ctx: Ctx, nodes: InlineNode[], spec: PdfStyle, color: RGB): void {\n const runs = nodesToRuns(ctx, nodes, spec);\n const maxWidth = ctx.flow.contentWidth();\n const lines = wrapRuns(runs, maxWidth);\n const lineHeight = lineHeightFor(ctx, spec.size);\n\n for (const line of lines) {\n if (ctx.flow.needNewPage(lineHeight)) ensurePage(ctx);\n drawLine(ctx, line, color);\n ctx.flow.advance(lineHeight);\n }\n}\n\nfunction renderList(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"list\" }>): void {\n const spacing = ctx.theme.spacing;\n const spec = specToStyle(ctx, ctx.theme.text.body);\n const lineHeight = lineHeightFor(ctx, spec.size);\n const indent = spec.size * 1.2;\n\n for (const item of block.items) {\n const bullet = block.ordered ? `${block.items.indexOf(item) + 1}. ` : \"• \";\n const runs: Run[] = [{ text: bullet, style: spec }];\n const wrapped = wrapRuns([...runs, ...nodesToRuns(ctx, item.children, spec)], ctx.flow.contentWidth() - indent);\n\n wrapped.forEach((line, i) => {\n const padded = i === 0 ? line : padRuns(line, indent, spec);\n if (ctx.flow.needNewPage(lineHeight)) ensurePage(ctx);\n drawLine(ctx, padded, ctx.theme.color.ink);\n ctx.flow.advance(lineHeight);\n });\n\n if (item.nested) {\n for (const sub of item.nested) {\n renderNestedList(ctx, sub, indent);\n }\n }\n }\n\n gap(ctx, spacing.entryGap);\n}\n\nfunction renderNestedList(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"list\" }>, baseIndent: Points): void {\n const spec = specToStyle(ctx, ctx.theme.text.body);\n const lineHeight = lineHeightFor(ctx, spec.size);\n const indent = baseIndent + spec.size * 1.2;\n\n for (const item of block.items) {\n const bullet = block.ordered ? `${block.items.indexOf(item) + 1}. ` : \"• \";\n const runs: Run[] = [{ text: bullet, style: spec }];\n const wrapped = wrapRuns([...runs, ...nodesToRuns(ctx, item.children, spec)], ctx.flow.contentWidth() - indent);\n\n wrapped.forEach((line, i) => {\n const padded = i === 0 ? padRuns(line, baseIndent, spec) : padRuns(line, indent, spec);\n if (ctx.flow.needNewPage(lineHeight)) ensurePage(ctx);\n drawLine(ctx, padded, ctx.theme.color.ink);\n ctx.flow.advance(lineHeight);\n });\n\n if (item.nested) {\n for (const sub of item.nested) {\n renderNestedList(ctx, sub, indent);\n }\n }\n }\n}\n\nfunction renderThematicBreak(ctx: Ctx): void {\n const spacing = ctx.theme.spacing;\n const y = ctx.flow.cursor.y;\n const h = 0.75;\n if (ctx.flow.needNewPage(spacing.entryGap + h)) ensurePage(ctx);\n const pdfPage = currentPdfPage(ctx);\n pdfPage.drawRectangle({\n x: marginLeft(ctx),\n y: yToPdf(ctx, y + spacing.entryGap + h),\n width: ctx.flow.contentWidth(),\n height: h,\n color: toColor(ctx.theme.color.muted),\n });\n ctx.flow.advance(spacing.entryGap * 2 + h);\n}\n\nfunction nodesToRuns(ctx: Ctx, nodes: InlineNode[], base: PdfStyle): Run[] {\n const runs: Run[] = [];\n const walk = (list: InlineNode[], style: PdfStyle): void => {\n for (const node of list) {\n switch (node.type) {\n case \"text\":\n if (node.text) runs.push({ text: ctx.sanitize ? sanitizeText(node.text) : node.text, style });\n break;\n case \"strong\":\n walk(node.children, { font: ctx.bold, size: style.size });\n break;\n case \"emphasis\":\n walk(node.children, { font: ctx.italic, size: style.size });\n break;\n case \"link\":\n runs.push({ text: ctx.sanitize ? sanitizeText(node.text) : node.text, style });\n break;\n case \"image\":\n break;\n }\n }\n };\n walk(nodes, base);\n return runs;\n}\n\nfunction padRuns(runs: Run[], pad: Points, spec: PdfStyle): Run[] {\n if (pad <= 0) return runs;\n return [{ text: \" \".repeat(Math.max(1, Math.round(pad / (spec.size * 0.5)))), style: spec }, ...runs];\n}\n\nfunction drawLine(ctx: Ctx, line: Run[], color: RGB): void {\n const pdfPage = currentPdfPage(ctx);\n const size = Math.max(...line.map((r) => r.style.size));\n const baseline = ctx.flow.cursor.y + size * ASCENT;\n let x = marginLeft(ctx);\n for (const run of line) {\n pdfPage.drawText(run.text, {\n x,\n y: yToPdf(ctx, baseline),\n size: run.style.size,\n font: run.style.font,\n color: toColor(color),\n });\n x += measureWidth(run);\n }\n}\n\nfunction marginLeft(ctx: Ctx): number {\n return ctx.page.margin.left;\n}\n\n/** Convert flow Y (from content-area top) to pdf-lib Y (from page bottom). */\nfunction yToPdf(ctx: Ctx, y: Points): Points {\n return ctx.page.size.height - ctx.page.margin.top - y;\n}\n\nfunction toColor(c: RGB): ReturnType<typeof rgb> {\n return rgb(c.r, c.g, c.b);\n}\n\nfunction createPage(doc: PDFDocument, page: PageConfig): void {\n doc.addPage([page.size.width, page.size.height]);\n}\n\n/** Embed PNG or JPEG bytes into the document, auto-detecting the format. */\nasync function embedImage(doc: PDFDocument, bytes: Uint8Array): Promise<PDFImage> {\n const isPng = bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47;\n const isJpeg = bytes[0] === 0xff && bytes[1] === 0xd8;\n if (isPng) return doc.embedPng(bytes);\n if (isJpeg) return doc.embedJpg(bytes);\n throw new Error(\"Unsupported image format: only PNG and JPEG are supported.\");\n}\nfunction ensurePage(ctx: Ctx): void {\n ctx.flow.newPage();\n ctx.doc.addPage([ctx.page.size.width, ctx.page.size.height]);\n}\n\nfunction currentPdfPage(ctx: Ctx): PDFPage {\n return ctx.doc.getPage(ctx.flow.pageNumber);\n}\n","import type { PdfStyle } from \"./fonts\";\n\n/** A styled run of text. */\nexport type Run = {\n text: string;\n style: PdfStyle;\n};\n\nexport function measureWidth(run: Run): number {\n return run.style.font.widthOfTextAtSize(run.text, run.style.size);\n}\n\nexport function measureLine(line: Run[]): number {\n return line.reduce((sum, run) => sum + measureWidth(run), 0);\n}\n\n/**\n * Greedy word-wrap: pack styled runs into lines that fit `maxWidth`.\n * Words are never split; a single word wider than the box wraps onto its own line.\n */\nexport function wrapRuns(runs: Run[], maxWidth: number): Run[][] {\n if (runs.length === 0) return [];\n\n const words: { text: string; style: PdfStyle }[] = [];\n for (const run of runs) {\n const parts = run.text.split(/(\\s+)/);\n for (const part of parts) {\n if (part === \"\" || /^\\s+$/.test(part)) continue;\n words.push({ text: part, style: run.style });\n }\n }\n\n const lines: Run[][] = [];\n let current: Run[] = [];\n let currentWidth = 0;\n\n for (const word of words) {\n const w = measureWidth(word);\n const gapWidth = current.length === 0 ? 0 : measureWidth({ text: \" \", style: word.style });\n\n if (current.length > 0 && currentWidth + gapWidth + w > maxWidth) {\n lines.push(current);\n current = [{ ...word }];\n currentWidth = w;\n continue;\n }\n\n if (current.length > 0) {\n current.push({ text: \" \", style: word.style });\n currentWidth += gapWidth;\n }\n current.push({ ...word });\n currentWidth += w;\n }\n\n if (current.length > 0) lines.push(current);\n return lines;\n}","// Characters representable by the WinAnsi (CP1252) encoding used by the\n// standard PDF fonts (Helvetica, Times, Courier). Anything outside this set\n// would make pdf-lib throw, so we sanitize text before measuring/drawing.\n//\n// WinAnsi covers: ASCII (0x20-0x7E), Latin-1 (0xA0-0xFF), plus a handful of\n// CP1252 specials (curly quotes, dashes, bullet, euro, etc.).\n\nconst CP1252_SPECIALS = new Set<number>([\n 0x20ac, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030,\n 0x0160, 0x2039, 0x0152, 0x017d, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022,\n 0x2013, 0x2014, 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x017e, 0x0178,\n]);\n\n/** Common unsupported punctuation mapped to a WinAnsi-safe equivalent. */\nconst FALLBACKS: Record<number, string> = {\n 0x30fb: \"\\u00b7\", // ・ -> ·\n 0x3001: \",\", // 、-> ,\n 0xff0c: \",\", // ,-> ,\n 0x3002: \".\", // 。-> .\n 0xff1f: \"?\", // ?-> ?\n 0xff01: \"!\", // !-> !\n 0x201c: '\"', // \" -> \"\n 0x201d: '\"', // \" -> \"\n 0x2018: \"'\", // ' -> '\n 0x2019: \"'\", // ' -> '\n};\n\nfunction isWinAnsi(code: number): boolean {\n if (code >= 0x20 && code <= 0x7e) return true;\n if (code >= 0xa0 && code <= 0xff) return true;\n return CP1252_SPECIALS.has(code);\n}\n\n/**\n * Replace characters that cannot be encoded by standard PDF fonts with a safe\n * equivalent, or drop them. This keeps CJK/kana text from crashing the render.\n */\nexport function sanitizeText(text: string): string {\n let out = \"\";\n for (const ch of text) {\n const code = ch.codePointAt(0)!;\n if (isWinAnsi(code)) {\n out += ch;\n continue;\n }\n const fallback = FALLBACKS[code];\n if (fallback) out += fallback;\n // otherwise: drop the character\n }\n return out;\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { validateCV, type Issue } from \"./validator\";\nimport { renderMarkdownToPdf, type FontSet, type ProfileImage } from \"./renderer\";\nimport { createDefaultTheme, type Theme } from \"./theme\";\nimport type { PageConfig } from \"./page\";\n\nexport type GenerateCVOptions = {\n /** Path to the source Markdown file. */\n input: string;\n /** Path where the generated PDF will be written. */\n output: string;\n /** Profile photo to render in the header. */\n profile?: ProfileImage;\n /** Custom TTF fonts for non-Latin text (e.g. Noto Sans CJK). */\n fonts?: FontSet;\n theme?: Theme;\n page?: PageConfig;\n};\n\nexport type GenerateCVResult = {\n /** All validation issues found (warnings only; errors throw). */\n issues: Issue[];\n /** Path the PDF was written to. */\n output: string;\n};\n\nexport async function generateCV(options: GenerateCVOptions): Promise<GenerateCVResult> {\n const markdown = await readFile(options.input, \"utf8\");\n\n const validation = validateCV(markdown);\n const errors = validation.issues.filter((i) => i.severity === \"error\");\n\n if (errors.length > 0) {\n const summary = errors.map((e) => `- ${e.message}`).join(\"\\n\");\n throw new Error(`CV validation failed:\\n${summary}`);\n }\n\n const theme = options.theme ?? createDefaultTheme();\n const bytes = await renderMarkdownToPdf(markdown, {\n theme,\n page: options.page,\n profile: options.profile,\n fonts: options.fonts,\n });\n\n await writeFile(options.output, bytes);\n\n return {\n issues: validation.issues,\n output: options.output,\n };\n}\n"],"mappings":"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","names":["marked","marked","PDFDocument","contentWidth","PDFDocument"]}
|
|
1
|
+
{"version":3,"sources":["../src/page/config.ts","../src/page/flow.ts","../src/parser/index.ts","../src/validator/index.ts","../src/theme/default.ts","../src/renderer/index.ts","../src/renderer/render.ts","../src/renderer/measure.ts","../src/renderer/sanitize.ts","../src/generate.ts"],"sourcesContent":["export type Points = number;\n\nexport type PageMargins = {\n top: Points;\n right: Points;\n bottom: Points;\n left: Points;\n};\n\nexport type PageSize = {\n width: Points;\n height: Points;\n};\n\nexport type PageConfig = {\n size: PageSize;\n margin: PageMargins;\n};\n\nconst A4 = Object.freeze({ width: 595.28, height: 841.89 });\n\nexport function contentWidth(page: PageConfig): Points {\n return page.size.width - page.margin.left - page.margin.right;\n}\n\nexport function contentHeight(page: PageConfig): Points {\n return page.size.height - page.margin.top - page.margin.bottom;\n}\n\nexport function createA4(margin: Partial<PageMargins> = {}): PageConfig {\n const marginValue: PageMargins = {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n ...margin,\n };\n\n return { size: { ...A4 }, margin: marginValue };\n}\n","import type { PageConfig, Points } from \"./config\";\nimport { contentHeight, contentWidth } from \"./config\";\n\nexport type Cursor = {\n x: Points;\n y: Points;\n};\n\n/**\n * Holds cursor state (relative to the current page's content area, origin at\n * the top-left) and coordinates page-breaks. Does not hardcode any dimension;\n * it asks the page config how much room is left.\n */\nexport type Flow = {\n readonly page: PageConfig;\n readonly cursor: Cursor;\n readonly pageNumber: number;\n contentWidth(): Points;\n contentHeight(): Points;\n isLastPageEmpty(): boolean;\n /** True when advancing `dy` would overflow the current content area. */\n needNewPage(dy: Points): boolean;\n /** Break to the next page, resetting the cursor to the top-left. */\n newPage(): void;\n /** Move the cursor down by `dy`. */\n advance(dy: Points): void;\n};\n\nexport function createFlow(page: PageConfig): Flow {\n const cursor: Cursor = { x: 0, y: 0 };\n let pageNumber = 0;\n\n return {\n get page() {\n return page;\n },\n get cursor() {\n return cursor;\n },\n get pageNumber() {\n return pageNumber;\n },\n contentWidth: () => contentWidth(page),\n contentHeight: () => contentHeight(page),\n\n isLastPageEmpty: () => cursor.y === 0,\n\n needNewPage: (dy) => cursor.y + dy >= contentHeight(page),\n\n newPage: () => {\n pageNumber += 1;\n cursor.x = 0;\n cursor.y = 0;\n },\n\n advance: (dy) => {\n cursor.y += dy;\n },\n };\n}\n","import { marked, type Tokens, type Token } from \"marked\";\nimport type {\n InlineNode,\n ListBlock,\n ListItem,\n MarkdownBlock,\n MarkdownDocument,\n} from \"./types\";\n\nexport function parseMarkdown(markdown: string): MarkdownDocument {\n const tokens = marked.lexer(markdown);\n\n const blocks: MarkdownBlock[] = [];\n\n for (const token of tokens) {\n const block = parseBlock(token);\n\n if (block) {\n blocks.push(block);\n }\n }\n\n return { blocks };\n}\n\nfunction parseBlock(token: Token): MarkdownBlock | null {\n switch (token.type) {\n case \"heading\": {\n const heading = token as Tokens.Heading;\n const level = clampHeadingLevel(heading.depth);\n return { type: \"heading\", level, children: parseInline(heading.tokens) };\n }\n\n case \"paragraph\":\n return { type: \"paragraph\", children: parseInline(token.tokens ?? []) };\n\n case \"list\":\n return parseList(token as Tokens.List);\n\n case \"hr\":\n return { type: \"thematicBreak\" };\n\n case \"blockquote\":\n return parseBlockquote(token as Tokens.Blockquote);\n\n case \"space\":\n return null;\n\n default:\n return null;\n }\n}\n\nfunction parseBlockquote(token: Tokens.Blockquote): MarkdownBlock {\n const children: InlineNode[] = [];\n\n for (const block of token.tokens ?? []) {\n if (block.type === \"paragraph\") {\n if (children.length > 0) {\n children.push({ type: \"text\", text: \" \" });\n }\n children.push(...parseInline((block as Tokens.Paragraph).tokens ?? []));\n }\n }\n\n return { type: \"blockquote\", children };\n}\n\nfunction parseList(token: Tokens.List): ListBlock {\n return {\n type: \"list\",\n ordered: token.ordered,\n items: token.items.map(parseListItem),\n };\n}\n\nfunction parseListItem(item: Tokens.ListItem): ListItem {\n const children: InlineNode[] = [];\n const nested: ListBlock[] = [];\n\n for (const block of item.tokens ?? []) {\n if (block.type === \"list\") {\n nested.push(parseList(block as Tokens.List));\n } else if (\"tokens\" in block && block.tokens) {\n children.push(...parseInline(block.tokens));\n }\n }\n\n return { children, ...(nested.length > 0 ? { nested } : {}) };\n}\n\nfunction clampHeadingLevel(depth: number): 1 | 2 | 3 {\n return Math.min(Math.max(depth, 1), 3) as 1 | 2 | 3;\n}\n\nfunction parseInline(tokens: Token[]): InlineNode[] {\n const nodes: InlineNode[] = [];\n\n for (const token of tokens) {\n switch (token.type) {\n case \"text\": {\n const text = token as Tokens.Text;\n nodes.push({ type: \"text\", text: text.text });\n break;\n }\n\n case \"strong\": {\n const strong = token as Tokens.Strong;\n nodes.push({ type: \"strong\", children: parseInline(strong.tokens) });\n break;\n }\n\n case \"em\": {\n const em = token as Tokens.Em;\n nodes.push({ type: \"emphasis\", children: parseInline(em.tokens) });\n break;\n }\n\n case \"link\": {\n const link = token as Tokens.Link;\n nodes.push({ type: \"link\", text: link.text, href: link.href });\n break;\n }\n\n case \"image\": {\n const image = token as Tokens.Image;\n nodes.push({ type: \"image\", href: image.href });\n break;\n }\n\n case \"codespan\": {\n const codespan = token as Tokens.Codespan;\n nodes.push({ type: \"text\", text: codespan.text });\n break;\n }\n }\n }\n\n return nodes;\n}\n","import { marked } from \"marked\";\nimport { parseMarkdown } from \"../parser\";\nimport type {\n HeadingBlock,\n InlineNode,\n MarkdownBlock,\n MarkdownDocument,\n} from \"../parser/types\";\n\nexport type Severity = \"error\" | \"warning\";\n\nexport type Issue = {\n severity: Severity;\n blockIndex?: number;\n message: string;\n};\n\nexport type ValidationResult = {\n issues: Issue[];\n valid: boolean;\n};\n\nconst EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$/i;\nconst RAW_EMAIL_REGEX = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/i;\n\nexport function validateCV(markdown: string): ValidationResult {\n const doc = parseMarkdown(markdown);\n\n if (doc.blocks.length === 0) {\n return { issues: [{ severity: \"error\", message: \"Document is empty.\" }], valid: false };\n }\n\n const issues: Issue[] = [];\n\n const hasName = doc.blocks.some((b) => b.type === \"heading\" && b.level === 1);\n if (!hasName) {\n issues.push({\n severity: \"error\",\n message: 'Missing a level-1 heading. Add \"# Your Name\" for the candidate name.',\n });\n }\n\n validateContact(doc, issues);\n validateHierarchy(doc, issues);\n validateSection(doc, \"experience\", issues);\n validateSection(doc, \"education\", issues);\n detectUnsupported(markdown, issues);\n\n return { issues, valid: issues.every((i) => i.severity !== \"error\") };\n}\n\nfunction validateContact(doc: MarkdownDocument, issues: Issue[]): void {\n const texts = flattenText(doc);\n const links = flattenLinks(doc);\n\n const emails = new Set<string>();\n for (const t of texts) {\n const m = t.match(RAW_EMAIL_REGEX);\n if (m) emails.add(m[0]);\n }\n for (const href of links) {\n if (href.startsWith(\"mailto:\")) emails.add(href.slice(\"mailto:\".length).split(\"?\")[0] ?? \"\");\n }\n\n const validEmails = [...emails].filter((e) => EMAIL_REGEX.test(e));\n const hasContactLink = links.some((href) => {\n const lower = href.toLowerCase();\n return /mailto:|github\\.com|linkedin\\.com|twitter\\.com|x\\.com|dev\\.to|t\\.me/.test(lower);\n });\n\n if (validEmails.length === 0 && !hasContactLink) {\n issues.push({\n severity: \"warning\",\n message: 'No contact info found. Add an email or a contact link (e.g. mailto:, github, linkedin).',\n });\n }\n\n if ([...emails].some((e) => !EMAIL_REGEX.test(e))) {\n issues.push({\n severity: \"warning\",\n message: `Invalid email address found: \"${[...emails].filter((e) => !EMAIL_REGEX.test(e)).join(\", \")}\".`,\n });\n }\n}\n\nfunction validateHierarchy(doc: MarkdownDocument, issues: Issue[]): void {\n let prevLevel = 0;\n\n doc.blocks.forEach((block, index) => {\n if (block.type !== \"heading\") return;\n if (prevLevel !== 0 && block.level > prevLevel + 1) {\n issues.push({\n severity: \"warning\",\n blockIndex: index,\n message: `Heading level skipped from H${prevLevel} to H${block.level}. Prefer H1 → H2 → H3 order.`,\n });\n }\n prevLevel = block.level;\n });\n}\n\nfunction validateSection(doc: MarkdownDocument, sectionName: string, issues: Issue[]): void {\n const headings: { block: HeadingBlock; index: number }[] = [];\n doc.blocks.forEach((block, index) => {\n if (block.type === \"heading\") headings.push({ block, index });\n });\n\n const target = headings.find(\n ({ block }) => block.level === 2 && headingText(block).toLowerCase() === sectionName,\n );\n\n if (!target) {\n issues.push({\n severity: \"warning\",\n message: `Section \"${sectionName}\" not found. Add \"## ${sectionName}\" heading.`,\n });\n return;\n }\n\n const body: { block: MarkdownBlock; index: number }[] = [];\n const nextSection = doc.blocks\n .slice(target.index + 1)\n .findIndex((b) => b.type === \"heading\" && b.level === 2);\n const end = nextSection === -1 ? doc.blocks.length : target.index + 1 + nextSection;\n\n for (let k = target.index + 1; k < end; k++) {\n const block = doc.blocks[k];\n if (block) body.push({ block, index: k });\n }\n\n const hasBody = body.some(({ block }) => block.type !== \"heading\" && block.type !== \"thematicBreak\");\n if (!hasBody) {\n issues.push({\n severity: \"warning\",\n blockIndex: target.index,\n message: `Section \"${sectionName}\" has no content below its heading.`,\n });\n return;\n }\n\n const entries = body.filter(\n (s): s is { block: HeadingBlock; index: number } =>\n s.block.type === \"heading\" && s.block.level === 3,\n );\n\n entries.forEach(({ block, index }) => {\n const hasContent = body\n .filter((s) => s.index > index)\n .some(({ block: b }) => b.type !== \"heading\" && b.type !== \"thematicBreak\");\n if (!hasContent) {\n issues.push({\n severity: \"warning\",\n blockIndex: index,\n message: `Entry \"${headingText(block)}\" in \"${sectionName}\" has no content below it.`,\n });\n }\n });\n}\n\nfunction detectUnsupported(markdown: string, issues: Issue[]): void {\n const supported = new Set([\"space\", \"heading\", \"paragraph\", \"list\", \"hr\", \"blockquote\"]);\n\n for (const token of marked.lexer(markdown)) {\n if (!supported.has(token.type)) {\n issues.push({\n severity: \"warning\",\n message: `Unsupported Markdown ignored: \"${token.type}\".`,\n });\n }\n }\n}\n\nfunction headingText(block: HeadingBlock): string {\n return inlineText(block.children);\n}\n\nfunction inlineText(nodes: InlineNode[]): string {\n let out = \"\";\n for (const node of nodes) {\n switch (node.type) {\n case \"text\":\n out += node.text;\n break;\n case \"strong\":\n case \"emphasis\":\n out += inlineText(node.children);\n break;\n case \"link\":\n out += node.text;\n break;\n case \"image\":\n break;\n }\n }\n return out;\n}\n\nfunction flattenText(doc: MarkdownDocument): string[] {\n const out: string[] = [];\n for (const block of doc.blocks) {\n if (block.type === \"paragraph\") out.push(inlineText(block.children));\n else if (block.type === \"heading\") out.push(inlineText(block.children));\n }\n return out;\n}\n\nfunction flattenLinks(doc: MarkdownDocument): string[] {\n const out: string[] = [];\n const walk = (nodes: InlineNode[]): void => {\n for (const node of nodes) {\n if (node.type === \"link\") out.push(node.href);\n else if (node.type === \"strong\" || node.type === \"emphasis\") walk(node.children);\n }\n };\n for (const block of doc.blocks) {\n if (block.type === \"paragraph\") walk(block.children);\n else if (block.type === \"heading\") walk(block.children);\n }\n return out;\n}\n","import type { Theme } from \"./types\";\n\nexport function createDefaultTheme(): Theme {\n return {\n name: \"ats-simple\",\n text: {\n name: { family: \"Helvetica\", size: 22, weight: \"bold\" },\n section: { family: \"Helvetica\", size: 13, weight: \"bold\" },\n entryTitle: { family: \"Helvetica\", size: 11, weight: \"bold\" },\n body: { family: \"Helvetica\", size: 10, weight: \"normal\" },\n },\n color: {\n ink: { r: 0.1, g: 0.1, b: 0.1 },\n accent: { r: 0.05, g: 0.15, b: 0.4 },\n muted: { r: 0.35, g: 0.35, b: 0.35 },\n },\n spacing: {\n sectionGap: 6,\n entryGap: 6,\n entrySpacing: 8,\n lineHeight: 1.35,\n columnGap: 8,\n headerGap: 8,\n },\n };\n}\n","import { PDFDocument } from \"pdf-lib\";\nimport { parseMarkdown } from \"../parser\";\nimport { createDefaultTheme, type Theme } from \"../theme\";\nimport { renderDocument, type FontSet, type ProfileImage } from \"./render\";\nimport type { PageConfig } from \"../page\";\n\nexport type RenderMarkdownOptions = {\n theme?: Theme;\n page?: PageConfig;\n profile?: ProfileImage;\n fonts?: FontSet;\n};\n\nexport async function renderMarkdownToPdf(\n markdown: string,\n opts: RenderMarkdownOptions = {},\n): Promise<Uint8Array> {\n const doc = await PDFDocument.create();\n const theme = opts.theme ?? createDefaultTheme();\n await renderDocument(doc, parseMarkdown(markdown), {\n theme,\n page: opts.page,\n profile: opts.profile,\n fonts: opts.fonts,\n });\n return doc.save();\n}\n\nexport { renderDocument } from \"./render\";\nexport type { FontSet, ProfileImage } from \"./render\";","import { PDFDocument, PDFString, StandardFonts, rgb, type PDFFont, type PDFPage, type PDFImage } from \"pdf-lib\";\nimport fontkit from \"@pdf-lib/fontkit\";import type { MarkdownBlock, MarkdownDocument, InlineNode } from \"../parser/types\";\nimport { createA4, createFlow, type PageConfig, type Points, type Flow } from \"../page\";\nimport type { FontSpec, Theme, RGB } from \"../theme\";\nimport type { PdfStyle } from \"./fonts\";\nimport { measureWidth, wrapRuns, type Run } from \"./measure\";\nimport { sanitizeText } from \"./sanitize\";\n\nexport type ProfilePosition = \"left\" | \"center\" | \"right\";\n\nexport type ProfileImage = {\n /** PNG or JPEG bytes. */\n bytes: Uint8Array;\n /** Side length of the (square) photo in points. Default 96. */\n size?: number;\n /** Horizontal position within the header. Default \"right\". */\n position?: ProfilePosition;\n};\n\ntype Ctx = {\n doc: PDFDocument;\n flow: Flow;\n page: PageConfig;\n theme: Theme;\n font: PDFFont;\n bold: PDFFont;\n italic: PDFFont;\n profile: { image: PDFImage; size: number; position: ProfilePosition } | null;\n sanitize: boolean;\n};\n\nexport type FontSet = {\n /** Regular weight TTF bytes (e.g. Noto Sans CJK). */\n regular: Uint8Array;\n bold?: Uint8Array;\n italic?: Uint8Array;\n};\n\nexport type RenderOptions = {\n theme?: Theme;\n page?: PageConfig;\n profile?: ProfileImage;\n fonts?: FontSet;\n};\n\n/** Fraction of the em-size where the text baseline sits (Helvetica ascent). */\nconst ASCENT = 0.72;\n\ntype StandardFamily = {\n regular: StandardFonts;\n bold: StandardFonts;\n italic: StandardFonts;\n};\n\nconst STANDARD_FONTS: Record<string, StandardFamily> = {\n helvetica: {\n regular: StandardFonts.Helvetica,\n bold: StandardFonts.HelveticaBold,\n italic: StandardFonts.HelveticaOblique,\n },\n times: {\n regular: StandardFonts.TimesRoman,\n bold: StandardFonts.TimesRomanBold,\n italic: StandardFonts.TimesRomanItalic,\n },\n courier: {\n regular: StandardFonts.Courier,\n bold: StandardFonts.CourierBold,\n italic: StandardFonts.CourierOblique,\n },\n};\n\n/**\n * Map a theme `family` name to a set of bundled standard fonts, falling back\n * to Helvetica for unknown families.\n */\nfunction resolveStandardFamily(family: string | undefined): StandardFamily {\n const key = (family ?? \"helvetica\").toLowerCase();\n return STANDARD_FONTS[key] ?? STANDARD_FONTS.helvetica!;\n}\n\nexport async function renderDocument(doc: PDFDocument, document: MarkdownDocument, opts: RenderOptions = {}): Promise<void> {\n const theme = opts.theme;\n if (!theme) throw new Error(\"renderDocument requires a theme\");\n const page = opts.page ?? createA4({ top: 40, right: 40, bottom: 40, left: 40 });\n const flow = createFlow(page);\n createPage(doc, page);\n\n if (opts.fonts) doc.registerFontkit(fontkit);\n\n const standard = resolveStandardFamily(theme.text.body.family);\n\n const font = opts.fonts?.regular\n ? await doc.embedFont(opts.fonts.regular, { subset: true })\n : await doc.embedFont(standard.regular);\n const bold = opts.fonts?.bold\n ? await doc.embedFont(opts.fonts.bold, { subset: true })\n : opts.fonts?.regular\n ? font\n : await doc.embedFont(standard.bold);\n const italic = opts.fonts?.italic\n ? await doc.embedFont(opts.fonts.italic, { subset: true })\n : opts.fonts?.regular\n ? font\n : await doc.embedFont(standard.italic);\n\n const profile = opts.profile\n ? {\n image: await embedImage(doc, opts.profile.bytes),\n size: opts.profile.size ?? 96,\n position: opts.profile.position ?? \"right\",\n }\n : null;\n\n const sanitize = !opts.fonts?.regular;\n\n const ctx: Ctx = { doc, flow, page, theme, font, bold, italic, profile, sanitize };\n\n renderProfile(ctx);\n\n for (const block of document.blocks) {\n renderBlock(ctx, block);\n }\n}\n\nfunction renderBlock(ctx: Ctx, block: MarkdownBlock): void {\n switch (block.type) {\n case \"heading\":\n renderHeading(ctx, block);\n break;\n case \"paragraph\":\n renderParagraph(ctx, block.children, specToStyle(ctx, ctx.theme.text.body));\n break;\n case \"list\":\n renderList(ctx, block);\n break;\n case \"thematicBreak\":\n renderThematicBreak(ctx);\n break;\n case \"blockquote\":\n renderBlockquote(ctx, block);\n break;\n }\n}\n\nfunction specToStyle(ctx: Ctx, spec: FontSpec): PdfStyle {\n const weight = spec.weight === \"bold\" ? \"bold\" : \"normal\";\n return { font: weight === \"bold\" ? ctx.bold : ctx.font, size: spec.size };\n}\n\nfunction lineHeightFor(ctx: Ctx, size: Points): Points {\n return size * ctx.theme.spacing.lineHeight;\n}\n\n/** Advance the cursor down by `pts`, breaking to a new page when needed. */\nfunction gap(ctx: Ctx, pts: Points): void {\n if (pts <= 0) return;\n if (ctx.flow.needNewPage(pts)) ensurePage(ctx);\n ctx.flow.advance(pts);\n}\n\nfunction renderHeading(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"heading\" }>): void {\n const spacing = ctx.theme.spacing;\n\n // Space above the heading, based on its level.\n if (block.level === 2) gap(ctx, spacing.sectionGap);\n else if (block.level === 3) gap(ctx, spacing.entrySpacing);\n\n const spec =\n block.level === 1 ? ctx.theme.text.name : block.level === 2 ? ctx.theme.text.section : ctx.theme.text.entryTitle;\n const color = block.level === 2 ? ctx.theme.color.accent : ctx.theme.color.ink;\n const style = specToStyle(ctx, spec);\n renderLines(ctx, block.children, style, color);\n\n // Space below the heading before its content.\n if (block.level === 1) gap(ctx, spacing.headerGap);\n else gap(ctx, spacing.entryGap);\n}\n\nfunction renderParagraph(ctx: Ctx, nodes: InlineNode[], spec: PdfStyle): void {\n const spacing = ctx.theme.spacing;\n renderLines(ctx, nodes, spec, ctx.theme.color.ink);\n gap(ctx, spacing.entryGap);\n}\n\nfunction renderProfile(ctx: Ctx): void {\n if (!ctx.profile) return;\n\n const { image, size, position } = ctx.profile;\n const pdfPage = currentPdfPage(ctx);\n const contentWidth = ctx.flow.contentWidth();\n\n let x: number;\n if (position === \"left\") x = marginLeft(ctx);\n else if (position === \"center\") x = marginLeft(ctx) + (contentWidth - size) / 2;\n else x = marginLeft(ctx) + contentWidth - size;\n\n const y = yToPdf(ctx, size);\n pdfPage.drawImage(image, { x, y, width: size, height: size });\n}\n\nfunction renderBlockquote(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"blockquote\" }>): void {\n const spacing = ctx.theme.spacing;\n const spec = specToStyle(ctx, ctx.theme.text.body);\n renderLines(ctx, block.children, spec, ctx.theme.color.muted);\n gap(ctx, spacing.entryGap);\n}\n\nfunction renderLines(ctx: Ctx, nodes: InlineNode[], spec: PdfStyle, color: RGB): void {\n const runs = nodesToRuns(ctx, nodes, spec);\n const maxWidth = ctx.flow.contentWidth();\n const lines = wrapRuns(runs, maxWidth);\n const lineHeight = lineHeightFor(ctx, spec.size);\n\n for (const line of lines) {\n if (ctx.flow.needNewPage(lineHeight)) ensurePage(ctx);\n drawLine(ctx, line, color);\n ctx.flow.advance(lineHeight);\n }\n}\n\nfunction renderList(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"list\" }>): void {\n const spacing = ctx.theme.spacing;\n const spec = specToStyle(ctx, ctx.theme.text.body);\n const lineHeight = lineHeightFor(ctx, spec.size);\n const indent = spec.size * 1.2;\n\n for (const item of block.items) {\n const bullet = block.ordered ? `${block.items.indexOf(item) + 1}. ` : \"• \";\n const runs: Run[] = [{ text: bullet, style: spec }];\n const wrapped = wrapRuns([...runs, ...nodesToRuns(ctx, item.children, spec)], ctx.flow.contentWidth() - indent);\n\n wrapped.forEach((line, i) => {\n const padded = i === 0 ? line : padRuns(line, indent, spec);\n if (ctx.flow.needNewPage(lineHeight)) ensurePage(ctx);\n drawLine(ctx, padded, ctx.theme.color.ink);\n ctx.flow.advance(lineHeight);\n });\n\n if (item.nested) {\n for (const sub of item.nested) {\n renderNestedList(ctx, sub, indent);\n }\n }\n }\n\n gap(ctx, spacing.entryGap);\n}\n\nfunction renderNestedList(ctx: Ctx, block: Extract<MarkdownBlock, { type: \"list\" }>, baseIndent: Points): void {\n const spec = specToStyle(ctx, ctx.theme.text.body);\n const lineHeight = lineHeightFor(ctx, spec.size);\n const indent = baseIndent + spec.size * 1.2;\n\n for (const item of block.items) {\n const bullet = block.ordered ? `${block.items.indexOf(item) + 1}. ` : \"• \";\n const runs: Run[] = [{ text: bullet, style: spec }];\n const wrapped = wrapRuns([...runs, ...nodesToRuns(ctx, item.children, spec)], ctx.flow.contentWidth() - indent);\n\n wrapped.forEach((line, i) => {\n const padded = i === 0 ? padRuns(line, baseIndent, spec) : padRuns(line, indent, spec);\n if (ctx.flow.needNewPage(lineHeight)) ensurePage(ctx);\n drawLine(ctx, padded, ctx.theme.color.ink);\n ctx.flow.advance(lineHeight);\n });\n\n if (item.nested) {\n for (const sub of item.nested) {\n renderNestedList(ctx, sub, indent);\n }\n }\n }\n}\n\nfunction renderThematicBreak(ctx: Ctx): void {\n const spacing = ctx.theme.spacing;\n const y = ctx.flow.cursor.y;\n const h = 0.75;\n if (ctx.flow.needNewPage(spacing.entryGap + h)) ensurePage(ctx);\n const pdfPage = currentPdfPage(ctx);\n pdfPage.drawRectangle({\n x: marginLeft(ctx),\n y: yToPdf(ctx, y + spacing.entryGap + h),\n width: ctx.flow.contentWidth(),\n height: h,\n color: toColor(ctx.theme.color.muted),\n });\n ctx.flow.advance(spacing.entryGap * 2 + h);\n}\n\nfunction nodesToRuns(ctx: Ctx, nodes: InlineNode[], base: PdfStyle): Run[] {\n const runs: Run[] = [];\n const walk = (list: InlineNode[], style: PdfStyle): void => {\n for (const node of list) {\n switch (node.type) {\n case \"text\":\n if (node.text) runs.push({ text: ctx.sanitize ? sanitizeText(node.text) : node.text, style });\n break;\n case \"strong\":\n walk(node.children, { font: ctx.bold, size: style.size });\n break;\n case \"emphasis\":\n walk(node.children, { font: ctx.italic, size: style.size });\n break;\n case \"link\":\n runs.push({ text: ctx.sanitize ? sanitizeText(node.text) : node.text, style, href: node.href });\n break;\n case \"image\":\n break;\n }\n }\n };\n walk(nodes, base);\n return runs;\n}\n\nfunction padRuns(runs: Run[], pad: Points, spec: PdfStyle): Run[] {\n if (pad <= 0) return runs;\n return [{ text: \" \".repeat(Math.max(1, Math.round(pad / (spec.size * 0.5)))), style: spec }, ...runs];\n}\n\nfunction drawLine(ctx: Ctx, line: Run[], color: RGB): void {\n const pdfPage = currentPdfPage(ctx);\n const size = Math.max(...line.map((r) => r.style.size));\n const baseline = ctx.flow.cursor.y + size * ASCENT;\n const baselinePdf = yToPdf(ctx, baseline);\n let x = marginLeft(ctx);\n for (const run of line) {\n const runColor = run.href ? ctx.theme.color.accent : color;\n pdfPage.drawText(run.text, {\n x,\n y: baselinePdf,\n size: run.style.size,\n font: run.style.font,\n color: toColor(runColor),\n });\n const width = measureWidth(run);\n if (run.href && width > 0) {\n addLinkAnnotation(\n ctx,\n pdfPage,\n run.href,\n x,\n baselinePdf - run.style.size * 0.25,\n x + width,\n baselinePdf + run.style.size * 0.85,\n );\n }\n x += width;\n }\n}\n\n/** Attach a clickable URI link annotation over a run's bounding box. */\nfunction addLinkAnnotation(ctx: Ctx, pdfPage: PDFPage, href: string, x1: number, y1: number, x2: number, y2: number): void {\n const context = ctx.doc.context;\n const annot = context.obj({\n Type: \"Annot\",\n Subtype: \"Link\",\n Rect: [x1, y1, x2, y2],\n Border: [0, 0, 0],\n A: { Type: \"Action\", S: \"URI\", URI: PDFString.of(href) },\n });\n pdfPage.node.addAnnot(context.register(annot));\n}\n\nfunction marginLeft(ctx: Ctx): number {\n return ctx.page.margin.left;\n}\n\n/** Convert flow Y (from content-area top) to pdf-lib Y (from page bottom). */\nfunction yToPdf(ctx: Ctx, y: Points): Points {\n return ctx.page.size.height - ctx.page.margin.top - y;\n}\n\nfunction toColor(c: RGB): ReturnType<typeof rgb> {\n return rgb(c.r, c.g, c.b);\n}\n\nfunction createPage(doc: PDFDocument, page: PageConfig): void {\n doc.addPage([page.size.width, page.size.height]);\n}\n\n/** Embed PNG or JPEG bytes into the document, auto-detecting the format. */\nasync function embedImage(doc: PDFDocument, bytes: Uint8Array): Promise<PDFImage> {\n const isPng = bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47;\n const isJpeg = bytes[0] === 0xff && bytes[1] === 0xd8;\n if (isPng) return doc.embedPng(bytes);\n if (isJpeg) return doc.embedJpg(bytes);\n throw new Error(\"Unsupported image format: only PNG and JPEG are supported.\");\n}\nfunction ensurePage(ctx: Ctx): void {\n ctx.flow.newPage();\n ctx.doc.addPage([ctx.page.size.width, ctx.page.size.height]);\n}\n\nfunction currentPdfPage(ctx: Ctx): PDFPage {\n return ctx.doc.getPage(ctx.flow.pageNumber);\n}\n","import type { PdfStyle } from \"./fonts\";\n\n/** A styled run of text. `href` marks the run as a clickable hyperlink. */\nexport type Run = {\n text: string;\n style: PdfStyle;\n href?: string;\n};\n\nexport function measureWidth(run: Run): number {\n return run.style.font.widthOfTextAtSize(run.text, run.style.size);\n}\n\nexport function measureLine(line: Run[]): number {\n return line.reduce((sum, run) => sum + measureWidth(run), 0);\n}\n\n/**\n * Greedy word-wrap: pack styled runs into lines that fit `maxWidth`.\n * Words are never split; a single word wider than the box wraps onto its own line.\n */\nexport function wrapRuns(runs: Run[], maxWidth: number): Run[][] {\n if (runs.length === 0) return [];\n\n const words: Run[] = [];\n for (const run of runs) {\n const parts = run.text.split(/(\\s+)/);\n for (const part of parts) {\n if (part === \"\" || /^\\s+$/.test(part)) continue;\n words.push({ text: part, style: run.style, href: run.href });\n }\n }\n\n const lines: Run[][] = [];\n let current: Run[] = [];\n let currentWidth = 0;\n\n for (const word of words) {\n const w = measureWidth(word);\n const gapWidth = current.length === 0 ? 0 : measureWidth({ text: \" \", style: word.style });\n\n if (current.length > 0 && currentWidth + gapWidth + w > maxWidth) {\n lines.push(current);\n current = [{ ...word }];\n currentWidth = w;\n continue;\n }\n\n if (current.length > 0) {\n current.push({ text: \" \", style: word.style });\n currentWidth += gapWidth;\n }\n current.push({ ...word });\n currentWidth += w;\n }\n\n if (current.length > 0) lines.push(current);\n return lines;\n}","// Characters representable by the WinAnsi (CP1252) encoding used by the\n// standard PDF fonts (Helvetica, Times, Courier). Anything outside this set\n// would make pdf-lib throw, so we sanitize text before measuring/drawing.\n//\n// WinAnsi covers: ASCII (0x20-0x7E), Latin-1 (0xA0-0xFF), plus a handful of\n// CP1252 specials (curly quotes, dashes, bullet, euro, etc.).\n\nconst CP1252_SPECIALS = new Set<number>([\n 0x20ac, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030,\n 0x0160, 0x2039, 0x0152, 0x017d, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022,\n 0x2013, 0x2014, 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x017e, 0x0178,\n]);\n\n/** Common unsupported punctuation mapped to a WinAnsi-safe equivalent. */\nconst FALLBACKS: Record<number, string> = {\n 0x30fb: \"\\u00b7\", // ・ -> ·\n 0x3001: \",\", // 、-> ,\n 0xff0c: \",\", // ,-> ,\n 0x3002: \".\", // 。-> .\n 0xff1f: \"?\", // ?-> ?\n 0xff01: \"!\", // !-> !\n 0x201c: '\"', // \" -> \"\n 0x201d: '\"', // \" -> \"\n 0x2018: \"'\", // ' -> '\n 0x2019: \"'\", // ' -> '\n};\n\nfunction isWinAnsi(code: number): boolean {\n if (code >= 0x20 && code <= 0x7e) return true;\n if (code >= 0xa0 && code <= 0xff) return true;\n return CP1252_SPECIALS.has(code);\n}\n\n/**\n * Replace characters that cannot be encoded by standard PDF fonts with a safe\n * equivalent, or drop them. This keeps CJK/kana text from crashing the render.\n */\nexport function sanitizeText(text: string): string {\n let out = \"\";\n for (const ch of text) {\n const code = ch.codePointAt(0)!;\n if (isWinAnsi(code)) {\n out += ch;\n continue;\n }\n const fallback = FALLBACKS[code];\n if (fallback) out += fallback;\n // otherwise: drop the character\n }\n return out;\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { validateCV, type Issue } from \"./validator\";\nimport { renderMarkdownToPdf, type FontSet, type ProfileImage } from \"./renderer\";\nimport { createDefaultTheme, type Theme } from \"./theme\";\nimport type { PageConfig } from \"./page\";\n\nexport type GenerateCVOptions = {\n /** Path to the source Markdown file. */\n input: string;\n /** Path where the generated PDF will be written. */\n output: string;\n /** Profile photo to render in the header. */\n profile?: ProfileImage;\n /** Custom TTF fonts for non-Latin text (e.g. Noto Sans CJK). */\n fonts?: FontSet;\n theme?: Theme;\n page?: PageConfig;\n};\n\nexport type GenerateCVResult = {\n /** All validation issues found (warnings only; errors throw). */\n issues: Issue[];\n /** Path the PDF was written to. */\n output: string;\n};\n\nexport async function generateCV(options: GenerateCVOptions): Promise<GenerateCVResult> {\n const markdown = await readFile(options.input, \"utf8\");\n\n const validation = validateCV(markdown);\n const errors = validation.issues.filter((i) => i.severity === \"error\");\n\n if (errors.length > 0) {\n const summary = errors.map((e) => `- ${e.message}`).join(\"\\n\");\n throw new Error(`CV validation failed:\\n${summary}`);\n }\n\n const theme = options.theme ?? createDefaultTheme();\n const bytes = await renderMarkdownToPdf(markdown, {\n theme,\n page: options.page,\n profile: options.profile,\n fonts: options.fonts,\n });\n\n await writeFile(options.output, bytes);\n\n return {\n issues: validation.issues,\n output: options.output,\n };\n}\n"],"mappings":"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","names":["marked","marked","PDFDocument","contentWidth","PDFDocument"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "md-2-ats",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Generate ATS-friendly CV/PDF from Markdown",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
"typecheck": "tsc --noEmit",
|
|
80
80
|
"lint": "eslint .",
|
|
81
81
|
"lint:fix": "eslint . --fix",
|
|
82
|
+
"example": "tsx example/index.ts",
|
|
82
83
|
"test": "vitest run",
|
|
83
84
|
"test:watch": "vitest"
|
|
84
85
|
}
|