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 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: yToPdf(ctx, baseline),
699
+ y: baselinePdf,
698
700
  size: run.style.size,
699
701
  font: run.style.font,
700
- color: toColor(color)
702
+ color: toColor(runColor)
701
703
  });
702
- x += measureWidth(run);
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":"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","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: yToPdf(ctx, baseline),
731
+ y: baselinePdf,
730
732
  size: run.style.size,
731
733
  font: run.style.font,
732
- color: toColor(color)
734
+ color: toColor(runColor)
733
735
  });
734
- x += measureWidth(run);
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
  }
@@ -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: yToPdf(ctx, baseline),
686
+ y: baselinePdf,
685
687
  size: run.style.size,
686
688
  font: run.style.font,
687
- color: toColor(color)
689
+ color: toColor(runColor)
688
690
  });
689
- x += measureWidth(run);
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.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
  }