百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

?? JavaScript提取PDF图片 ??(js读取pdf文件内容)

suiw9 2024-11-17 01:42 20 浏览 0 评论

本文讲述在浏览器端 提取 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

相关推荐

俄罗斯的 HTTPS 也要被废了?(俄罗斯网站关闭)

发布该推文的ScottHelme是一名黑客,SecurityHeaders和ReportUri的创始人、Pluralsight作者、BBC常驻黑客。他表示,CAs现在似乎正在停止为俄罗斯域名颁发...

如何强制所有流量使用 HTTPS一网上用户

如何强制所有流量使用HTTPS一网上用户使用.htaccess强制流量到https的最常见方法可能是使用.htaccess重定向请求。.htaccess是一个简单的文本文件,简称为“.h...

https和http的区别(https和http有何区别)

“HTTPS和HTTP都是数据传输的应用层协议,区别在于HTTPS比HTTP安全”。区别在哪里,我们接着往下看:...

快码住!带你十分钟搞懂HTTP与HTTPS协议及请求的区别

什么是协议?网络协议是计算机之间为了实现网络通信从而达成的一种“约定”或“规则”,正是因为这个“规则”的存在,不同厂商的生产设备、及不同操作系统组成的计算机之间,才可以实现通信。简单来说,计算机与网络...

简述HTTPS工作原理(简述https原理,以及与http的区别)

https是在http协议的基础上加了一层SSL(由网景公司开发),加密由ssl实现,它的目的是为用户提供对网站服务器的身份认证(需要CA),以至于保护交换数据的隐私和完整性,原理如图示。1、客户端发...

21、HTTPS 有几次握手和挥手?HTTPS 的原理什么是(高薪 常问)

HTTPS是3次握手和4次挥手,和HTTP是一样的。HTTPS的原理...

一次安全可靠的通信——HTTPS原理

为什么HTTPS协议就比HTTP安全呢?一次安全可靠的通信应该包含什么东西呢,这篇文章我会尝试讲清楚这些细节。Alice与Bob的通信...

为什么有的网站没有使用https(为什么有的网站点不开)

有的网站没有使用HTTPS的原因可能涉及多个方面,以下是.com、.top域名的一些见解:服务器性能限制:HTTPS使用公钥加密和私钥解密技术,这要求服务器具备足够的计算能力来处理加解密操作。如果服务...

HTTPS是什么?加密原理和证书。SSL/TLS握手过程

秘钥的产生过程非对称加密...

图解HTTPS「转」(图解http 完整版 彩色版 pdf)

我们都知道HTTPS能够加密信息,以免敏感信息被第三方获取。所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用HTTPS协议。...

HTTP 和 HTTPS 有何不同?一文带你全面了解

随着互联网时代的高速发展,Web服务器和客户端之间的安全通信需求也越来越高。HTTP和HTTPS是两种广泛使用的Web通信协议。本文将介绍HTTP和HTTPS的区别,并探讨为什么HTTPS已成为We...

HTTP与HTTPS的区别,详细介绍(http与https有什么区别)

HTTP与HTTPS介绍超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的...

一文让你轻松掌握 HTTPS(https详解)

一文让你轻松掌握HTTPS原文作者:UC国际研发泽原写在最前:欢迎你来到“UC国际技术”公众号,我们将为大家提供与客户端、服务端、算法、测试、数据、前端等相关的高质量技术文章,不限于原创与翻译。...

如何在Spring Boot应用程序上启用HTTPS?

HTTPS是HTTP的安全版本,旨在提供传输层安全性(TLS)[安全套接字层(SSL)的后继产品],这是地址栏中的挂锁图标,用于在Web服务器和浏览器之间建立加密连接。HTTPS加密每个数据包以安全方...

一文彻底搞明白Http以及Https(http0)

早期以信息发布为主的Web1.0时代,HTTP已可以满足绝大部分需要。证书费用、服务器的计算资源都比较昂贵,作为HTTP安全扩展的HTTPS,通常只应用在登录、交易等少数环境中。但随着越来越多的重要...

取消回复欢迎 发表评论: