一个基于 canvas 的 pdf 图片分页切割方法

主要应用场景是导出 pdf 的时候图片过长的情况下没法在一页内完整导出,此时需要对图片进行分页切割,这里实现的就是一个基于 canvas 的图片分页切割方法

代码实现

ts 复制代码
/**
 * 获取 base64 图片的尺寸。
 *
 * @param {string} base64 - 图片的Base64编码字符串。
 * @returns {Object} 包含图片宽度和高度的对象,格式为{width: number, height: number}。
 */
export function getPngDimensions(base64: string) {
    // 从base64字符串中提取一部分数据并解码
    const header = atob(base64.slice(22, 70)).slice(16, 24);
    // 将解码后的字符串转换为Uint8Array
    const uint8 = Uint8Array.from(header, c => c.charCodeAt(0));
    // 使用DataView来处理Uint8Array的数据
    const dataView = new DataView(uint8.buffer);

    return {
        // 从DataView中获取图片宽度
        width: dataView.getInt32(0),
        // 从DataView中获取图片高度
        height: dataView.getInt32(4),
    };
}


/**
 * 根据给定的页面尺寸和边距,对PDF中的图片进行分割。
 *
 * @param {Object} imageContent - 图片Base64编码字符串。
 * @param {Object} pageSize - 页面尺寸对象,格式为{width: number, height: number},单位是毫米。
 * @param {number[]} pageMargins - 页面边距数组,包含两个值,表示水平(左右)和垂直(上下)方向的间距,单位是毫米。
 * @returns {Promise<Object[]>} 返回分割后的图片对象,包含每一页分割后的图片大小
 */
export function splitPdfImage(
    imageContent: string, 
    pageSize: { width: number; height: number }, 
    pageMargins: number[]
   ) {
    // 获取图片的宽度和高度
    const { width, height } = getPngDimensions(imageContent);
    // 创建一个div元素,用于模拟页面
    const pageDom = document.createElement('div');
    // 设置div元素的宽度,考虑页面边距
    pageDom.style.width = pageSize.width - pageMargins[0] * 4 +'mm';
    // 设置div元素的高度,考虑页面边距
    pageDom.style.height = (pageSize.height - pageMargins[1] * 2.5) +'mm';
    // 设置div元素的定位为绝对定位
    pageDom.style.position = 'absolute';
    // 设置div元素的顶部位置为0
    pageDom.style.top = '0';
    // 将div元素添加到文档主体中
    document.body.append(pageDom);
    const scale = 2;
    const num = 38;
    // 计算页面高度,基于div元素的高度和一些缩放因子
    const PAGE_HEIGHT = Math.floor(pageDom.clientHeight / num) * num * scale;
    // 计算页面宽度,基于div元素的宽度和缩放因子
    const PAGE_WIDTH = pageDom.clientWidth * scale;
    // 计算图片在当前页面宽度下的打印高度
    const printHeight = (height * PAGE_WIDTH) / width;
    return new Promise(resolve => {
        const content: any[] = [];
        // 如果图片的打印高度大于页面高度,则需要分割图片
        if (printHeight > PAGE_HEIGHT) {
            const img = new Image();
            // 当图片加载完成时执行以下操作
            img.onload = () => {
                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d')!;
                const printHeight = (img.height * PAGE_WIDTH) / img.width;

                canvas.width = PAGE_WIDTH;

                // 循环分割图片
                for (let pages = 0; printHeight > pages * PAGE_HEIGHT; pages++) {
                    // 设置当前canvas的高度,避免最后一张图片超出实际高度
                    canvas.height = Math.min(PAGE_HEIGHT, printHeight - pages * PAGE_HEIGHT);
                    // 在canvas上绘制图片的相应部分
                    ctx.drawImage(img, 0, -pages * PAGE_HEIGHT, canvas.width, printHeight);
                    // 将分割后的图片内容添加到content数组中
                    content.push({
                      image: canvas.toDataURL('image/png'),
                      width: Math.min(width, Math.floor(PAGE_WIDTH / scale)),
                      height: Math.floor(canvas.height / scale),
                    });
                }
                // 清理canvas
                ctx!.clearRect(0, 0, canvas.width, canvas.height)
                canvas.width = 1
                canvas.height = 1
                canvas.remove()
                img.src = '';
                img.remove();
                resolve(content);
            };
            img.src = imageContent;
        } else {
            // 如果图片高度不超过页面高度,直接返回原图片内容
            resolve([{ image: imageContent, width, height }]);
        }
    }).then((res) => {
        // 移除之前创建的模拟页面的div元素
        pageDom.remove();
        return res;
    });
}

使用示例

ts 复制代码
splitPdfImage(imageContent, pdfPage.pageSize, pdfPage.pageMargins).then(res => {
    console.log('---split image---', res)
})
相关推荐
老华带你飞2 小时前
学生宿舍管理|基于java + vue学生宿舍管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
一天前2 小时前
一个功能强大的 React Native 拖拽排序组件库,支持单列和多列布局,提供流畅的拖拽体验和自动滚动功能
前端
OpenTiny社区2 小时前
2025年OpenTiny年度人气贡献者评选正式开始
前端·javascript·vue.js
JosieBook2 小时前
【Vue】04 Vue技术——Vue 模板语法详解:插值与指令
前端·javascript·vue.js
汤姆Tom2 小时前
硬核指南:Volta —— 重新定义 JavaScript 工具链管理
前端·敏捷开发·命令行
贺今宵2 小时前
el-table-v2element plus+大量数据展示虚拟表格实现自定义排序,选择样式调整行高亮
javascript·vue.js·ecmascript
Goodbaibaibai2 小时前
Element自定义主题色
前端·css·css3
灰海2 小时前
为什么给<a>标签设置了download属性, 浏览器没有下载而是打开新标签!!
前端·vue·html·下载·download
1024肥宅2 小时前
面试和算法:常见面试题实现与深度解析
前端·javascript·面试