当前位置:

首页 > 编程开发 > 基于JavaScript实现在线PDF压缩工具

基于JavaScript实现在线PDF压缩工具

基于JavaScript实现PDF压缩工具,核心流程为选文件、定参数、读PDF、筛选JPEG图片流、通过Canvas重编码后替换,最后生成新PDF下载。只处理JPEG资源,避免格式分支,压缩后图片变大则保留原数据,支持元数据清理以进一步减小体积。

# 这篇只讲本项目里“PDF压缩”工具的功能层 Ja vaScript 实现 整个链路,说穿了就是一条线:**选文件 → 定参数 → 读PDF → 找JPEG图片流 → 重编码 → 生成新PDF → 下载**。 核心逻辑其实不复杂,但有个关键点必须说清楚:我们不是要“重写整个PDF”,而是优先处理PDF内部已经存在的JPEG图片资源。把它们重新压缩一遍,再把结果打包成一个新的PDF文件。这个思路的好处很明显——大部分PDF的体积大头都在图片上,动图片就是动根本。 基于Ja vaScript实现在线PDF压缩工具 ## 1. 参数先归一化,避免压缩流程分支失控 工具支持两种模式:`jpeg-only` 和 `condense-lite`。前者是真正动手处理图片,后者更像是个“轻量整理员”——清理清理元数据、做点简单的结构化调整。 但无论选哪个,进入主流程之前,都得先把算法、画质、缩放比例这些参数规整清楚。为什么?因为后面的压缩代码只认规范参数,乱入非法输入就会到处出错。所以第一步要做的事情很明确: ```js function resolveAlgorithm(algorithm) { if (algorithm === PDF_COMPRESS_ALGORITHM_CONDENSE_LITE) { return PDF_COMPRESS_ALGORITHM_CONDENSE_LITE; } return PDF_COMPRESS_ALGORITHM_JPEG_ONLY; } function resolveQuality(level) { return QUALITY_MAP[level] || QUALITY_MAP.medium; } function resolveScale(scale) { var value = Number(scale); if (!Number.isFinite(value) || value <= 0 || value > 1) { return 1; } return value; } ``` 把这些参数统一、标准化后,后续的压缩流程就能安心接收输入,不用再到处判断“这个值对不对”。整个代码逻辑会干净很多。 ## 2. 先从PDF里筛出真正可压缩的JPEG图片流 PDF文件里塞满了各种资源:字体、矢量图、图片、注释……但不是每个资源都适合压缩。得先把那些“能动手”的对象挑出来。 实现里会遍历PDF的间接对象,只保留同时满足这几个条件的: - 类型是 `XObject` - 子类型是 `Image` - 过滤器里包含 `DCTDecode` 这三条组合在一起,基本就是在说:这是一张JPEG图片。只有它,才值得我们去重新编码。 ```js function listJpegStreams(pdfDoc) { var imageStreams = []; var entries = pdfDoc.context.enumerateIndirectObjects(); for (var i = 0; i < entries.length; i += 1) { var obj = entries[i][1]; if (!(obj instanceof PDFRawStream)) continue; var dict = obj.dict; var type = dict.get(PDFName.of("Type")); var subtype = dict.get(PDFName.of("Subtype")); var filter = dict.get(PDFName.of("Filter")); var filterText = filter ? String(filter.toString()) : ""; if (String(type || "") !== "/XObject") continue; if (String(subtype || "") !== "/Image") continue; if (filterText.indexOf("DCTDecode") === -1) continue; imageStreams.push({ obj: obj }); } return imageStreams; } ``` 这里的关键点是只处理JPEG流。这样可以避开大量复杂的格式分支,压缩行为也更可控,不会出现“明明压缩了,文件反而变大”这种尴尬情况。 ## 3. 图片重压缩的核心是“转成位图再重新编码” 拿到JPEG字节之后,不是直接去改二进制编码。这条路走不通,因为JPEG的解码过程太复杂了,直接操作码流容易出错,而且很难在浏览器环境下做到全场景兼容。 更聪明的做法是走浏览器标准图像链路:`Blob → ObjectURL → Image → Canvas → toBlob`。说人话就是——先把原始字节包装成Blob,转成ObjectURL,让Image对象去加载,然后画到Canvas上,最后用toBlob重新编码成JPEG。 ```js function recompressJpegBytes(bytes, scale, quality) { return new Promise(function (resolve) { var blob = new Blob([bytes], { type: "image/jpeg" }); var objectUrl = URL.createObjectURL(blob); var image = new Image(); image.onload = function () { var width = Math.max(1, Math.floor(image.width * scale)); var height = Math.max(1, Math.floor(image.height * scale)); var canvas = document.createElement("canvas"); var context = canvas.getContext("2d"); canvas.width = width; canvas.height = height; context.fillStyle = "#FFFFFF"; context.fillRect(0, 0, width, height); context.drawImage(image, 0, 0, width, height); canvas.toBlob(function (nextBlob) { URL.revokeObjectURL(objectUrl); if (!nextBlob) return resolve(null); nextBlob.arrayBuffer().then(function (buffer) { resolve({ buffer: new Uint8Array(buffer), width: width, height: height, }); }); }, "image/jpeg", quality); }; image.src = objectUrl; }); } ``` 这一步直接决定了压缩效果: - `quality` 控制JPEG输出质量,值越低压缩率越高,但画质也会变差 - `scale` 控制像素尺寸缩放,缩小分辨率能直接减少文件体积 - `fillRect + drawImage` 保证重新绘制后的图像能稳定导出,不会出现奇怪的渲染残留 ## 4. 主流程按“读文件、扫图片、替换流、保存结果”推进 真正的入口函数是 `compressPdfFile`。它会先读取文件,再用 `pdf-lib` 加载PDF,然后根据模式决定是否遍历JPEG图片流。 ```js var originalBytes = await file.arrayBuffer(); var pdfDoc = await PDFDocument.load(originalBytes, { updateMetadata: false, }); var jpegStreams = listJpegStreams(pdfDoc); for (var i = 0; i < jpegStreams.length; i += 1) { var stream = jpegStreams[i].obj; var streamBytes = stream.contents; var converted = await recompressJpegBytes(streamBytes, scale, quality); if ( converted && converted.buffer && converted.buffer.byteLength < streamBytes.byteLength ) { stream.contents = converted.buffer; if (scale !== 1) { stream.dict.set(PDFName.of("Width"), pdfDoc.context.obj(converted.width)); stream.dict.set(PDFName.of("Height"), pdfDoc.context.obj(converted.height)); } } } ``` 这里有个很重要的判断:只有新图片字节更小,才会替换原始流。这样就不会因为重复编码反而把文件变大。如果压缩后的图片反而变大,就保留原始数据。 ## 5. 元数据清理是另一条独立压缩支线 除了图片重编码,工具还支持移除元数据。这部分独立于图片处理,是另一条好用的压缩路径。 实现上会删除文档信息字典和目录上的 `Metadata`: ```js function stripPdfMetadata(pdfDoc) { var context = pdfDoc.context; var catalog = pdfDoc.catalog; var infoRef = context.trailerInfo.Info; if (infoRef) { context.delete(infoRef); context.trailerInfo.Info = undefined; } if (catalog && catalog.has(PDFName.of("Metadata"))) { catalog.delete(PDFName.of("Metadata")); } } ``` 这条路径不依赖图片内容,所以有时候PDF里连一张可处理的JPEG都没有,但依然可能通过清理元数据,让文件体积变小5%到10%。 ## 6. 进度、取消和结果信息都在同一条状态链上 页面层不是简单地调用一次压缩函数就完事了。压缩过程被拆成了几个可感知的阶段:读取、解析、扫描图片、重编码、清理元数据、保存完成。 每个阶段都会触发更新,用户看到了进度条的变化,知道当前处于哪个阶段,心里有数。 ```js var result = await compressPdfFile( this.selectedFile, { algorithm: this.algorithm, quality: this.qualityLevel, scale: this.scaleValue, removeMetadata: this.removeMetadata, cancelToken: this.cancelToken, }, function (progress) { self.progressPercent = Math.max(0, Math.min(100, progress.percent || 0)); self.progressStage = String(progress.stage || ""); self.progressTotal = Number(progress.total) || 0; self.progressProcessed = Number(progress.processed) || 0; self.progressReplacedCount = Number(progress.replacedCount) || 0; }, ); ``` 取消机制也设计得很干脆——本质上是一个共享的布尔标记。每个关键节点检查一次,用户一点取消,任务就会在下一次异步边界停下。 ```js export function createPdfCompressCancelToken() { return { cancelled: false }; } function throwIfCancelled(cancelToken) { if (cancelToken && cancelToken.cancelled) { throw createPdfCompressCancelledError(); } } ``` ## 7. 导出结果时顺手补齐“用户可理解”的信息 压缩完成之后,工具不会只丢给你一个Blob。它会列出压缩前后的体积对比、替换了多少张图片、压缩比例,以及处理时用了什么算法。页面拿到这些信息,直接生成下载链接就行了。 ```js var result = { outputBlob: outputBlob, outputName: buildCompressedPdfName(file && file.name), jpegCount: jpegCount, replacedCount: replacedCount, metadataRemoved: metadataRemoved, originalSize: Number(file && file.size) || 0, outputSize: outputBlob.size, ratio: getPdfCompressionRatio(file && file.size, outputBlob.size), algorithm: algorithm, }; ``` 这套PDF压缩功能的核心其实不复杂,核心是把浏览器图像能力和 `pdf-lib` 的对象操作串起来:先定位PDF内可处理的JPEG资源,再重编码、替换、保存,最后把整个过程包装成普通用户可直接使用的工具。代码量不大,但路径清晰,效果也足够稳定。
本文内容来源于互联网,如有侵权请联系删除。
作者最新文章
编程开发
相关文章 更多
C++动态数组初始化怎么写?常用语句与代码示例
C++动态数组初始化怎么写?常用语句与代码示例

深入解析C++中动态数组的初始化机制,涵盖new操作符的不同用法、基本类型与类对象的初始化差异,以及为何在现代C++开发中应优先使用std::vector。

JS金额计算怎么避免四舍五入误差
JS金额计算怎么避免四舍五入误差

解决JavaScript中0.1+0.2!=0.3的精度问题。深入解析IEEE 754浮点数机制,对比展示用toFixed与结算用整数分计算的适用场景,提供decimal.js库的正确用法与验证方法,帮助开发者在电商与金融场景中实现精确的金额处理。

前端:JavaScript中的二叉树算法实现
前端:JavaScript中的二叉树算法实现

圣诞快乐接下来,咱们一起深入探讨一下js数据结构中的树。其实呀,这里的树和现实生活中的树很相似,都有树干、树枝。而在程序里,树可是一种非常重要的数据结构哦,对于存储那些需要快速查找的数据来说,它简直太有用啦!它是一种分层数据的抽象模型。一个树结构包含了一系列具有父子关系的节点。每个节点都有一个父节点

谷歌浏览器Mac版入口
谷歌浏览器Mac版入口

谷歌浏览器Mac版官方安装指南 谷歌浏览器Mac版官方安装入口是https://www.google.com/chrome/,需macOS 12+系统、500MB空间,下载.dmg后拖入应用程序安装,支持多设备同步、性能优化与隐私保护功能。 苹果电脑Chrome的安装入口究竟在哪里?这个问题最近可是

Chrome浏览器JS脚本不运行怎么办
Chrome浏览器JS脚本不运行怎么办

Chrome中JavaScript未执行需依次检查:一、移除站点级禁用并添加允许域名;二、开启全局JavaScript开关;三、禁用干扰扩展;四、在开发者工具中启用JavaScript;五、重置内容设置为默认。 有时在Chrome里打开网页,会发现交互按钮点了没反应,数据加载不出来,页面仿佛“静止”

IE浏览器怀旧版在线网址
IE浏览器怀旧版在线网址

IE浏览器怀旧版在线网址:一次精准的技术时光回溯 最近,不少老用户和怀旧爱好者在反复搜索一个问题:那个经典的Internet Explorer,如今还能在哪里原汁原味地体验到?答案指向一个特定的地址:https://ie.microsoft.com/legacy/。 这个网站远不止是一个简单的“皮肤

火狐浏览器有哪些设置功能
火狐浏览器有哪些设置功能

火狐浏览器五大核心设置功能:解锁高效、安全与个性化体验 火狐浏览器功能强大,但如果不仔细挖掘,很多能大幅提升效率和安全性的设置可能就“藏着掖着”了。这就好比拥有一台高性能设备,却只用了基础模式。那么,如何把它调整到最顺手、最安全的状态?接下来,我们就聚焦于当前版本(截至2025年末)最关键的五大设置

chrome搜索免验证入口
chrome搜索免验证入口

Chrome官方免验证入口为https://www.google.cn/chrome/,提供全平台安装包、免登录即用、本地化安全机制及引擎级性能优化。 到底该去哪里找正版、免费且无需繁琐验证的Chrome浏览器入口?这个问题困扰了不少网友。今天,我们就来直通核心,为大家详细拆解Chrome引擎的官方

using namespace 使用中遇到的问题怎么解决
using namespace 使用中遇到的问题怎么解决

命名空间的基本概念与常见引入问题在C++等编程语言中,命名空间(namespace)是一种将代码标识符(如变量、函数、类名)封装在特定名称下的机制,其主要目的是避免命名冲突,尤其是在大型项目或使用多个第三方库时。使用“using namespace”指令可以将指定命名空间中的所有名称引入当前作用域,

c语言函数递归 实操经验总结:这些技巧很实用
c语言函数递归 实操经验总结:这些技巧很实用

理解递归的基本原理在C语言中,递归是一种函数调用自身的编程技术。要掌握它,首先需要理解其核心思想:将一个复杂的大问题,分解为一个或几个与原问题相似但规模更小的子问题,直到子问题足够简单,可以直接求解。这个过程通常包含两个关键部分:递归出口和递归体。递归出口定义了问题何时不再继续分解,即最简单、可直接

查看更多
精品专题 更多
装机必备
装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows
Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

macOS软件
macOS软件

正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

Mac软件 更多
灵活计算器
灵活计算器
macOS/iOS/Android

灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

赤友清理大师
赤友清理大师
macOS

赤友清理大师是一款为 Mac 设计的智能清理优化工具,可精准扫描垃圾、大文件、重复文件等,释放磁盘空间。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

极度公式
极度公式
Windows/macOS/Linux

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

WINDOWS 更多
Windows 10
Windows 10
Windows

Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

极度公式
极度公式
Windows/macOS/Linux

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

密码键盘
密码键盘
Windows/macOS/iOS/Android

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。