如何获取pdfmake中页面对象的最终尺寸?解决动态收据布局难题
用pdfmake实现动态列销售订单收据(Node后端)
一、动态切换单/双列表格方案
因商品行高度不一致,无法通过行数判断布局,核心思路是预计算内容总高度,对比页面可用高度后选择对应布局:
1. 预计算行高与总高度
先封装近似计算文本行高的函数,根据字体大小、文本长度和列宽估算每行高度:
function calculateRowHeight(text, fontSize, maxWidth) { // 按默认Roboto字体的字符宽度比例估算,可根据实际字体调整 const charWidth = fontSize * 0.6; const charsPerLine = Math.floor(maxWidth / charWidth); const lines = Math.ceil(text.length / charsPerLine); // 行高取字号的1.3倍,匹配pdfmake默认行高 return lines * fontSize * 1.3; }
结合页面参数计算所有商品行的总高度:
// 页面基础参数(A4纸为例) const pageParams = { width: 210, height: 297, margins: [20, 30, 20, 30], // 上、下、左、右边距 headerFooterHeight: 40, // 预留页头页脚高度 fontSize: 10 }; const availableHeight = pageParams.height - pageParams.margins[0] - pageParams.margins[1] - pageParams.headerFooterHeight; const singleColWidth = pageParams.width - pageParams.margins[2] - pageParams.margins[3]; // 模拟商品数据 const products = [ { name: '大容量无线充电移动电源 20000mAh', qty: 2, price: 199 }, { name: 'Type-C快充数据线', qty: 3, price: 29 }, // ...更多商品 ]; // 计算表格总高度(含表头) let totalTableHeight = pageParams.fontSize * 1.5; // 表头高度 products.forEach(prod => { // 商品名称列占单列宽度的60%,计算其行高 const nameHeight = calculateRowHeight(prod.name, pageParams.fontSize, singleColWidth * 0.6); totalTableHeight += nameHeight; });
2. 生成对应布局
根据总高度判断,选择单列或双列布局:
let receiptContent = []; // 添加页头 receiptContent.push({ text: 'XX商城销售订单', style: 'header', alignment: 'center' }); if (totalTableHeight <= availableHeight) { // 单列表格:铺满整页宽度 receiptContent.push({ table: { headerRows: 1, widths: ['*', 'auto', 'auto'], // 名称列自适应,数量、单价固定宽度 body: [ ['商品名称', '数量', '单价'], ...products.map(p => [p.name, p.qty, `¥${p.price}`]) ] }, layout: 'lightHorizontalLines' }); } else { // 双列表格:拆分商品列表为两组,左右排列 const splitIndex = Math.ceil(products.length / 2); const leftProducts = products.slice(0, splitIndex); const rightProducts = products.slice(splitIndex); receiptContent.push({ columns: [ { width: (singleColWidth - 10) / 2, // 中间留10mm间距 table: { headerRows: 1, widths: ['*', 'auto', 'auto'], body: [ ['商品名称', '数量', '单价'], ...leftProducts.map(p => [p.name, p.qty, `¥${p.price}`]) ] }, layout: 'lightHorizontalLines' }, { width: (singleColWidth - 10) / 2, table: { headerRows: 1, widths: ['*', 'auto', 'auto'], body: [ ['商品名称', '数量', '单价'], ...rightProducts.map(p => [p.name, p.qty, `¥${p.price}`]) ] }, layout: 'lightHorizontalLines' } ], columnGap: 10 }); } // 最终PDF定义 const docDefinition = { pageSize: 'A4', pageMargins: pageParams.margins, styles: { header: { fontSize: 16, bold: true, margin: [0, 0, 0, 20] } }, content: receiptContent };
3. Node后端生成PDF
引入pdfmake并生成文件/返回流:
const pdfMake = require('pdfmake'); const fs = require('fs'); // 配置字体(需提前准备字体文件) const fonts = { Roboto: { normal: './fonts/Roboto-Regular.ttf', bold: './fonts/Roboto-Bold.ttf' } }; const printer = new pdfMake(fonts); const pdfDoc = printer.createPdfKitDocument(docDefinition); // 保存到本地文件 pdfDoc.pipe(fs.createWriteStream('receipt.pdf')); pdfDoc.end(); // 或者返回给HTTP客户端(Express示例) // pdfDoc.pipe(res); // pdfDoc.end();
二、获取pdfmake对象的最终尺寸
pdfmake本身无实时获取渲染后对象尺寸的API,可通过以下方式实现:
1. 预计算近似值
和前文行高计算逻辑一致,针对文本、表格等元素,根据字体、宽度、内容长度提前估算尺寸,精度足够应对大多数布局预估场景。
2. 基于PDFKit的事件跟踪
pdfmake底层依赖PDFKit,可监听PDFKit实例的事件跟踪坐标:
const pdfDoc = printer.createPdfKitDocument(docDefinition); let currentY = pageParams.margins[0]; // 初始Y坐标为页面上边距 // 新页面触发时重置Y坐标 pdfDoc.on('pageAdded', () => { currentY = pageParams.margins[0]; }); // 若需精确跟踪,可直接使用PDFKit的API手动构建部分内容(替代pdfmake封装) // 例如添加文本后获取当前Y坐标: pdfDoc.text('测试文本', pageParams.margins[2], currentY, { fontSize: 10 }); currentY = pdfDoc.y; // 获取文本渲染后的Y坐标,从而计算高度
3. 生成后解析PDF
若需精确的最终尺寸,可使用pdf-lib等第三方库解析生成的PDF文件,提取元素位置和尺寸:
const { PDFDocument } = require('pdf-lib'); const fs = require('fs'); async function getElementDimensions(pdfPath) { const pdfBytes = fs.readFileSync(pdfPath); const pdfDoc = await PDFDocument.load(pdfBytes); const pages = pdfDoc.getPages(); // 解析第一页内容(示例) const firstPage = pages[0]; const content = firstPage.getContent(); // 遍历内容流提取元素尺寸,逻辑需根据PDF内容结构调整 }
内容的提问来源于stack exchange,提问作者SepSol
相关产品推荐
相关产品推荐

