pdf转图片

2,249 阅读3分钟
前置知识
  • 这个 demo 使用到了一个知识点
  • canvas 可以使用 toDataURL 来将当前画布视图生成一个快照,返回 Base64 地址
  • 查看 demo
// 生成url
const getUrl = (url, callback) => {
  // 创建画布
  const canvas = document.createElement("canvas");
  // 拿到2d上下文
  const ctx = canvas.getContext("2d");
  // 创建图片
  const img = new Image();
  // 设置跨域
  img.setAttribute("crossorigin", "anonymous");
  img.src = url;
  // 设置的时候要放在img的onload事件内
  img.onload = () => {
    canvas.width = img.width;
    canvas.height = img.height;
    // 放置图片
    ctx.drawImage(img, 0, 0);
    // 生成快照地址
    const baseUrl = canvas.toDataURL("image/jpeg", 1);
    // 测试是否可以使用
    appendImg(baseUrl);
  };
};
// 插入图片函数,可忽略
const appendImg = (url) => {
  const img = new Image();
  img.src = url;
  img.onload = () => {
    document.body.appendChild(img);
  };
};
getUrl("https://mozilla.github.io/pdf.js/images/logo.svg", appendImg);

image.png

  • 可以看到,图片正常展示,但是有一些周围是黑色的
  • 这是因为,canvas 背景色默认为透明色,当生成jpeg快照的时候,就变成黑色了
  • 解决办法,看下方 demo
    • 思路:判断是否为透明色,将其改成白色
    • 不足:看到还是有些黑色锯齿,暂时没有解决
    • 将其改为png格式即可

image.png

// 在ctx.drawImage(img, 0, 0);后canvas.toDataURL前插入这段代码
let imageData = ctx.getImageData(0, 0, img.width, img.height);
for (let i = 0; i < imageData.data.length; i += 4) {
  // 当该像素是透明的,则设置成白色
  if (imageData.data[i + 3] === 0) {
    imageData.data[i] = 255;
    imageData.data[i + 1] = 255;
    imageData.data[i + 2] = 255;
    imageData.data[i + 3] = 255;
  }
}
ctx.putImageData(imageData, 0, 0);

回归正题

  • 需求:将pdf合并成一张图片

不使用第三方插件,初步尝试

  1. 直接使用 img 标签来展示
    • safari 浏览器可以展示,但只展示 pdf 的第一页
    • 谷歌浏览器展示失败
  2. 使用原生标签展示
    • embed
    • object
    • 问题:可以展示,但是不太好看
<!-- mdn推荐使用object标签 -->
<embed src="url" type="application/pdf" width="800" height="800" />
<object data="url" type="application/pdf" width="800" height="800"></object>
使用插件
  • 官网:http://mozilla.github.io/pdf.js/
  • 先尝试一下,不用架子,在本地写个 demo 跑一下
    • 到官网下载依赖文件我下载的2.12.313版本
    • 因为pdf通过插件本身处理后再插入canvas,他不是透明背景了,所以上面的透明转白色知识暂时没用到
    • 思路:
      1. 解析每张pdf实际上在这个demo中,这个插件帮我们做的只是读取pdf并且插入到canvas
      2. 然后依次将其插入到新建的canvas中,然后生成快照,将快照地址存储到数组中
      3. 循环数组,按照顺序位置,依次向canvas中放置图片,
      4. 然后将最终的canvas生成base64地址,传递给callback
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="./pdfjs-2.12.313-dist/build/pdf.js"></script>
    <script src="./pdfjs-2.12.313-dist/build/pdf.worker.js"></script>
    <title>测试</title>
  </head>

  <body>
    <img src="" alt="" width="100%" height="auto" id="i1" />
    <canvas id="the-canvas" style="border:1px  solid black"></canvas>
  </body>
</html>
<script>
  function addPdf(url) {
    let loadingTask = pdfjsLib.getDocument(url);
    loadingTask.promise.then((pdf) => {
      //页数
      let numPages = pdf.numPages;
      let scale = 2;
      let Mycanvas = document.querySelector("#the-canvas");
      let imgList = [];
      let top = 0;
      let height = 0;
      let width = 0;
      let okRender = new Promise(async (res, rej) => {
        for (let i = 1; i <= numPages; i++) {
          let page = await pdf.getPage(i);
          let viewport = page.getViewport({
            scale: scale,
          });

          let canvas = document.createElement("canvas");
          canvas.height = viewport.height;
          canvas.width = viewport.width;
          console.log(viewport.height, 9);
          let context = canvas.getContext("2d");
          let renderContext = {
            canvasContext: context, // 此为canvas的context
            viewport: viewport,
          };
          let success = await page.render(renderContext).promise;
          if (i === 1) {
            height = viewport.height;
            width = viewport.width;
            Mycanvas.height = viewport.height * numPages;
            Mycanvas.width = viewport.width;
          }
          let dataURL = canvas.toDataURL("image/jpeg", 1);
          imgList.push(dataURL);
          console.log(i, numPages, imgList.length, imgList);
          if (i === numPages) {
            res();
          }
        }
      });
      okRender.then((res) => {
        let context = Mycanvas.getContext("2d");
        imgList.forEach((item) => {
          var image = new Image();
          image.src = item;
          image.onload = () => {
            context.drawImage(image, 0, top, width, height);
            top += height;
          };
        });
      });
    });
  }
  addPdf("url");
</script>
  • 感觉 ok 了,找个架子试一下
  • 在项目里使用
    • 虽然他名字为 pdfjs,但是对应的 npm 包为 pdfjs-dist
    • 在 ts 里面用的时候,安装声明文件要注意,pdfjs-dist@2.2.228对应@types/pdfjs-dist@2.1.7,否则有些奇奇怪怪的问题
import pdfjs from "pdfjs-dist";

const addPdf = (url: string, callback?: any) => {
  let loadingTask: any = pdfjs.getDocument(url);
  loadingTask.promise.then((pdf: any) => {
    //页数
    let numPages = pdf.numPages;
    let scale = 2;
    let Mycanvas = document.createElement("canvas");
    let imgList: any = [];
    let top = 0;
    let height = 0;
    let width = 0;
    let okRender = new Promise(async (res: any, rej) => {
      for (let i = 1; i <= numPages; i++) {
        let page = await pdf.getPage(i);
        let viewport = page.getViewport({
          scale: scale,
        });
        let canvas = document.createElement("canvas");
        canvas.height = viewport.height;
        canvas.width = viewport.width;
        let context = canvas.getContext("2d");
        let renderContext = {
          canvasContext: context, // 此为canvas的context
          viewport: viewport,
        };
        let success = await page.render(renderContext).promise;
        if (i === 1) {
          height = viewport.height;
          width = viewport.width;
          Mycanvas.height = viewport.height * numPages;
          Mycanvas.width = viewport.width;
        }
        let dataURL = canvas.toDataURL("image/jpeg", 1);
        imgList.push(dataURL);
        if (i === numPages) {
          res();
        }
      }
    });
    okRender.then(() => {
      let context = Mycanvas.getContext("2d");
      imgList.forEach((item: any, index: number) => {
        var image = document.createElement("img");
        image.src = item;
        image.onload = async () => {
          context.drawImage(image, 0, top, width, height);
          top += height;
          if (index === imgList.length - 1) {
            callback(Mycanvas.toDataURL("image/jpeg", 1));
          }
        };
      });
    });
  });
};

问题

字体缺失
  • 有时会出现缺失字体情况,如中文不展示等
    //调用形式改变,添加cMapUrl与cMapPacked
    //cMapUrl可使用cdn也可使用本地资源,如包中的/web/cmaps
    let loadingTask = pdfjsLib.getDocument({
      url:pdf地址,
      cMapUrl:'https://unpkg.com/pdfjs-dist@2.0.489/cmaps/',
      // cMapUrl:'./cmaps/',
      cMapPacked: true
    });
总结一下
  • 如果不是在page.render.promise.then里面生成快照,那就是一团黑,因为他是异步,在外边优先执行会导致生成了一个透明画板的快照
  • 放在page.render.promise.then也有点小问题,偶尔会出现第二张先渲染的情况,或偶尔黑屏
  • 所以干脆直接 async 和 await 了