一个基于 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)
})
相关推荐
wuhen_n38 分钟前
网络请求在Vite层的代理与Mock:告别跨域和后端依赖
前端·javascript·vue.js
用户69371750013846 小时前
Google 正在“收紧侧加载”:陌生 APK 安装或需等待 24 小时
android·前端
蓝帆傲亦6 小时前
Web 前端搜索文字高亮实现方法汇总
前端
用户69371750013846 小时前
Room 3.0:这次不是升级,是重来
android·前端·google
漫随流水7 小时前
旅游推荐系统(view.py)
前端·数据库·python·旅游
踩着两条虫8 小时前
VTJ.PRO 核心架构全公开!从设计稿到代码,揭秘AI智能体如何“听懂人话”
前端·vue.js·ai编程
jzlhll1239 小时前
kotlin Flow first() last()总结
开发语言·前端·kotlin
用头发抵命9 小时前
Vue 3 中优雅地集成 Video.js 播放器:从组件封装到功能定制
开发语言·javascript·ecmascript
蓝冰凌10 小时前
Vue 3 中 defineExpose 的行为【defineExpose暴露ref变量】详解:自动解包、响应性与实际使用
前端·javascript·vue.js
奔跑的呱呱牛10 小时前
generate-route-vue基于文件系统的 Vue Router 动态路由生成工具
前端·javascript·vue.js