Skip to content

安装两个库

Shell
pnpm install html2canvas jspdf
pnpm install html2canvas jspdf

函数代码

能有效防止分页文字截断的问题

JavaScript
import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';

/**
 * 按照A4纸分页生成PDF
 * @param {HTMLElement} ele - 需要导出pdf的容器元素(dom节点 不是id)
 * @param {string} [pdfFileName] - 导出文件的名字
 * @param {Object} [options] - 配置选项
 * @param {number} [options.width=595.28] - A4纸宽度
 * @param {number} [options.height=841.89] - A4纸高度
 * @param {number} [options.scale=3] - 缩放层级,提高清晰度
 * @param {number} [options.margin=0] - 纸张左右边距
 * @param {string} [options.splitClassName] - 避免分段截断的类名,当pdf有多页时需要传入此参数
 * @param {HTMLElement} [options.header] - 页眉元素
 * @param {HTMLElement} [options.footer] - 页脚元素
 * @param {string} [options.direction='p'] - 'p' 或 'l',默认 'p'(纵向),'l' 为横向
 * @param {string} [options.outputType='save'] - 'save' | 'file' | 'blob' 等,默认 'save'
 * @returns {Promise} PDF生成结果
 */
export const PagingDownPDF = async (ele, pdfFileName = null, options = {}) => {
  const {
    width = 595.28,
    height = 841.89,
    scale = 3,
    margin = 20,
    splitClassName = '',
    header = null,
    footer = null,
    direction = 'p',
    outputType = 'save',
  } = options;

  if (!(ele instanceof HTMLElement)) {
    throw new TypeError('element节点请传入dom节点');
  }

  // 内部状态变量
  const state = {
    element: ele,
    contentWidth: width - margin,
    contentHeaderWidth: width - margin,
    contentFooterWidth: width - margin,
    outputType: outputType,
    fileName: pdfFileName || '导出的pdf文件',
    scale: scale,
    baseY: 15,
    isTransformBaseY: false,
    header: header,
    footer: footer,
    direction: direction,
    A4_WIDTH: width,
    A4_HEIGHT: height,
    splitClassName: splitClassName,
    pdfFooterHeight: 0,
    pdfHeaderHeight: 0,
    pdf: null,
    rate: 1,
    pages: [],
    canvasEle: null,
    __header: null,
    __footer: null,
    originalPageHeight: 0,
  };

  if (state.direction === 'l') {
    [state.A4_HEIGHT, state.A4_WIDTH] = [state.A4_WIDTH, state.A4_HEIGHT];
  }

  // 内部工具函数
  const loadImage = (url) => {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.setAttribute('crossOrigin', 'anonymous');
      img.src = url;
      img.onload = () => resolve(img);
      img.onerror = () => reject(new Error('图像加载失败'));
    });
  };

  const toCanvas = async (element, width) => {
    let canvas = await html2canvas(element, {
      allowTaint: true,
      scale: state.scale || window.devicePixelRatio * 2,
      useCORS: true,
    });
    const canvasWidth = canvas.width;
    const canvasHeight = canvas.height;
    const height = (width / canvasWidth) * canvasHeight;
    const canvasData = canvas.toDataURL('image/jpeg', 1.0);
    canvas = null;
    return { width, height, data: canvasData };
  };

  const createAndDisplayCanvas = async () => {
    const imgData = await toCanvas(state.element, state.contentWidth);
    const canvasEle = document.createElement('canvas');
    canvasEle.width = imgData.width;
    canvasEle.height = imgData.height;
    canvasEle.style.position = 'fixed';
    canvasEle.style.top = '0';
    canvasEle.style.right = '0';
    state.canvasEle = canvasEle;
    document.body.appendChild(canvasEle);
    const ctx = canvasEle.getContext('2d');
    const img = await loadImage(imgData.data);
    ctx.drawImage(img, 0, 0, imgData.width, imgData.height);
    scan(ctx, imgData);
  };

  const scan = (ctx, imgData) => {
    if (!ctx || !imgData) {
      throw new Error('Invalid arguments: ctx or imgData is null/undefined');
    }

    // 从内容底部开始向上扫描,找到实际内容结束位置
    let scanPosition = imgData.height - 1; // 从底部开始扫描
    let contentEndPosition = imgData.height; // 记录内容实际结束位置

    // 向上扫描找到最后一个非空白行
    while (scanPosition >= 0) {
      const imageData = ctx.getImageData(0, scanPosition, imgData.width, 1);
      const uniqueArr = Array.from(new Set(imageData.data));

      // 如果不是空白行(即包含内容)
      if (uniqueArr.length > 1) {
        contentEndPosition = scanPosition + 1; // 找到内容实际结束位置
        break;
      }
      scanPosition--;
    }

    // 如果整个内容都是空白,则只有一页
    if (contentEndPosition === 0) {
      state.pages = [0];
      if (state.canvasEle) {
        state.canvasEle.remove();
        state.canvasEle = null;
      }
      return;
    }

    // 根据实际内容高度计算分页
    const pageHeight = parseInt(state.originalPageHeight, 10);
    let currentPageEnd = pageHeight;
    state.pages = [0]; // 重置分页数组

    while (currentPageEnd < contentEndPosition) {
      // 在当前分页点附近寻找最佳分割点(空白区域)
      let bestSplitPoint = currentPageEnd;
      const searchStart = Math.max(currentPageEnd - 50, 0); // 向前搜索50像素
      const searchEnd = Math.min(currentPageEnd + 50, contentEndPosition); // 向后搜索50像素

      // 在搜索范围内寻找空白区域作为分割点
      for (let y = searchStart; y < searchEnd; y++) {
        const imageData = ctx.getImageData(0, y, imgData.width, 1);
        const uniqueArr = Array.from(new Set(imageData.data));

        if (uniqueArr.length === 1 && y > state.pages[state.pages.length - 1] + pageHeight * 0.8) {
          // 找到空白区域,且在当前页的80%之后
          bestSplitPoint = y;
          break;
        }
      }

      state.pages.push(bestSplitPoint);
      currentPageEnd = bestSplitPoint + pageHeight;
    }

    // 检查最后一页是否内容太少,如果是则不创建该页
    const lastPageContentHeight = contentEndPosition - state.pages[state.pages.length - 1];
    if (lastPageContentHeight < pageHeight * 0.1 && state.pages.length > 1) {
      // 如果最后一页内容少于10%的页面高度,且不是第一页,则移除最后一页
      state.pages.pop();
    }

    // 清理临时canvas
    if (state.canvasEle) {
      state.canvasEle.remove();
      state.canvasEle = null;
    }
  };

  const preventSplitElements = (pageHeight) => {
    if (!state.splitClassName) return;
    const elements = state.element.querySelectorAll(`.${state.splitClassName}`);
    const containerTop = state.element.getBoundingClientRect().top;

    elements.forEach((el) => {
      const rect = el.getBoundingClientRect();
      const offsetTop = rect.top - containerTop;
      const bottom = offsetTop + rect.height;
      const startPage = Math.floor(offsetTop / pageHeight);
      const endPage = Math.floor(bottom / pageHeight);

      if (startPage !== endPage) {
        const placeholder = document.createElement('div');
        placeholder.className = 'pdf-split-placeholder';
        placeholder.style.height = startPage * pageHeight - offsetTop + 'px';
        placeholder.style.width = '100%';
        el.parentNode.insertBefore(placeholder, el);
      }
    });
  };

  const addImage = (_x, _y, pdf, data, width, height) => {
    pdf.addImage(data, 'JPEG', _x, _y, width, height);
  };

  const addBlank = (x, y, width, height, pdf) => {
    pdf.setFillColor(255, 255, 255);
    pdf.rect(x, y, Math.ceil(width), Math.ceil(height), 'F');
  };

  const addHeader = async (pageNo, header, pdf, contentWidth) => {
    if (!header || !(header instanceof HTMLElement)) {
      return;
    }
    if (!state.__header) {
      state.__header = await toCanvas(header, contentWidth);
    }
    const { height, data } = state.__header;
    const leftX = (state.A4_WIDTH - state.contentHeaderWidth) / 2;
    pdf.addImage(data, 'JPEG', leftX, 0, contentWidth, height);
  };

  const addFooter = async (pageSize, pageNo, footer, pdf, contentWidth) => {
    if (!footer || !(footer instanceof HTMLElement)) {
      return;
    }

    const pageNoDom = footer.querySelector('.pdf-footer-page');
    const pageSizeDom = footer.querySelector('.pdf-footer-page-count');
    if (pageNoDom) {
      pageNoDom.innerText = pageNo;
    }
    if (pageSizeDom) {
      pageSizeDom.innerText = pageSize;
    }

    if (pageNoDom || !state.__footer) {
      state.__footer = await toCanvas(footer, contentWidth);
    }
    const leftX = (state.A4_WIDTH - state.contentFooterWidth) / 2;
    const { height, data } = state.__footer;
    pdf.addImage(data, 'JPEG', leftX, state.A4_HEIGHT - height, contentWidth, height);
  };

  const getPdfByType = (pdf) => {
    let result = null;
    switch (state.outputType) {
      case 'file':
        result = new File([pdf.output('blob')], state.fileName, {
          type: 'application/pdf',
          lastModified: Date.now(),
        });
        break;
      case 'save':
        result = pdf.save(state.fileName);
        break;
      default:
        result = pdf.output(state.outputType);
    }
    return result;
  };

  // 主要的PDF生成逻辑
  // 滚动置顶,防止顶部空白
  window.pageYOffset = 0;
  document.documentElement.scrollTop = 0;
  document.body.scrollTop = 0;

  return new Promise(async (resolve, reject) => {
    // jsPDF实例
    const pdf = new jsPDF({
      unit: 'pt',
      format: 'a4',
      orientation: state.direction,
    });

    state.pdf = pdf;
    let pdfFooterHeight = 0;
    let pdfHeaderHeight = 0;

    // 距离PDF左边的距离,/ 2 表示居中 ,,预留空间给左边,  右边,也就是左右页边距
    const baseX = (state.A4_WIDTH - state.contentWidth) / 2;

    // 距离PDF 页眉和页脚的间距, 留白留空
    let baseY = state.baseY;
    // 元素在网页页面的宽度
    const elementWidth = state.element.scrollWidth;

    // PDF内容宽度 和 在HTML中宽度 的比, 用于将 元素在网页的高度 转化为 PDF内容内的高度, 将 元素距离网页顶部的高度  转化为 距离Canvas顶部的高度
    const rate = state.contentWidth / elementWidth;
    state.rate = rate;
    if (state.isTransformBaseY) {
      state.baseY = baseY = baseY * rate;
    }

    // 页脚元素 经过转换后在PDF页面的高度
    if (state.footer) {
      pdfFooterHeight = (await toCanvas(state.footer, state.contentFooterWidth)).height;
      state.pdfFooterHeight = pdfFooterHeight;
    }

    // 页眉元素 经过转换后在PDF的高度
    if (state.header) {
      pdfHeaderHeight = (await toCanvas(state.header, state.contentHeaderWidth)).height;
      state.pdfHeaderHeight = pdfHeaderHeight;
    }

    // 除去页头、页眉、还有内容与两者之间的间距后 每页内容的实际高度
    const originalPageHeight = state.A4_HEIGHT - pdfFooterHeight - pdfHeaderHeight - 2 * baseY;
    state.originalPageHeight = originalPageHeight;

    // 处理 splitClassName 元素,避免被分页截断
    preventSplitElements(originalPageHeight / state.rate);

    state.pages = [0]; // 要从0开始
    // 计算分页
    await createAndDisplayCanvas();
    const pages = state.pages;
    const { width, height, data } = await toCanvas(state.element, state.contentWidth);
    // 扫描函数已经处理了所有分页逻辑,这里不再需要额外的分页判断

    // 根据分页位置 开始分页生成pdf
    for (let i = 0; i < pages.length; ++i) {
      // 页眉高度
      const pdfHeaderH = pdfHeaderHeight;
      // 页脚高度
      const pdfFooterH = pdfFooterHeight;
      // 根据分页位置新增图片,要排除页眉和顶部留白
      addImage(baseX, baseY + pdfHeaderH - pages[i], pdf, data, width, height);

      // 将 内容 与 页眉之间留空留白的部分进行遮白处理
      addBlank(0, pdfHeaderH, state.A4_WIDTH, baseY, pdf);
      // 将 内容 与 页脚之间留空留白的部分进行遮白处理
      addBlank(0, state.A4_HEIGHT - baseY - pdfFooterH, state.A4_WIDTH, baseY, pdf);
      // 对于除最后一页外,对 内容 的多余部分进行遮白处理
      if (i < pages.length - 1) {
        // 获取当前页面需要的内容部分高度
        const imageHeight = pages[i + 1] - pages[i];
        // 对多余的内容部分进行遮白,但只在当前页面内容不足一页时进行遮白
        if (imageHeight < originalPageHeight) {
          addBlank(
            0,
            baseY + imageHeight + pdfHeaderH,
            state.A4_WIDTH,
            state.A4_HEIGHT - imageHeight,
            pdf,
          );
        }
      }
      // 添加页眉
      await addHeader(i + 1, state.header, pdf, state.contentHeaderWidth);
      // 添加页脚
      await addFooter(pages.length, i + 1, state.footer, pdf, state.contentFooterWidth);

      // 若不是最后一页,则分页
      if (i !== pages.length - 1) {
        // 增加分页
        pdf.addPage();
      }
    }
    try {
      const result = await getPdfByType(pdf);

      // 清理插入的占位符
      const placeholders = state.element.querySelectorAll('.pdf-split-placeholder');
      placeholders.forEach((el) => el.remove());

      resolve({
        pdfResult: result,
      });
    } catch (error) {
      reject('生成pdf出错', error);
    }
  });
};
import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';

/**
 * 按照A4纸分页生成PDF
 * @param {HTMLElement} ele - 需要导出pdf的容器元素(dom节点 不是id)
 * @param {string} [pdfFileName] - 导出文件的名字
 * @param {Object} [options] - 配置选项
 * @param {number} [options.width=595.28] - A4纸宽度
 * @param {number} [options.height=841.89] - A4纸高度
 * @param {number} [options.scale=3] - 缩放层级,提高清晰度
 * @param {number} [options.margin=0] - 纸张左右边距
 * @param {string} [options.splitClassName] - 避免分段截断的类名,当pdf有多页时需要传入此参数
 * @param {HTMLElement} [options.header] - 页眉元素
 * @param {HTMLElement} [options.footer] - 页脚元素
 * @param {string} [options.direction='p'] - 'p' 或 'l',默认 'p'(纵向),'l' 为横向
 * @param {string} [options.outputType='save'] - 'save' | 'file' | 'blob' 等,默认 'save'
 * @returns {Promise} PDF生成结果
 */
export const PagingDownPDF = async (ele, pdfFileName = null, options = {}) => {
  const {
    width = 595.28,
    height = 841.89,
    scale = 3,
    margin = 20,
    splitClassName = '',
    header = null,
    footer = null,
    direction = 'p',
    outputType = 'save',
  } = options;

  if (!(ele instanceof HTMLElement)) {
    throw new TypeError('element节点请传入dom节点');
  }

  // 内部状态变量
  const state = {
    element: ele,
    contentWidth: width - margin,
    contentHeaderWidth: width - margin,
    contentFooterWidth: width - margin,
    outputType: outputType,
    fileName: pdfFileName || '导出的pdf文件',
    scale: scale,
    baseY: 15,
    isTransformBaseY: false,
    header: header,
    footer: footer,
    direction: direction,
    A4_WIDTH: width,
    A4_HEIGHT: height,
    splitClassName: splitClassName,
    pdfFooterHeight: 0,
    pdfHeaderHeight: 0,
    pdf: null,
    rate: 1,
    pages: [],
    canvasEle: null,
    __header: null,
    __footer: null,
    originalPageHeight: 0,
  };

  if (state.direction === 'l') {
    [state.A4_HEIGHT, state.A4_WIDTH] = [state.A4_WIDTH, state.A4_HEIGHT];
  }

  // 内部工具函数
  const loadImage = (url) => {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.setAttribute('crossOrigin', 'anonymous');
      img.src = url;
      img.onload = () => resolve(img);
      img.onerror = () => reject(new Error('图像加载失败'));
    });
  };

  const toCanvas = async (element, width) => {
    let canvas = await html2canvas(element, {
      allowTaint: true,
      scale: state.scale || window.devicePixelRatio * 2,
      useCORS: true,
    });
    const canvasWidth = canvas.width;
    const canvasHeight = canvas.height;
    const height = (width / canvasWidth) * canvasHeight;
    const canvasData = canvas.toDataURL('image/jpeg', 1.0);
    canvas = null;
    return { width, height, data: canvasData };
  };

  const createAndDisplayCanvas = async () => {
    const imgData = await toCanvas(state.element, state.contentWidth);
    const canvasEle = document.createElement('canvas');
    canvasEle.width = imgData.width;
    canvasEle.height = imgData.height;
    canvasEle.style.position = 'fixed';
    canvasEle.style.top = '0';
    canvasEle.style.right = '0';
    state.canvasEle = canvasEle;
    document.body.appendChild(canvasEle);
    const ctx = canvasEle.getContext('2d');
    const img = await loadImage(imgData.data);
    ctx.drawImage(img, 0, 0, imgData.width, imgData.height);
    scan(ctx, imgData);
  };

  const scan = (ctx, imgData) => {
    if (!ctx || !imgData) {
      throw new Error('Invalid arguments: ctx or imgData is null/undefined');
    }

    // 从内容底部开始向上扫描,找到实际内容结束位置
    let scanPosition = imgData.height - 1; // 从底部开始扫描
    let contentEndPosition = imgData.height; // 记录内容实际结束位置

    // 向上扫描找到最后一个非空白行
    while (scanPosition >= 0) {
      const imageData = ctx.getImageData(0, scanPosition, imgData.width, 1);
      const uniqueArr = Array.from(new Set(imageData.data));

      // 如果不是空白行(即包含内容)
      if (uniqueArr.length > 1) {
        contentEndPosition = scanPosition + 1; // 找到内容实际结束位置
        break;
      }
      scanPosition--;
    }

    // 如果整个内容都是空白,则只有一页
    if (contentEndPosition === 0) {
      state.pages = [0];
      if (state.canvasEle) {
        state.canvasEle.remove();
        state.canvasEle = null;
      }
      return;
    }

    // 根据实际内容高度计算分页
    const pageHeight = parseInt(state.originalPageHeight, 10);
    let currentPageEnd = pageHeight;
    state.pages = [0]; // 重置分页数组

    while (currentPageEnd < contentEndPosition) {
      // 在当前分页点附近寻找最佳分割点(空白区域)
      let bestSplitPoint = currentPageEnd;
      const searchStart = Math.max(currentPageEnd - 50, 0); // 向前搜索50像素
      const searchEnd = Math.min(currentPageEnd + 50, contentEndPosition); // 向后搜索50像素

      // 在搜索范围内寻找空白区域作为分割点
      for (let y = searchStart; y < searchEnd; y++) {
        const imageData = ctx.getImageData(0, y, imgData.width, 1);
        const uniqueArr = Array.from(new Set(imageData.data));

        if (uniqueArr.length === 1 && y > state.pages[state.pages.length - 1] + pageHeight * 0.8) {
          // 找到空白区域,且在当前页的80%之后
          bestSplitPoint = y;
          break;
        }
      }

      state.pages.push(bestSplitPoint);
      currentPageEnd = bestSplitPoint + pageHeight;
    }

    // 检查最后一页是否内容太少,如果是则不创建该页
    const lastPageContentHeight = contentEndPosition - state.pages[state.pages.length - 1];
    if (lastPageContentHeight < pageHeight * 0.1 && state.pages.length > 1) {
      // 如果最后一页内容少于10%的页面高度,且不是第一页,则移除最后一页
      state.pages.pop();
    }

    // 清理临时canvas
    if (state.canvasEle) {
      state.canvasEle.remove();
      state.canvasEle = null;
    }
  };

  const preventSplitElements = (pageHeight) => {
    if (!state.splitClassName) return;
    const elements = state.element.querySelectorAll(`.${state.splitClassName}`);
    const containerTop = state.element.getBoundingClientRect().top;

    elements.forEach((el) => {
      const rect = el.getBoundingClientRect();
      const offsetTop = rect.top - containerTop;
      const bottom = offsetTop + rect.height;
      const startPage = Math.floor(offsetTop / pageHeight);
      const endPage = Math.floor(bottom / pageHeight);

      if (startPage !== endPage) {
        const placeholder = document.createElement('div');
        placeholder.className = 'pdf-split-placeholder';
        placeholder.style.height = startPage * pageHeight - offsetTop + 'px';
        placeholder.style.width = '100%';
        el.parentNode.insertBefore(placeholder, el);
      }
    });
  };

  const addImage = (_x, _y, pdf, data, width, height) => {
    pdf.addImage(data, 'JPEG', _x, _y, width, height);
  };

  const addBlank = (x, y, width, height, pdf) => {
    pdf.setFillColor(255, 255, 255);
    pdf.rect(x, y, Math.ceil(width), Math.ceil(height), 'F');
  };

  const addHeader = async (pageNo, header, pdf, contentWidth) => {
    if (!header || !(header instanceof HTMLElement)) {
      return;
    }
    if (!state.__header) {
      state.__header = await toCanvas(header, contentWidth);
    }
    const { height, data } = state.__header;
    const leftX = (state.A4_WIDTH - state.contentHeaderWidth) / 2;
    pdf.addImage(data, 'JPEG', leftX, 0, contentWidth, height);
  };

  const addFooter = async (pageSize, pageNo, footer, pdf, contentWidth) => {
    if (!footer || !(footer instanceof HTMLElement)) {
      return;
    }

    const pageNoDom = footer.querySelector('.pdf-footer-page');
    const pageSizeDom = footer.querySelector('.pdf-footer-page-count');
    if (pageNoDom) {
      pageNoDom.innerText = pageNo;
    }
    if (pageSizeDom) {
      pageSizeDom.innerText = pageSize;
    }

    if (pageNoDom || !state.__footer) {
      state.__footer = await toCanvas(footer, contentWidth);
    }
    const leftX = (state.A4_WIDTH - state.contentFooterWidth) / 2;
    const { height, data } = state.__footer;
    pdf.addImage(data, 'JPEG', leftX, state.A4_HEIGHT - height, contentWidth, height);
  };

  const getPdfByType = (pdf) => {
    let result = null;
    switch (state.outputType) {
      case 'file':
        result = new File([pdf.output('blob')], state.fileName, {
          type: 'application/pdf',
          lastModified: Date.now(),
        });
        break;
      case 'save':
        result = pdf.save(state.fileName);
        break;
      default:
        result = pdf.output(state.outputType);
    }
    return result;
  };

  // 主要的PDF生成逻辑
  // 滚动置顶,防止顶部空白
  window.pageYOffset = 0;
  document.documentElement.scrollTop = 0;
  document.body.scrollTop = 0;

  return new Promise(async (resolve, reject) => {
    // jsPDF实例
    const pdf = new jsPDF({
      unit: 'pt',
      format: 'a4',
      orientation: state.direction,
    });

    state.pdf = pdf;
    let pdfFooterHeight = 0;
    let pdfHeaderHeight = 0;

    // 距离PDF左边的距离,/ 2 表示居中 ,,预留空间给左边,  右边,也就是左右页边距
    const baseX = (state.A4_WIDTH - state.contentWidth) / 2;

    // 距离PDF 页眉和页脚的间距, 留白留空
    let baseY = state.baseY;
    // 元素在网页页面的宽度
    const elementWidth = state.element.scrollWidth;

    // PDF内容宽度 和 在HTML中宽度 的比, 用于将 元素在网页的高度 转化为 PDF内容内的高度, 将 元素距离网页顶部的高度  转化为 距离Canvas顶部的高度
    const rate = state.contentWidth / elementWidth;
    state.rate = rate;
    if (state.isTransformBaseY) {
      state.baseY = baseY = baseY * rate;
    }

    // 页脚元素 经过转换后在PDF页面的高度
    if (state.footer) {
      pdfFooterHeight = (await toCanvas(state.footer, state.contentFooterWidth)).height;
      state.pdfFooterHeight = pdfFooterHeight;
    }

    // 页眉元素 经过转换后在PDF的高度
    if (state.header) {
      pdfHeaderHeight = (await toCanvas(state.header, state.contentHeaderWidth)).height;
      state.pdfHeaderHeight = pdfHeaderHeight;
    }

    // 除去页头、页眉、还有内容与两者之间的间距后 每页内容的实际高度
    const originalPageHeight = state.A4_HEIGHT - pdfFooterHeight - pdfHeaderHeight - 2 * baseY;
    state.originalPageHeight = originalPageHeight;

    // 处理 splitClassName 元素,避免被分页截断
    preventSplitElements(originalPageHeight / state.rate);

    state.pages = [0]; // 要从0开始
    // 计算分页
    await createAndDisplayCanvas();
    const pages = state.pages;
    const { width, height, data } = await toCanvas(state.element, state.contentWidth);
    // 扫描函数已经处理了所有分页逻辑,这里不再需要额外的分页判断

    // 根据分页位置 开始分页生成pdf
    for (let i = 0; i < pages.length; ++i) {
      // 页眉高度
      const pdfHeaderH = pdfHeaderHeight;
      // 页脚高度
      const pdfFooterH = pdfFooterHeight;
      // 根据分页位置新增图片,要排除页眉和顶部留白
      addImage(baseX, baseY + pdfHeaderH - pages[i], pdf, data, width, height);

      // 将 内容 与 页眉之间留空留白的部分进行遮白处理
      addBlank(0, pdfHeaderH, state.A4_WIDTH, baseY, pdf);
      // 将 内容 与 页脚之间留空留白的部分进行遮白处理
      addBlank(0, state.A4_HEIGHT - baseY - pdfFooterH, state.A4_WIDTH, baseY, pdf);
      // 对于除最后一页外,对 内容 的多余部分进行遮白处理
      if (i < pages.length - 1) {
        // 获取当前页面需要的内容部分高度
        const imageHeight = pages[i + 1] - pages[i];
        // 对多余的内容部分进行遮白,但只在当前页面内容不足一页时进行遮白
        if (imageHeight < originalPageHeight) {
          addBlank(
            0,
            baseY + imageHeight + pdfHeaderH,
            state.A4_WIDTH,
            state.A4_HEIGHT - imageHeight,
            pdf,
          );
        }
      }
      // 添加页眉
      await addHeader(i + 1, state.header, pdf, state.contentHeaderWidth);
      // 添加页脚
      await addFooter(pages.length, i + 1, state.footer, pdf, state.contentFooterWidth);

      // 若不是最后一页,则分页
      if (i !== pages.length - 1) {
        // 增加分页
        pdf.addPage();
      }
    }
    try {
      const result = await getPdfByType(pdf);

      // 清理插入的占位符
      const placeholders = state.element.querySelectorAll('.pdf-split-placeholder');
      placeholders.forEach((el) => el.remove());

      resolve({
        pdfResult: result,
      });
    } catch (error) {
      reject('生成pdf出错', error);
    }
  });
};
AI助手