preface

The company project needs to realize the requirement of preview PDF files in the embedded H5 project of APP

thinking

At the moment when I got this requirement in hand, I was confused. Because this is a field THAT I have never been involved in before, I had some thoughts: 1. Previewing PDF should have plug-ins, search for a wave of plug-ins that can be used in VUE: vuE-PDF, PDFJS 2. Just to clarify the requirements: There may be multiple PDFS, each of which may be more than one page, so at least at the first level it must be circular

practice

At first, I was going to use VUe-PDF, which I don’t even know how to use. However, with the convenient tool of search, this problem is not a problem. As a result, began a wave of crazy search behavior, however, the ideal is full, the reality is very skinny, search methods, I have not a successful realization of the demand. Since this doesn’t work, try the original PDFJS (vue-PDF seems to be based on this). Follow the tutorial method by downloading a bunch of files, unzip them, and place them in your project. A fierce operation such as tiger, a look at the input only 5, cry. Finally, I found another way to introduce PDFJS, namely PDFJS-dist. I don’t know what the difference is between PDFJS and PDFJS. It seems to be the same thing. After looking at this, it seemed easier to operate without having to download the source code and put it into the project, so I started again

operation

Step 1: Introduction

npm i pdfjs-dist
Copy the code

In fact there is also a hole, I began to download the latest version of, but the honey bug, see elder bosses behind the article, the second step: find a specified version 2.2.228 use saw many articles, are combined with canvas to use, so I will not open up a new method of using (I won’t ah, want to also not move)

/ / HTML
 <template v-if="pdfList.length > 0">
      <div v-for="(pdfPages, index) in pdfList" :key="index" class>
        <canvas
          v-for="page in pdfPages"
          :key="page"
          :id="'pdfCanvas' + page"
        ></canvas>
      </div>
    </template>
Copy the code
// 1. Introduce PDFJS
import PDFJS from "pdfjs-dist";

created() {
    PDFJS.GlobalWorkerOptions.workerSrc = require("pdfjs-dist/build/pdf.worker.min.js");
    // Don't ask me why I wrote it here, I'm not very clear, I also read the article under import at first, but I tried it didn't work, so I tried to write it here, the result was OK
}
Copy the code

At the beginning, I did not encapsulate the canvas part, but wrote it all in one component, and there were no other problems at this time except the huge amount of component code

// Request interface to obtain PDF file stream (data is third-party, and it is also a plaintext link, data sensitive, considering security, we do not save the server, the backend is just forwarding)
async getFileUrl() {
      try {
        const res = await this.$http.get(url);
        console.log(res, "getFileUrl");
        if (xxx.code === 200) {
          let urlList = xxx.data;
          urlList.forEach(item= > {
            this.handlerUrl(item);
          });
          return; }}catch (error) {
        console.log(error, "getFileUrl"); }},// In addition to PDF data, there is also OFD data, ignore OFD for now
    handlerUrl(item) {
      if (item.fileFormat === "PDF") {
        this.getPdfFile(item.licenseId);
      } else if (item.fileFormat === "OFD") {
        this.getOfdFile(item.licenseId); }},Copy the code

The next step is to render the PDF

async getPdfFile(licenseId) {
      const url = `xxx`;
      try {
        const res = await this.$http({
          url,
          method: "get".params: {
            licenseId
          },
          responseType: "blob"
        });
        // Change the file stream to a URL
        if (res.status == 200) {
          const content = res.data;
          let file = new Blob([content], {
            type: "application/pdf; charset-UTF-8"
          });
          const objectURL = URL.createObjectURL(file);
          this.pdfList.push(objectURL);
          this._loadFile(objectURL);
          return; }}catch (error) {
        console.log(error, "getPdfFile"); }},_loadFile(url) {
      PDFJS.getDocument(url).promise.then(pdf= > {
        this.pdfDoc = pdf;
        const pdfPages = this.pdfDoc.numPages;
        this.pdfList = pdfPages;
        this.$nextTick(() = > {
          this._renderPage(1);
        });
      });
    },
    _renderPage(num) {
      this.pdfDoc.getPage(num).then(page= > {
        let canvas = document.getElementById("pdfCanvas" + num + this.index);
        var vp = page.getViewport({ scale: 1 });
        let ctx = canvas.getContext("2d");
        let dpr = window.devicePixelRatio || 1;
        let bsr =
          ctx.webkitBackingStorePixelRatio ||
          ctx.mozBackingStorePixelRatio ||
          ctx.msBackingStorePixelRatio ||
          ctx.oBackingStorePixelRatio ||
          ctx.backingStorePixelRatio ||
          1;
        let ratio = dpr / bsr;
        let viewport = page.getViewport({
          scale: window.innerWidth / vp.width
        });
        canvas.width = viewport.width * ratio;
        canvas.height = viewport.height * ratio;

        canvas.style.width = viewport.width + "px";

        canvas.style.height = viewport.height + "px";

        ctx.setTransform(ratio, 0.0, ratio, 0.0);
        let renderContext = {
          canvasContext: ctx,
          viewport: viewport
        };
        console.log(canvas, "-- -- -- -- -- -");
        if (canvas) {
          page.render(renderContext);
        }
        if (this.pdfList > num) {
          this._renderPage(num + 1); }}); }Copy the code

Boy, I thought this was OK, because at that time the test data returned only one, render out nothing wrong, very nice. However, it is possible to have multiple arrays, so it is too early to rejoice. When there are two data points, the problem is, what is the console error

How can this, this, this, this be wrong? Then the page rendering PDF is also confused, directly rotated 180 degrees, and the display is not complete, σ (flying ° д °; っ

He is not determined to baidu first, found, did no one have encountered this problem? Or do I have a weird need? No, I tried again, but I failed. I couldn’t find it. / (ㄒ o ㄒ) / ~ ~

I have no choice. Do it yourself. What does this mean?

Error: Cannot use the same canvas during multiple Render () operations. Use a different canvas or make sure the previous action has been cancelled or completed.Copy the code

Do not use the same canvas during operation. Didn’t I loop? How can it be the same canvas? Is it a height problem? So I set a fixed height, failed. Is it my problem with loop nesting? So I encapsulated the canvas and failed

<div v-if="pdfList.length > 0">
      <div
        v-for="(pdfPages, index) in pdfList"
        :key="index"
        class="pdf-preview"
      >
        <CanvasPreviewPdf :pdfData="pdfPages" :index="index" />
      </div>
    </div>
Copy the code

Wait, I seem to be missing something important, multiple Render operations, same canvas, why is my canvas the same? That’s a good question. So I carefully compared the rendered canvas tag, boy, set the ID, rendering is the same, that is, in the parent component loop, inside the canvas, each group id is the same, as shown in the picture below (this is after I modified, before modification, PdfCanvas10 and pdfCanvas11 are both called pdfCanvas1, which means they are duplicated), so I concatenated the index loop in the parent component

<template>
  <div>
    <div v-for="page in pdfList" :key="page">
      <canvas :id="'pdfCanvas' + page + index"></canvas>
    </div>
  </div>
</template>

<script>
import PDFJS from "pdfjs-dist";
export default {
  name: "PreviewPdf".components: {},
  data() {
    return {
      pdfList: []}; },props: {
    pdfData: {
      type: Object
    },
    index: {
      type: Number}},created() {
    PDFJS.GlobalWorkerOptions.workerSrc = require("pdfjs-dist/build/pdf.worker.min.js");
    this.getPdfFile(this.pdfData.licenseId);
  },
  methods: {
    / / PDF file
    async getPdfFile(licenseId) {
      const url = ` `;
      try {
        const res = await this.$http({
          url,
          method: "get".params: {
            licenseId
          },
          responseType: "blob"
        });
        if (res.status == 200) {
          const content = res.data;
          let file = new Blob([content], {
            type: "application/pdf; charset-UTF-8"
          });
          const objectURL = URL.createObjectURL(file);
          this.pdfList.push(objectURL);
          this._loadFile(objectURL);
          return; }}catch (error) {
        console.log(error, "getPdfFile"); }},_loadFile(url) {
      PDFJS.getDocument(url).promise.then(pdf= > {
        this.pdfDoc = pdf;
        const pdfPages = this.pdfDoc.numPages;
        this.pdfList = pdfPages;
        this.$nextTick(() = > {
          this._renderPage(1);
        });
      });
    },
    _renderPage(num) {
      this.pdfDoc.getPage(num).then(page= > {
        let canvas = document.getElementById("pdfCanvas" + num + this.index);
        var vp = page.getViewport({ scale: 1 });
        let ctx = canvas.getContext("2d");
        let dpr = window.devicePixelRatio || 1;
        let bsr =
          ctx.webkitBackingStorePixelRatio ||
          ctx.mozBackingStorePixelRatio ||
          ctx.msBackingStorePixelRatio ||
          ctx.oBackingStorePixelRatio ||
          ctx.backingStorePixelRatio ||
          1;
        let ratio = dpr / bsr;
        let viewport = page.getViewport({
          scale: window.innerWidth / vp.width
        });
        canvas.width = viewport.width * ratio;
        canvas.height = viewport.height * ratio;

        canvas.style.width = viewport.width + "px";

        canvas.style.height = viewport.height + "px";

        ctx.setTransform(ratio, 0.0, ratio, 0.0);
        let renderContext = {
          canvasContext: ctx,
          viewport: viewport
        };
        console.log(canvas, "-- -- -- -- -- -");
        if (canvas) {
          page.render(renderContext);
        }
        if (this.pdfList > num) {
          this._renderPage(num + 1); }}); }}};</script>

<style scoped></style>
Copy the code

Afterword.

To be fair, I didn’t realize it was such a question. Writing it down might help a partner who has the same problem AS me