原生JS实现图片转存为PDF

最近在研究普通文本,图片如何转存为pdf文件,搜了一圈发现都是推荐用第三库,扒拉了一下比较出名的jsPDF的核心代码,算是小小的研究明白了,下面是图片转pdf的小demo代码以及效果图:

demo

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Generate PDF with Image</title>
</head>
<body>
    <button id="exportPdf">Export to PDF</button>

    <script>
        document.getElementById('exportPdf').addEventListener('click', () => {
            try {
                const imageData = createImage();
                const pdfData = createPDF(imageData);

                // 创建 Blob 并下载 PDF
                const blob = new Blob([pdfData], { type: 'application/pdf' });
                const link = document.createElement('a');
                link.href = URL.createObjectURL(blob);
                link.download = 'image.pdf';
                link.click();
            } catch (error) {
                console.error('Error generating PDF:', error);
            }
        });

        function createImage() {
            // 创建一个 Canvas 元素并绘制图像
            const canvas = document.createElement('canvas');
            const context = canvas.getContext('2d');
            canvas.width = 200;
            canvas.height = 200;

            context.fillStyle = 'green';
            context.fillRect(10, 10, 180, 180);

            // 将 canvas 内容转换为 Data URL
            return canvas.toDataURL('image/jpeg', 1.0);
        }

        function createPDF(imageData) {
            const width = 210; // A4 宽度 (mm)
            const height = 297; // A4 高度 (mm)
            const pdfWidth = width * 72 / 25.4; // 转换为点 (pt)
            const pdfHeight = height * 72 / 25.4; // 转换为点 (pt)

            const imgObj = createImageObject(imageData);

            // 计算图片在页面中的位置
            const imgX = (pdfWidth - imgObj.width) / 2;
            const imgY = (pdfHeight - imgObj.height) / 2;

            // PDF 文档结构
            const pdf = [
                '%PDF-1.4',
                '1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj',
                '2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj',
                '3 0 obj << /Type /Page /Parent 2 0 R /Resources << /XObject << /I1 4 0 R >> >> /MediaBox [0 0 ' + pdfWidth + ' ' + pdfHeight + '] /Contents 5 0 R >> endobj',
                '4 0 obj << /Type /XObject /Subtype /Image /Width ' + imgObj.width + ' /Height ' + imgObj.height + ' /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ' + imgObj.data.byteLength + ' >> stream',
                binaryString(imgObj.data),
                'endstream endobj',
                '5 0 obj << /Length 44 >> stream',
                'q ' + imgObj.width + ' 0 0 ' + imgObj.height + ' ' + imgX + ' ' + imgY + ' cm /I1 Do Q',
                'endstream endobj',
                'xref',
                '0 6',
                '0000000000 65535 f ',
                '0000000009 00000 n ',
                '0000000056 00000 n ',
                '0000000115 00000 n ',
                '0000000202 00000 n ',
                '0000000281 00000 n ',
                'trailer << /Size 6 /Root 1 0 R >>',
                'startxref',
            ];
            pdf.push(
              pdf.join('\n').length,
              '%%EOF')

            return new Uint8Array(pdf.join('\n').split('').map(c => c.charCodeAt(0)));
        }

        function createImageObject(imageData) {
            const parts = imageData.split(',')[1];
            const binary = atob(parts);
            const length = binary.length;
            const buffer = new ArrayBuffer(length);
            const view = new Uint8Array(buffer);

            for (let i = 0; i < length; i++) {
                view[i] = binary.charCodeAt(i);
            }

            return {
                data: view,
                width: 200, // 图片宽度 (px)
                height: 200 // 图片高度 (px)
            };
        }

        function binaryString(buffer) {
            let result = '';
            const len = buffer.byteLength;
            for (let i = 0; i < len; i++) {
                result += String.fromCharCode(buffer[i]);
            }
            return result;
        }
    </script>
</body>
</html>

效果图

文末附上上面用到的pdf各个字段的解释:

在 PDF 文件中,嵌入图像对象的过程涉及定义图像对象(/XObject)、颜色空间(/ColorSpace)、图像数据流等。

PDF 图像对象结构

  1. 图像对象定义: 使用 /Type /XObject 和 /Subtype /Image 指定图像对象的类型。
  2. 颜色空间: 使用 /ColorSpace /DeviceRGB 指定颜色空间。
  3. 位深度: 使用 /BitsPerComponent 8 指定每个颜色分量的位数。
  4. 图像尺寸: 使用 /Width 和 /Height 指定图像的宽度和高度。
  5. 图像数据流: 使用 /Filter 指定压缩类型(如 /DCTDecode 用于 JPEG 压缩)。

pdf文档解析

关于更多关于pdf文档对象及其结构的定义和使用可以查看:pdf文档结构解析

第三方库

当然了,现实中如果不是专门做pdf相关产品, 可以直接借用一些第三方库,比如jsPDF(只能导出pdf), PDF.js(只解析和导入pdf), pdf-lib(导入导出都有,但功能局限)来实现pdf的导入导出甚至更复杂的交互。

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值