feat: complete OpenAPI docs with all Puppeteer PDF options
Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Has been cancelled

- Add scale, pageRanges, preferCSSPageSize, width, height to PdfOptions
- Add headerTemplate, footerTemplate, displayHeaderFooter to docs
- Pass all options through routes to browser service for HTML, Markdown, and URL endpoints
- Export PdfRenderOptions interface for type reuse
- Bump version to 0.4.5
This commit is contained in:
DocFast Bot 2026-02-21 13:19:31 +00:00
parent f332d425ec
commit 1545df9a7b
5 changed files with 111 additions and 21 deletions

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "docfast-api", "name": "docfast-api",
"version": "0.4.3", "version": "0.4.5",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "docfast-api", "name": "docfast-api",
"version": "0.4.3", "version": "0.4.5",
"dependencies": { "dependencies": {
"compression": "^1.8.1", "compression": "^1.8.1",
"express": "^4.21.0", "express": "^4.21.0",

View file

@ -1,6 +1,6 @@
{ {
"name": "docfast-api", "name": "docfast-api",
"version": "0.4.4", "version": "0.4.5",
"description": "Markdown/HTML to PDF API with built-in invoice templates", "description": "Markdown/HTML to PDF API with built-in invoice templates",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {

View file

@ -47,6 +47,14 @@ interface ConvertBody {
margin?: { top?: string; right?: string; bottom?: string; left?: string }; margin?: { top?: string; right?: string; bottom?: string; left?: string };
printBackground?: boolean; printBackground?: boolean;
filename?: string; filename?: string;
headerTemplate?: string;
footerTemplate?: string;
displayHeaderFooter?: boolean;
scale?: number;
pageRanges?: string;
preferCSSPageSize?: boolean;
width?: string;
height?: string;
} }
/** /**
@ -131,6 +139,14 @@ convertRouter.post("/html", async (req: Request & { acquirePdfSlot?: () => Promi
landscape: body.landscape, landscape: body.landscape,
margin: body.margin, margin: body.margin,
printBackground: body.printBackground, printBackground: body.printBackground,
headerTemplate: body.headerTemplate,
footerTemplate: body.footerTemplate,
displayHeaderFooter: body.displayHeaderFooter,
scale: body.scale,
pageRanges: body.pageRanges,
preferCSSPageSize: body.preferCSSPageSize,
width: body.width,
height: body.height,
}); });
const filename = sanitizeFilename(body.filename || "document.pdf"); const filename = sanitizeFilename(body.filename || "document.pdf");
@ -228,6 +244,14 @@ convertRouter.post("/markdown", async (req: Request & { acquirePdfSlot?: () => P
landscape: body.landscape, landscape: body.landscape,
margin: body.margin, margin: body.margin,
printBackground: body.printBackground, printBackground: body.printBackground,
headerTemplate: body.headerTemplate,
footerTemplate: body.footerTemplate,
displayHeaderFooter: body.displayHeaderFooter,
scale: body.scale,
pageRanges: body.pageRanges,
preferCSSPageSize: body.preferCSSPageSize,
width: body.width,
height: body.height,
}); });
const filename = sanitizeFilename(body.filename || "document.pdf"); const filename = sanitizeFilename(body.filename || "document.pdf");
@ -310,7 +334,7 @@ convertRouter.post("/url", async (req: Request & { acquirePdfSlot?: () => Promis
res.status(415).json({ error: "Unsupported Content-Type. Use application/json." }); res.status(415).json({ error: "Unsupported Content-Type. Use application/json." });
return; return;
} }
const body = req.body as { url?: string; format?: string; landscape?: boolean; margin?: any; printBackground?: boolean; waitUntil?: string; filename?: string }; const body = req.body as { url?: string; format?: string; landscape?: boolean; margin?: any; printBackground?: boolean; waitUntil?: string; filename?: string; headerTemplate?: string; footerTemplate?: string; displayHeaderFooter?: boolean; scale?: number; pageRanges?: string; preferCSSPageSize?: boolean; width?: string; height?: string };
if (!body.url) { if (!body.url) {
res.status(400).json({ error: "Missing 'url' field" }); res.status(400).json({ error: "Missing 'url' field" });
@ -355,6 +379,14 @@ convertRouter.post("/url", async (req: Request & { acquirePdfSlot?: () => Promis
landscape: body.landscape, landscape: body.landscape,
margin: body.margin, margin: body.margin,
printBackground: body.printBackground, printBackground: body.printBackground,
headerTemplate: body.headerTemplate,
footerTemplate: body.footerTemplate,
displayHeaderFooter: body.displayHeaderFooter,
scale: body.scale,
pageRanges: body.pageRanges,
preferCSSPageSize: body.preferCSSPageSize,
width: body.width,
height: body.height,
waitUntil: body.waitUntil, waitUntil: body.waitUntil,
hostResolverRules: `MAP ${parsed.hostname} ${resolvedAddress}`, hostResolverRules: `MAP ${parsed.hostname} ${resolvedAddress}`,
}); });

View file

@ -218,17 +218,24 @@ export async function closeBrowser(): Promise<void> {
instances.length = 0; instances.length = 0;
} }
export interface PdfRenderOptions {
format?: string;
landscape?: boolean;
margin?: { top?: string; right?: string; bottom?: string; left?: string };
printBackground?: boolean;
headerTemplate?: string;
footerTemplate?: string;
displayHeaderFooter?: boolean;
scale?: number;
pageRanges?: string;
preferCSSPageSize?: boolean;
width?: string;
height?: string;
}
export async function renderPdf( export async function renderPdf(
html: string, html: string,
options: { options: PdfRenderOptions = {}
format?: string;
landscape?: boolean;
margin?: { top?: string; right?: string; bottom?: string; left?: string };
printBackground?: boolean;
headerTemplate?: string;
footerTemplate?: string;
displayHeaderFooter?: boolean;
} = {}
): Promise<Buffer> { ): Promise<Buffer> {
const { page, instance } = await acquirePage(); const { page, instance } = await acquirePage();
try { try {
@ -245,6 +252,11 @@ export async function renderPdf(
headerTemplate: options.headerTemplate, headerTemplate: options.headerTemplate,
footerTemplate: options.footerTemplate, footerTemplate: options.footerTemplate,
displayHeaderFooter: options.displayHeaderFooter || false, displayHeaderFooter: options.displayHeaderFooter || false,
...(options.scale !== undefined && { scale: options.scale }),
...(options.pageRanges && { pageRanges: options.pageRanges }),
...(options.preferCSSPageSize !== undefined && { preferCSSPageSize: options.preferCSSPageSize }),
...(options.width && { width: options.width }),
...(options.height && { height: options.height }),
}); });
return Buffer.from(pdf); return Buffer.from(pdf);
})(), })(),
@ -260,11 +272,7 @@ export async function renderPdf(
export async function renderUrlPdf( export async function renderUrlPdf(
url: string, url: string,
options: { options: PdfRenderOptions & {
format?: string;
landscape?: boolean;
margin?: { top?: string; right?: string; bottom?: string; left?: string };
printBackground?: boolean;
waitUntil?: string; waitUntil?: string;
hostResolverRules?: string; hostResolverRules?: string;
} = {} } = {}
@ -316,6 +324,14 @@ export async function renderUrlPdf(
landscape: options.landscape || false, landscape: options.landscape || false,
printBackground: options.printBackground !== false, printBackground: options.printBackground !== false,
margin: options.margin || { top: "0", right: "0", bottom: "0", left: "0" }, margin: options.margin || { top: "0", right: "0", bottom: "0", left: "0" },
...(options.headerTemplate && { headerTemplate: options.headerTemplate }),
...(options.footerTemplate && { footerTemplate: options.footerTemplate }),
...(options.displayHeaderFooter !== undefined && { displayHeaderFooter: options.displayHeaderFooter }),
...(options.scale !== undefined && { scale: options.scale }),
...(options.pageRanges && { pageRanges: options.pageRanges }),
...(options.preferCSSPageSize !== undefined && { preferCSSPageSize: options.preferCSSPageSize }),
...(options.width && { width: options.width }),
...(options.height && { height: options.height }),
}); });
return Buffer.from(pdf); return Buffer.from(pdf);
})(), })(),

View file

@ -49,7 +49,7 @@ const options: swaggerJsdoc.Options = {
type: "string", type: "string",
enum: ["A4", "Letter", "Legal", "A3", "A5", "Tabloid"], enum: ["A4", "Letter", "Legal", "A3", "A5", "Tabloid"],
default: "A4", default: "A4",
description: "Page size", description: "Page size. Ignored if width/height are set.",
}, },
landscape: { landscape: {
type: "boolean", type: "boolean",
@ -58,6 +58,7 @@ const options: swaggerJsdoc.Options = {
}, },
margin: { margin: {
type: "object", type: "object",
description: "Page margins. Accepts CSS units (e.g. '20mm', '1in', '72px').",
properties: { properties: {
top: { type: "string", example: "20mm" }, top: { type: "string", example: "20mm" },
bottom: { type: "string", example: "20mm" }, bottom: { type: "string", example: "20mm" },
@ -68,12 +69,53 @@ const options: swaggerJsdoc.Options = {
printBackground: { printBackground: {
type: "boolean", type: "boolean",
default: true, default: true,
description: "Print background graphics", description: "Print background graphics and colors",
}, },
filename: { filename: {
type: "string", type: "string",
default: "document.pdf", default: "document.pdf",
description: "Suggested filename for the PDF", description: "Suggested filename for the PDF download",
},
headerTemplate: {
type: "string",
description: "HTML template for the page header. Requires displayHeaderFooter: true. Use these CSS classes for dynamic values: date, title, url, pageNumber, totalPages. Example: '<span class=\"pageNumber\"></span> / <span class=\"totalPages\"></span>'",
},
footerTemplate: {
type: "string",
description: "HTML template for the page footer. Requires displayHeaderFooter: true. Supports the same CSS classes as headerTemplate.",
},
displayHeaderFooter: {
type: "boolean",
default: false,
description: "Whether to show header and footer templates. Must be true for headerTemplate/footerTemplate to render.",
},
scale: {
type: "number",
minimum: 0.1,
maximum: 2,
default: 1,
description: "Scale of the webpage rendering. 1 = 100%, 0.5 = 50%, 2 = 200%.",
example: 1,
},
pageRanges: {
type: "string",
description: "Paper ranges to print, e.g. '1-5', '1,3,5', '2-4,6'. Empty string means all pages.",
example: "1-3",
},
preferCSSPageSize: {
type: "boolean",
default: false,
description: "Give any CSS @page size declared in the page priority over the format option.",
},
width: {
type: "string",
description: "Paper width with units. Overrides format. Accepts CSS units (e.g. '10in', '210mm', '8.5in').",
example: "8.5in",
},
height: {
type: "string",
description: "Paper height with units. Overrides format. Accepts CSS units (e.g. '11in', '297mm').",
example: "11in",
}, },
}, },
}, },