md-2-ats 0.1.0
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 +15 -0
- package/LICENSE +21 -0
- package/README.md +238 -0
- package/dist/cli.js +935 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +814 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +208 -0
- package/dist/index.d.ts +208 -0
- package/dist/index.js +768 -0
- package/dist/index.js.map +1 -0
- package/package.json +86 -0
|
@@ -0,0 +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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { PDFDocument } from 'pdf-lib';
|
|
2
|
+
|
|
3
|
+
type Points = number;
|
|
4
|
+
type PageMargins = {
|
|
5
|
+
top: Points;
|
|
6
|
+
right: Points;
|
|
7
|
+
bottom: Points;
|
|
8
|
+
left: Points;
|
|
9
|
+
};
|
|
10
|
+
type PageSize = {
|
|
11
|
+
width: Points;
|
|
12
|
+
height: Points;
|
|
13
|
+
};
|
|
14
|
+
type PageConfig = {
|
|
15
|
+
size: PageSize;
|
|
16
|
+
margin: PageMargins;
|
|
17
|
+
};
|
|
18
|
+
declare function contentWidth(page: PageConfig): Points;
|
|
19
|
+
declare function contentHeight(page: PageConfig): Points;
|
|
20
|
+
declare function createA4(margin?: Partial<PageMargins>): PageConfig;
|
|
21
|
+
|
|
22
|
+
type Cursor = {
|
|
23
|
+
x: Points;
|
|
24
|
+
y: Points;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Holds cursor state (relative to the current page's content area, origin at
|
|
28
|
+
* the top-left) and coordinates page-breaks. Does not hardcode any dimension;
|
|
29
|
+
* it asks the page config how much room is left.
|
|
30
|
+
*/
|
|
31
|
+
type Flow = {
|
|
32
|
+
readonly page: PageConfig;
|
|
33
|
+
readonly cursor: Cursor;
|
|
34
|
+
readonly pageNumber: number;
|
|
35
|
+
contentWidth(): Points;
|
|
36
|
+
contentHeight(): Points;
|
|
37
|
+
isLastPageEmpty(): boolean;
|
|
38
|
+
/** True when advancing `dy` would overflow the current content area. */
|
|
39
|
+
needNewPage(dy: Points): boolean;
|
|
40
|
+
/** Break to the next page, resetting the cursor to the top-left. */
|
|
41
|
+
newPage(): void;
|
|
42
|
+
/** Move the cursor down by `dy`. */
|
|
43
|
+
advance(dy: Points): void;
|
|
44
|
+
};
|
|
45
|
+
declare function createFlow(page: PageConfig): Flow;
|
|
46
|
+
|
|
47
|
+
type MarkdownDocument = {
|
|
48
|
+
blocks: MarkdownBlock[];
|
|
49
|
+
};
|
|
50
|
+
type MarkdownBlock = HeadingBlock | ParagraphBlock | ListBlock | ThematicBreakBlock | BlockquoteBlock;
|
|
51
|
+
type BlockquoteBlock = {
|
|
52
|
+
type: 'blockquote';
|
|
53
|
+
children: InlineNode[];
|
|
54
|
+
};
|
|
55
|
+
type HeadingBlock = {
|
|
56
|
+
type: 'heading';
|
|
57
|
+
level: 1 | 2 | 3;
|
|
58
|
+
children: InlineNode[];
|
|
59
|
+
};
|
|
60
|
+
type ParagraphBlock = {
|
|
61
|
+
type: 'paragraph';
|
|
62
|
+
children: InlineNode[];
|
|
63
|
+
};
|
|
64
|
+
type ListBlock = {
|
|
65
|
+
type: 'list';
|
|
66
|
+
ordered: boolean;
|
|
67
|
+
items: ListItem[];
|
|
68
|
+
};
|
|
69
|
+
type ListItem = {
|
|
70
|
+
children: InlineNode[];
|
|
71
|
+
nested?: ListBlock[];
|
|
72
|
+
};
|
|
73
|
+
type ThematicBreakBlock = {
|
|
74
|
+
type: 'thematicBreak';
|
|
75
|
+
};
|
|
76
|
+
type InlineNode = TextNode | StrongNode | EmphasisNode | LinkNode | ImageNode;
|
|
77
|
+
type TextNode = {
|
|
78
|
+
type: 'text';
|
|
79
|
+
text: string;
|
|
80
|
+
};
|
|
81
|
+
type StrongNode = {
|
|
82
|
+
type: 'strong';
|
|
83
|
+
children: InlineNode[];
|
|
84
|
+
};
|
|
85
|
+
type EmphasisNode = {
|
|
86
|
+
type: 'emphasis';
|
|
87
|
+
children: InlineNode[];
|
|
88
|
+
};
|
|
89
|
+
type LinkNode = {
|
|
90
|
+
type: 'link';
|
|
91
|
+
text: string;
|
|
92
|
+
href: string;
|
|
93
|
+
};
|
|
94
|
+
type ImageNode = {
|
|
95
|
+
type: 'image';
|
|
96
|
+
href: string;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
declare function parseMarkdown(markdown: string): MarkdownDocument;
|
|
100
|
+
|
|
101
|
+
type Severity = "error" | "warning";
|
|
102
|
+
type Issue = {
|
|
103
|
+
severity: Severity;
|
|
104
|
+
blockIndex?: number;
|
|
105
|
+
message: string;
|
|
106
|
+
};
|
|
107
|
+
type ValidationResult = {
|
|
108
|
+
issues: Issue[];
|
|
109
|
+
valid: boolean;
|
|
110
|
+
};
|
|
111
|
+
declare function validateCV(markdown: string): ValidationResult;
|
|
112
|
+
|
|
113
|
+
type RGB = {
|
|
114
|
+
r: number;
|
|
115
|
+
g: number;
|
|
116
|
+
b: number;
|
|
117
|
+
};
|
|
118
|
+
/** Size in points. */
|
|
119
|
+
type FontSpec = {
|
|
120
|
+
family: string;
|
|
121
|
+
size: number;
|
|
122
|
+
weight: "normal" | "bold";
|
|
123
|
+
};
|
|
124
|
+
type TextTheme = {
|
|
125
|
+
name: FontSpec;
|
|
126
|
+
section: FontSpec;
|
|
127
|
+
entryTitle: FontSpec;
|
|
128
|
+
body: FontSpec;
|
|
129
|
+
};
|
|
130
|
+
type SpacingTheme = {
|
|
131
|
+
/** Space below a section heading before its content. */
|
|
132
|
+
sectionGap: number;
|
|
133
|
+
/** Space between an entry title and its first line of content. */
|
|
134
|
+
entryGap: number;
|
|
135
|
+
/** Vertical gap between distinct entries (companies / institutions). */
|
|
136
|
+
entrySpacing: number;
|
|
137
|
+
/** Leading (line height) multiplier applied to text size. */
|
|
138
|
+
lineHeight: number;
|
|
139
|
+
/** Horizontal gap between an inline label and its content on one line. */
|
|
140
|
+
columnGap: number;
|
|
141
|
+
/** Space above the header block and between header rows. */
|
|
142
|
+
headerGap: number;
|
|
143
|
+
};
|
|
144
|
+
type Theme = {
|
|
145
|
+
name: string;
|
|
146
|
+
text: TextTheme;
|
|
147
|
+
color: {
|
|
148
|
+
ink: RGB;
|
|
149
|
+
accent: RGB;
|
|
150
|
+
muted: RGB;
|
|
151
|
+
};
|
|
152
|
+
spacing: SpacingTheme;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
declare function createDefaultTheme(): Theme;
|
|
156
|
+
|
|
157
|
+
type ProfilePosition = "left" | "center" | "right";
|
|
158
|
+
type ProfileImage = {
|
|
159
|
+
/** PNG or JPEG bytes. */
|
|
160
|
+
bytes: Uint8Array;
|
|
161
|
+
/** Side length of the (square) photo in points. Default 96. */
|
|
162
|
+
size?: number;
|
|
163
|
+
/** Horizontal position within the header. Default "right". */
|
|
164
|
+
position?: ProfilePosition;
|
|
165
|
+
};
|
|
166
|
+
type FontSet = {
|
|
167
|
+
/** Regular weight TTF bytes (e.g. Noto Sans CJK). */
|
|
168
|
+
regular: Uint8Array;
|
|
169
|
+
bold?: Uint8Array;
|
|
170
|
+
italic?: Uint8Array;
|
|
171
|
+
};
|
|
172
|
+
type RenderOptions = {
|
|
173
|
+
theme?: Theme;
|
|
174
|
+
page?: PageConfig;
|
|
175
|
+
profile?: ProfileImage;
|
|
176
|
+
fonts?: FontSet;
|
|
177
|
+
};
|
|
178
|
+
declare function renderDocument(doc: PDFDocument, document: MarkdownDocument, opts?: RenderOptions): Promise<void>;
|
|
179
|
+
|
|
180
|
+
type RenderMarkdownOptions = {
|
|
181
|
+
theme?: Theme;
|
|
182
|
+
page?: PageConfig;
|
|
183
|
+
profile?: ProfileImage;
|
|
184
|
+
fonts?: FontSet;
|
|
185
|
+
};
|
|
186
|
+
declare function renderMarkdownToPdf(markdown: string, opts?: RenderMarkdownOptions): Promise<Uint8Array>;
|
|
187
|
+
|
|
188
|
+
type GenerateCVOptions = {
|
|
189
|
+
/** Path to the source Markdown file. */
|
|
190
|
+
input: string;
|
|
191
|
+
/** Path where the generated PDF will be written. */
|
|
192
|
+
output: string;
|
|
193
|
+
/** Profile photo to render in the header. */
|
|
194
|
+
profile?: ProfileImage;
|
|
195
|
+
/** Custom TTF fonts for non-Latin text (e.g. Noto Sans CJK). */
|
|
196
|
+
fonts?: FontSet;
|
|
197
|
+
theme?: Theme;
|
|
198
|
+
page?: PageConfig;
|
|
199
|
+
};
|
|
200
|
+
type GenerateCVResult = {
|
|
201
|
+
/** All validation issues found (warnings only; errors throw). */
|
|
202
|
+
issues: Issue[];
|
|
203
|
+
/** Path the PDF was written to. */
|
|
204
|
+
output: string;
|
|
205
|
+
};
|
|
206
|
+
declare function generateCV(options: GenerateCVOptions): Promise<GenerateCVResult>;
|
|
207
|
+
|
|
208
|
+
export { type Cursor, type Flow, type FontSet, type FontSpec, type GenerateCVOptions, type GenerateCVResult, type Issue, type PageConfig, type PageMargins, type PageSize, type Points, type ProfileImage, type RGB, type RenderMarkdownOptions, type Severity, type SpacingTheme, type TextTheme, type Theme, type ValidationResult, contentHeight, contentWidth, createA4, createDefaultTheme, createFlow, generateCV, parseMarkdown, renderDocument, renderMarkdownToPdf, validateCV };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { PDFDocument } from 'pdf-lib';
|
|
2
|
+
|
|
3
|
+
type Points = number;
|
|
4
|
+
type PageMargins = {
|
|
5
|
+
top: Points;
|
|
6
|
+
right: Points;
|
|
7
|
+
bottom: Points;
|
|
8
|
+
left: Points;
|
|
9
|
+
};
|
|
10
|
+
type PageSize = {
|
|
11
|
+
width: Points;
|
|
12
|
+
height: Points;
|
|
13
|
+
};
|
|
14
|
+
type PageConfig = {
|
|
15
|
+
size: PageSize;
|
|
16
|
+
margin: PageMargins;
|
|
17
|
+
};
|
|
18
|
+
declare function contentWidth(page: PageConfig): Points;
|
|
19
|
+
declare function contentHeight(page: PageConfig): Points;
|
|
20
|
+
declare function createA4(margin?: Partial<PageMargins>): PageConfig;
|
|
21
|
+
|
|
22
|
+
type Cursor = {
|
|
23
|
+
x: Points;
|
|
24
|
+
y: Points;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Holds cursor state (relative to the current page's content area, origin at
|
|
28
|
+
* the top-left) and coordinates page-breaks. Does not hardcode any dimension;
|
|
29
|
+
* it asks the page config how much room is left.
|
|
30
|
+
*/
|
|
31
|
+
type Flow = {
|
|
32
|
+
readonly page: PageConfig;
|
|
33
|
+
readonly cursor: Cursor;
|
|
34
|
+
readonly pageNumber: number;
|
|
35
|
+
contentWidth(): Points;
|
|
36
|
+
contentHeight(): Points;
|
|
37
|
+
isLastPageEmpty(): boolean;
|
|
38
|
+
/** True when advancing `dy` would overflow the current content area. */
|
|
39
|
+
needNewPage(dy: Points): boolean;
|
|
40
|
+
/** Break to the next page, resetting the cursor to the top-left. */
|
|
41
|
+
newPage(): void;
|
|
42
|
+
/** Move the cursor down by `dy`. */
|
|
43
|
+
advance(dy: Points): void;
|
|
44
|
+
};
|
|
45
|
+
declare function createFlow(page: PageConfig): Flow;
|
|
46
|
+
|
|
47
|
+
type MarkdownDocument = {
|
|
48
|
+
blocks: MarkdownBlock[];
|
|
49
|
+
};
|
|
50
|
+
type MarkdownBlock = HeadingBlock | ParagraphBlock | ListBlock | ThematicBreakBlock | BlockquoteBlock;
|
|
51
|
+
type BlockquoteBlock = {
|
|
52
|
+
type: 'blockquote';
|
|
53
|
+
children: InlineNode[];
|
|
54
|
+
};
|
|
55
|
+
type HeadingBlock = {
|
|
56
|
+
type: 'heading';
|
|
57
|
+
level: 1 | 2 | 3;
|
|
58
|
+
children: InlineNode[];
|
|
59
|
+
};
|
|
60
|
+
type ParagraphBlock = {
|
|
61
|
+
type: 'paragraph';
|
|
62
|
+
children: InlineNode[];
|
|
63
|
+
};
|
|
64
|
+
type ListBlock = {
|
|
65
|
+
type: 'list';
|
|
66
|
+
ordered: boolean;
|
|
67
|
+
items: ListItem[];
|
|
68
|
+
};
|
|
69
|
+
type ListItem = {
|
|
70
|
+
children: InlineNode[];
|
|
71
|
+
nested?: ListBlock[];
|
|
72
|
+
};
|
|
73
|
+
type ThematicBreakBlock = {
|
|
74
|
+
type: 'thematicBreak';
|
|
75
|
+
};
|
|
76
|
+
type InlineNode = TextNode | StrongNode | EmphasisNode | LinkNode | ImageNode;
|
|
77
|
+
type TextNode = {
|
|
78
|
+
type: 'text';
|
|
79
|
+
text: string;
|
|
80
|
+
};
|
|
81
|
+
type StrongNode = {
|
|
82
|
+
type: 'strong';
|
|
83
|
+
children: InlineNode[];
|
|
84
|
+
};
|
|
85
|
+
type EmphasisNode = {
|
|
86
|
+
type: 'emphasis';
|
|
87
|
+
children: InlineNode[];
|
|
88
|
+
};
|
|
89
|
+
type LinkNode = {
|
|
90
|
+
type: 'link';
|
|
91
|
+
text: string;
|
|
92
|
+
href: string;
|
|
93
|
+
};
|
|
94
|
+
type ImageNode = {
|
|
95
|
+
type: 'image';
|
|
96
|
+
href: string;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
declare function parseMarkdown(markdown: string): MarkdownDocument;
|
|
100
|
+
|
|
101
|
+
type Severity = "error" | "warning";
|
|
102
|
+
type Issue = {
|
|
103
|
+
severity: Severity;
|
|
104
|
+
blockIndex?: number;
|
|
105
|
+
message: string;
|
|
106
|
+
};
|
|
107
|
+
type ValidationResult = {
|
|
108
|
+
issues: Issue[];
|
|
109
|
+
valid: boolean;
|
|
110
|
+
};
|
|
111
|
+
declare function validateCV(markdown: string): ValidationResult;
|
|
112
|
+
|
|
113
|
+
type RGB = {
|
|
114
|
+
r: number;
|
|
115
|
+
g: number;
|
|
116
|
+
b: number;
|
|
117
|
+
};
|
|
118
|
+
/** Size in points. */
|
|
119
|
+
type FontSpec = {
|
|
120
|
+
family: string;
|
|
121
|
+
size: number;
|
|
122
|
+
weight: "normal" | "bold";
|
|
123
|
+
};
|
|
124
|
+
type TextTheme = {
|
|
125
|
+
name: FontSpec;
|
|
126
|
+
section: FontSpec;
|
|
127
|
+
entryTitle: FontSpec;
|
|
128
|
+
body: FontSpec;
|
|
129
|
+
};
|
|
130
|
+
type SpacingTheme = {
|
|
131
|
+
/** Space below a section heading before its content. */
|
|
132
|
+
sectionGap: number;
|
|
133
|
+
/** Space between an entry title and its first line of content. */
|
|
134
|
+
entryGap: number;
|
|
135
|
+
/** Vertical gap between distinct entries (companies / institutions). */
|
|
136
|
+
entrySpacing: number;
|
|
137
|
+
/** Leading (line height) multiplier applied to text size. */
|
|
138
|
+
lineHeight: number;
|
|
139
|
+
/** Horizontal gap between an inline label and its content on one line. */
|
|
140
|
+
columnGap: number;
|
|
141
|
+
/** Space above the header block and between header rows. */
|
|
142
|
+
headerGap: number;
|
|
143
|
+
};
|
|
144
|
+
type Theme = {
|
|
145
|
+
name: string;
|
|
146
|
+
text: TextTheme;
|
|
147
|
+
color: {
|
|
148
|
+
ink: RGB;
|
|
149
|
+
accent: RGB;
|
|
150
|
+
muted: RGB;
|
|
151
|
+
};
|
|
152
|
+
spacing: SpacingTheme;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
declare function createDefaultTheme(): Theme;
|
|
156
|
+
|
|
157
|
+
type ProfilePosition = "left" | "center" | "right";
|
|
158
|
+
type ProfileImage = {
|
|
159
|
+
/** PNG or JPEG bytes. */
|
|
160
|
+
bytes: Uint8Array;
|
|
161
|
+
/** Side length of the (square) photo in points. Default 96. */
|
|
162
|
+
size?: number;
|
|
163
|
+
/** Horizontal position within the header. Default "right". */
|
|
164
|
+
position?: ProfilePosition;
|
|
165
|
+
};
|
|
166
|
+
type FontSet = {
|
|
167
|
+
/** Regular weight TTF bytes (e.g. Noto Sans CJK). */
|
|
168
|
+
regular: Uint8Array;
|
|
169
|
+
bold?: Uint8Array;
|
|
170
|
+
italic?: Uint8Array;
|
|
171
|
+
};
|
|
172
|
+
type RenderOptions = {
|
|
173
|
+
theme?: Theme;
|
|
174
|
+
page?: PageConfig;
|
|
175
|
+
profile?: ProfileImage;
|
|
176
|
+
fonts?: FontSet;
|
|
177
|
+
};
|
|
178
|
+
declare function renderDocument(doc: PDFDocument, document: MarkdownDocument, opts?: RenderOptions): Promise<void>;
|
|
179
|
+
|
|
180
|
+
type RenderMarkdownOptions = {
|
|
181
|
+
theme?: Theme;
|
|
182
|
+
page?: PageConfig;
|
|
183
|
+
profile?: ProfileImage;
|
|
184
|
+
fonts?: FontSet;
|
|
185
|
+
};
|
|
186
|
+
declare function renderMarkdownToPdf(markdown: string, opts?: RenderMarkdownOptions): Promise<Uint8Array>;
|
|
187
|
+
|
|
188
|
+
type GenerateCVOptions = {
|
|
189
|
+
/** Path to the source Markdown file. */
|
|
190
|
+
input: string;
|
|
191
|
+
/** Path where the generated PDF will be written. */
|
|
192
|
+
output: string;
|
|
193
|
+
/** Profile photo to render in the header. */
|
|
194
|
+
profile?: ProfileImage;
|
|
195
|
+
/** Custom TTF fonts for non-Latin text (e.g. Noto Sans CJK). */
|
|
196
|
+
fonts?: FontSet;
|
|
197
|
+
theme?: Theme;
|
|
198
|
+
page?: PageConfig;
|
|
199
|
+
};
|
|
200
|
+
type GenerateCVResult = {
|
|
201
|
+
/** All validation issues found (warnings only; errors throw). */
|
|
202
|
+
issues: Issue[];
|
|
203
|
+
/** Path the PDF was written to. */
|
|
204
|
+
output: string;
|
|
205
|
+
};
|
|
206
|
+
declare function generateCV(options: GenerateCVOptions): Promise<GenerateCVResult>;
|
|
207
|
+
|
|
208
|
+
export { type Cursor, type Flow, type FontSet, type FontSpec, type GenerateCVOptions, type GenerateCVResult, type Issue, type PageConfig, type PageMargins, type PageSize, type Points, type ProfileImage, type RGB, type RenderMarkdownOptions, type Severity, type SpacingTheme, type TextTheme, type Theme, type ValidationResult, contentHeight, contentWidth, createA4, createDefaultTheme, createFlow, generateCV, parseMarkdown, renderDocument, renderMarkdownToPdf, validateCV };
|