网站首页 > 技术文章 正文
本文讲述在浏览器端 提取 PDF 图片 。
提取 PDF 图片,是指把每一页里的多张图片都提取出来,不是把一整页都转换为一张图片 ^_^ 。
效果
源码
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@3.10.111/build/pdf.worker.js';
async function loadPDFFile(loadingTask) {
const pdf = await loadingTask.promise;
const numPages = pdf.numPages;
for (let curPage = 1; curPage <= numPages; curPage++) {
console.log('loadingServerFile curPage: ', curPage);
const page = await pdf.getPage(curPage);
const scale = 1.5;
const viewport = page.getViewport({ scale });
// Support HiDPI-screens.
const outputScale = window.devicePixelRatio || 1;
const canvas = new OffscreenCanvas(200, 200);
const context = canvas.getContext("2d");
const transform = outputScale !== 1
? [outputScale, 0, 0, outputScale, 0, 0]
: null;
const renderContext = {
canvasContext: context,
transform,
viewport,
};
const renderTask = page.render(renderContext);
await renderTask.promise;
const ops = await page.getOperatorList();
// https://stackoverflow.com/a/39855420 Extract Images
const imageNames = ops.fnArray.reduce((acc, curr, i) => {
if ([pdfjsLib.OPS.paintImageXObject, pdfjsLib.OPS.paintJpegXObject].includes(curr)) {
acc.push(ops.argsArray[i][0]);
}
return acc;
}, []);
for (const imageName of imageNames) {
console.log('imageName: ', imageName);
page.objs.get(imageName, (image) => {
// console.log('image: ', image);
(async function() {
// https://stackoverflow.com/questions/52959839/convert-imagebitmap-to-blob/52959897#52959897
const bmp = image.bitmap;
// create a canvas
// the difference of ImageData and ImageBitmap https://stackoverflow.com/a/60033582
// const canvas = document.createElement('canvas');
// resize it to the size of our ImageBitmap
// canvas.width = bmp.width;
// canvas.height = bmp.height;
// OffscreenCanvas
const resizeScale = 1/4;
const width = bmp.width * resizeScale;
const height = bmp.height * resizeScale;
const canvas = new OffscreenCanvas(width, height);
// get a bitmaprenderer context
// bitmaprenderer 和
const ctx = canvas.getContext('bitmaprenderer');
ctx.transferFromImageBitmap(bmp);
// get it back as a Blob
const blob = await canvas.convertToBlob();
const img = document.body.appendChild(new Image());
img.width = width;
img.height = height;
img.src = URL.createObjectURL(blob);
})();
});
}
}
}
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener(
"change",
() => {
const file = fileInput.files[0];
console.log('fileInput file: ', file);
var fileReader = new FileReader();
fileReader.onload = function() {
var typedarray = new Uint8Array(this.result);
const loadingTask = pdfjsLib.getDocument(typedarray);
loadPDFFile(loadingTask);
};
fileReader.readAsArrayBuffer(file);
},
false
);
技术实现详解
使用 PDF.js 解析和渲染 PDFs,在 github 上有 43.9kb Star,非常成熟。
一、 引入 pdf.js
需要同时引入 pdf.js 和 WebWorker,其中 WebWorker 在浏览器子线程解析图片等资源,不阻塞页面UI和交互。引入代码如下:
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.10.111/pdf.js"></script>
<script>
// pdf.js 从性能考虑,使用了 WebWorker, 在浏览器子线程解析图片等资源,不阻塞页面UI和交互。
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@3.10.111/build/pdf.worker.js';
</script>
二、上传 PDF 文件并监听
上传文件时,使用 FileReader 异步文件读取对象,读取之后转换为 Uint8Array,传入 pdfjsLib,得到 PDF 加载任务。
代码如下:
<input type="file" id="fileInput" name="avatar" title ="选择 pdf 文件"/>
<script>
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener(
"change",
() => {
const file = fileInput.files[0];
console.log('fileInput file: ', file);
// 上传文件时,使用 FileReader 对象读取
var fileReader = new FileReader();
fileReader.onload = function() {
// 将文件对象转化为 Uint8Array
var typedarray = new Uint8Array(this.result);
// 返回 PDF 加载任务 PDFDocumentLoadingTask
const loadingTask = pdfjsLib.getDocument(typedarray);
// 开始加载 PDF,这里封装了一个函数
loadPDFFile(loadingTask);
};
fileReader.readAsArrayBuffer(file);
},
false
);
</script>
三、获取和遍历 PDF 页数
async function loadPDFFile(loadingTask) {
// PDF 加载任务
const pdf = await loadingTask.promise;
// 获取 PDF 页数
const numPages = pdf.numPages;
for (let curPage = 1; curPage <= numPages; curPage++) {
// 返回当前页
console.log('loadingServerFile curPage: ', curPage);
const page = await pdf.getPage(curPage);
const scale = 1.5;
// 获取渲染视角尺寸
const viewport = page.getViewport({ scale });
// Support HiDPI-screens.
const outputScale = window.devicePixelRatio || 1;
// 传入离屏 Canvas
const canvas = new OffscreenCanvas(200, 200);
// 获取 Canvas 上下文
const context = canvas.getContext("2d");
// 转换尺寸
const transform = outputScale !== 1
? [outputScale, 0, 0, outputScale, 0, 0]
: null;
const renderContext = {
canvasContext: context,
transform,
viewport,
};
// 调用 page.render 触发渲染
const renderTask = page.render(renderContext);
// 等待 renderTask 渲染任务
// await delay(500); // 如果渲染过快,可能导致 CPU 飙升、电脑可用内存不够,可以加延迟减慢速度
await renderTask.promise;
}
}
代码里使用 OffscreenCanvas,提供了一个可以脱离屏幕渲染的 canvas 对象,在主线程和子线程 (web worker) 都可以使用,是为了性能优化,提取 PDF 图片不用渲染到页面,但也要渲染任务
如果渲染过快,可能导致 CPU 飙升、电脑可用内存不够,可以加延迟 setTimeout 减慢速度
四、提取每页 PDF 的图片
在这一步,我们获取了 pdfjs 提取的 PDF 图片,是 ImageBitmap 格式,它是对 Canvas 上位图数据的引用,存储在 GPU 中,可以在 web worker 进行生成,从而避免影响主线程绘制 UI,也避免阻塞响应用户交互。
代码如下:
async function loadPDFFile(loadingTask) {
// ...接上一步
const renderTask = page.render(renderContext);
// 等待当前页渲染任务执行
await renderTask.promise;
// 获取操作列表
const ops = await page.getOperatorList();
// 提取图片
const imageNames = ops.fnArray.reduce((acc, curr, i) => {
if ([pdfjsLib.OPS.paintImageXObject, pdfjsLib.OPS.paintJpegXObject].includes(curr)) {
acc.push(ops.argsArray[i][0]);
}
return acc;
}, []);
for (const imageName of imageNames) {
console.log('imageName: ', imageName);
page.objs.get(imageName, (image) => {
// console.log('image: ', image);
(async function() {
const bmp = image.bitmap;
// create a canvas
console.log('bmp: ', bmp);
})();
});
}
}
五、ImageBitmap 转化为 Img
再坚持一下,最后一步啦!─=≡Σ(((つ??ω??)つ
在上一步中,我们获取了在 Canvas 绘图的引用 ImageBitmap,我们需要转换为浏览器下的 Img 图片。
具体过程:先将 ImageBitmap 渲染到 Canvas,调用 canvas.convertToBlob 就能得到 Blob 对象。
async function loadPDFFile(loadingTask) {
// ...接上一步,拿到了每页 PDF 的 image.bitmap 对象
for (const imageName of imageNames) {
console.log('imageName: ', imageName);
page.objs.get(imageName, (image) => {
// console.log('image: ', image);
(async function() {
const bmp = image.bitmap;
console.log('bmp: ', bmp);
// OffscreenCanvas
const resizeScale = 1/4; // 这个可以控制转换后的图片大小
const width = bmp.width * resizeScale;
const height = bmp.height * resizeScale;
const canvas = new OffscreenCanvas(width, height);
// 获取 canvas bitmaprenderer 上下文
const ctx = canvas.getContext('bitmaprenderer');
// 把 ImageBitmap 渲染到 OffscreenCanvas
ctx.transferFromImageBitmap(bmp);
// 把 canvas 画布转化为 Blob 对象
// https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas/convertToBlob
const blob = await canvas.convertToBlob();
console.log('blob: ', blob); // blob
// 最后使用 Blob 作为 URL.createObjectURL 的参数,渲染出 img 图片
// 如果不需要渲染,则可以讲 Blob 数据上传到云存储
const img = document.body.appendChild(new Image());
img.width = width;
img.height = height;
img.src = URL.createObjectURL(blob);
})();
});
}
}
OffscreenCanvas 可以放在 Web Worker 中创建,从而不阻塞主线程的UI绘制和交互响应。
bitmaprenderer.transferFromImageBitmap 可以实现不拷贝数据,只传递地址的方式,把 ImageBitmap 传递到 Canvas 上。
背景(缘起)
前段时间遇到 从 PDF 中提取图片用于 评论、审核 的功能。
由于项目时间赶、任务重,由后端实现了一页 PDF 转换为一张图片。
后端提取优劣:
- 优: 用户体验好,不影响用户电脑性能
- 劣: 加大服务器压力 占用大量CPU资源,在我Mac笔记本MacBook Pro (13-inch, 2017, Four Thunderbolt 3 Ports) 解析时,能看到 CPU 每次都飙到 100% 以上 占用大量内存,解析后的图片,在上传到 CDN 以前,放在服务器没有释放 提取流程变长 需要把 PDF 文件上传到服务器,由后端处理
前端提取优劣:
- 优: 不占用服务器资源,在浏览器端就能完成提取。
- 劣: 可能会引起用户电脑卡顿。
网上关于在浏览器端JavaScript提取PDF图片的资料很少,没有完整Demo,只有直接把 PDF 整页转成图片。
因对个人学习和工作都有帮助,花了几个周末收集资料、阅读 pdf.js 文档、源码,调试代码,才有了本文。
客官们觉得不错,帮忙点个赞哈哈!
原文链接:https://juejin.cn/post/7284433532075524151
猜你喜欢
- 2024-10-06 VUE前端编程:PDF插件填坑记 vue-to-pdf
- 2024-10-06 把HTML转成PDF的4个方案及实现方法
- 2024-10-06 使用reveal.js制作精美的网页版PPT
- 2024-10-06 硬核!《web前端开发规范手册》,高清版 PDF 开放下载,拿走不谢
- 2024-10-06 让 PDF文档看起来像扫描的一样 pdf变成扫描版
- 2024-10-06 阿里架构师花近十年时间整理出来的前端核心知识pdf(前端岗)
- 2024-10-06 一个解决支持HTML/CSS/JS网页转PDF(高质量)的终极解决方案
- 2024-10-06 基于springboot的多格式转PDF springboot word转pdf
- 2024-10-06 Java后端实现HTML网页报表导出pdf方案
- 2024-10-06 原来Word转PDF是那么简单,1分钟即可搞定!你现在还不会吗?
你 发表评论:
欢迎- 501℃几个Oracle空值处理函数 oracle处理null值的函数
- 495℃Oracle分析函数之Lag和Lead()使用
- 495℃Oracle数据库的单、多行函数 oracle执行多个sql语句
- 482℃0497-如何将Kerberos的CDH6.1从Oracle JDK 1.8迁移至OpenJDK 1.8
- 478℃Oracle 12c PDB迁移(一) oracle迁移到oceanbase
- 468℃【数据统计分析】详解Oracle分组函数之CUBE
- 454℃Oracle有哪些常见的函数? oracle中常用的函数
- 450℃最佳实践 | 提效 47 倍,制造业生产 Oracle 迁移替换
- 最近发表
-
- Directus 火了!无代码 SQL 数据的协作应用利器!
- PHP和NodeJS的代码执行效率比较(php和nodejs的区别)
- 工商银行获得发明专利授权:“基于数据库映射动态接口的前端页面应用开发方法及装置”
- FAISS和Chroma:两种流行的向量数据库的比较
- 什么是数据库的索引?(数据库索引的定义和作用)
- Vercel和Neon“首次”推出用于前端云的无服务器SQL数据库
- 记一次前端逻辑绕过登录到内网挖掘
- 学Access好还是MySQL好?(access与mysql的语句区别)
- 惬意!清晨慢品 HTML canvas 标签题,面试知识轻松 get
- 前端实现知识图谱-force(d3.js)(前端知识树)
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端缓存 (63)
- 前端react (48)
- 前端aes加密 (58)
- 前端脚手架 (56)
- 前端md5加密 (54)
- 前端富文本编辑器 (47)
- 前端路由 (55)
- 前端数组 (65)
- 前端定时器 (47)
- Oracle RAC (73)
- oracle恢复 (76)
- oracle 删除表 (48)
- oracle 用户名 (74)
- oracle 工具 (55)
- oracle 内存 (50)
- oracle 导出表 (57)
- oracle 中文 (51)
- oracle链接 (47)
- oracle的函数 (57)
- 前端调试 (52)
- 前端登录页面 (48)
本文暂时没有评论,来添加一个吧(●'◡'●)