You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 02:00:01