夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 技术文档

    • Git使用手册
    • Markdown使用教程
    • npm常用命令
    • yarn基本命令
    • npm packageJson属性详解
    • yaml语言教程
    • Stylus预处理语言
    • Less预处理语言
    • Sass预处理语言
    • PWA运用
    • 富文本编辑器Ueditor
    • 发布npm
    • 构建一个TS的npm包
    • tsup打包工具
    • leaflet地图框架
    • pdfjs-dist使用
      • vue使用pdf-dist实现pdf预览以及水印
        • 一.使用pdf-dist插件将PDF文件转换为一张张canvas图片
        • 二.页面引入插件
        • 三.渲染PDF
        • 四.添加水印
        • 五.完整代码(带翻页)
        • 六.完整代码(滑动)
    • xlsx插件使用
    • changesets
    • release-it
    • mathjs
  • GitHub技巧

  • 博客搭建

  • Ajax

  • Vite

  • Vitest

  • Nuxt

  • UI库文章

  • Docker

  • 技术
  • 技术文档
阿莫、
2024-06-13
目录

pdfjs-dist使用

基于 pdf.js 的 pdf 预览插件

# pdfjs-dist使用

# 一、文件预览

# 1、安装 pdfjs-dist ,此处指定版本为 2.16.105

yarn add pdfjs-dist@2.16.105

注:3.x版本部分功能的实现方法与旧版本存在差异。

# 2、html 结构内容

<template>
    <div id="pdf-view">
        <canvas v-for="page in state.pdfPages" :key="page" id="pdfCanvas" />
        <div id="text-view"></div>
    </div>
</template>
1
2
3
4
5
6

# 3、js 功能实现:

<script setup>
import * as pdfjsViewer from 'pdfjs-dist/web/pdf_viewer.js'
import 'pdfjs-dist/web/pdf_viewer.css'
import * as PDF from 'pdfjs-dist'

// 文件路径
import pdf from './2020试卷.pdf';

import { ref, reactive, onMounted, nextTick } from 'vue';

const state = reactive({
    // 文件路径
    pdfPath: pdf, 
    // 总页数
    pdfPages: 1, 
    // 页面缩放
    pdfScale: 2, 
})

onMounted(() => {
    loadFile(state.pdfPath)
});

let pdfDoc = null;

function loadFile(url) {
    PDF.getDocument({
        url,
        cMapUrl: 'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.16.105/cmaps/',
        cMapPacked: true,
    }).promise.then((pdf) => {
        pdfDoc = pdf
        // 获取pdf文件总页数
        state.pdfPages = pdf.numPages
        nextTick(() => {
            renderPage(1) // 从第一页开始渲染
        })
    })
}

function renderPage(num) {
    pdfDoc.getPage(num).then((page) => {
        const canvas = document.getElementById('pdfCanvas')
        const ctx = canvas.getContext('2d')
        const viewport = page.getViewport({ scale: state.pdfScale })
        canvas.width = viewport.width
        canvas.height = viewport.height
        const renderContext = {
            canvasContext: ctx,
            viewport
        }
        page.render(renderContext)
    })
}
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

# 4、可能出现的问题

# (1) 部分字体出现乱码或浏览器控制台出现警告

浏览器警告:浏览器警告

解决方案:

在 getDocument 方法中追加 cMapUrl 和 cMapPacked 参数 (opens new window):

PDF.getDocument({
    url,
    cMapUrl: 'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.16.105/cmaps/',
    cMapPacked: true,
})

1
2
3
4
5
6

注:cMapUrl 参数可指定为本地文件路径,可在路径 node_modules/pdfjs-dist/cmaps 中获取。通过测试发现,该警告即便不处理依然不影响页面展示,但是在后续的文本选中功能上可能会受影响。

# 二、文本选中

# 1、功能实现

在文件预览的基础上添加以下代码:

import { TextLayerBuilder } from 'pdfjs-dist/web/pdf_viewer.js';
const pdfjsWorker = import('pdfjs-dist/build/pdf.worker.entry')
PDF.GlobalWorkerOptions.workerSrc = pdfjsWorker

const eventBus = new pdfjsViewer.EventBus();

function renderPage(num) {
    pdfDoc.getPage(num).then((page) => {
        ...
        const renderContext = {
            ...
        }
        // page.render(renderContext)

        // 获取文本内容和渲染页面的 Promise
        const getTextContentPromise = page.getTextContent();
        const renderPagePromise = page.render(renderContext);

        Promise.all([getTextContentPromise, renderPagePromise])
            .then(([textContent]) => {
                const textLayerDiv = document.createElement('div');
                // 注意:此处不要修改该元素的class名称,该元素的样式通过外部导入,名称是固定的
                textLayerDiv.setAttribute('class', 'textLayer');
                // 设置容器样式
                textLayerDiv.setAttribute('style', `
                    z-index: 1;
                    opacity: 1;
                    background-color:#fff;
                    transform: scale(1.1);
                    width: 100%,
                    height: 100%,
                `);
                // 设置容器的位置和宽高
                textLayerDiv.style.left = canvas.offsetLeft + 'px';
                textLayerDiv.style.top = canvas.offsetTop + 'px';
                textLayerDiv.style.height = canvas.offsetHeight + 'px';
                textLayerDiv.style.width = canvas.offsetWidth + 'px';

                const textView = document.querySelector('#text-view');
                textView.appendChild(textLayerDiv);

                const textLayer = new TextLayerBuilder({
                    // container: ,
                    textLayerDiv: textLayerDiv,
                    pageIndex: page.pageIndex,
                    viewport: viewport,
                    eventBus,
                    // textDivs: []
                });

                textLayer.setTextContent(textContent);
                textLayer.render();
            }) 
            .catch((error) => {
                console.error('Error rendering page:', error);
            })
    })
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

# 2、可能出现的问题:

# (1) 页面文字可选中,但文本不可见

通过测试发现,将 pdfjs-dist/web/pdf_viewer.css 路径下的 color 属性注释后可显示文本。

.textLayer span,
.textLayer br {
  /* color: transparent; */
  position: absolute;
  white-space: pre;
  cursor: text;
  transform-origin: 0% 0%;
}

1
2
3
4
5
6
7
8
9
# (2) 浏览器控制台报错 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'dispatch')

浏览器报错:

浏览器报错

解决方案:

通过上网查找资料得知,需在 TextLayerBuilder 中追加参数 eventBus:

const eventBus = new pdfjsViewer.EventBus();

function renderPage(num) {
    pdfDoc.getPage(num).then((page) => {
        ...
    Promise.all([getTextContentPromise, renderPagePromise])
        .then(([textContent]) => {
                    ...
        const textLayer = new TextLayerBuilder({
            ...
            eventBus,
        });
        ...
}).catch ((error) => {...})})}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# vue使用pdf-dist实现pdf预览以及水印

# 一.使用pdf-dist插件将PDF文件转换为一张张canvas图片

npm install pdf-dist
1

# 二.页面引入插件

const pdfJS = require("pdfjs-dist");
pdfJS.GlobalWorkerOptions.workerSrc = require("pdfjs-dist/build/pdf.worker.entry");
1
2

# 三.渲染PDF

// 根据页码渲染相应的PDF
renderPage(num) {
  this.renderingPage = true;
  this.pdfData.promise.then((pdf) => {

    this.pdfPageNumber = pdf.numPages;

    pdf.getPage(num).then((page) => {
      // 获取DOM中为预览PDF准备好的canvasDOM对象
      let canvas = this.$refs.myCanvas;
      let ctx = canvas.getContext("2d");

      // 获取页面比率
      let ratio = this._getRatio(ctx);
      
      // 根据页面宽度和视口宽度的比率就是内容区的放大比率
      let dialogWidth = this.$refs["canvasCont"].offsetWidth;
      let pageWidth = page.view[2] * ratio;
      let scale = dialogWidth / pageWidth;
      let viewport = page.getViewport({ scale });
      

      // 记录内容区宽高,后期添加水印时需要
      this.width = viewport.width * ratio;
      this.height = viewport.height * ratio;

      canvas.width = this.width;
      canvas.height = this.height;

      // 缩放比率
      ctx.setTransform(ratio, 0, 0, ratio, 0, 0);

      let renderContext = {
        canvasContext: ctx,
        viewport: viewport,
      };
      page.render(renderContext).promise.then(() => {
        this.renderingPage = false;
        this.pageNo = num;

        // 添加水印
        this._renderWatermark();
      });
    });
  });
},
// 计算角度
_getRatio(ctx) {
  let dpr = window.devicePixelRatio || 1;
  let bsr =
    ctx.webkitBackingStorePixelRatio ||
    ctx.mozBackingStorePixelRatio ||
    ctx.msBackingStorePixelRatio ||
    ctx.oBackingStorePixelRatio ||
    ctx.backingStorePixelRatio ||
    1;
  return dpr / bsr;
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

# 四.添加水印

// 生成水印图片
_initWatermark() {
  let canvas = document.createElement("canvas");
  canvas.width = 200;
  canvas.height = 200;
  let ctx = canvas.getContext("2d");
  ctx.rotate((-18 * Math.PI) / 180);
  ctx.font = "10px Vedana";
  ctx.fillStyle = "rgba(0, 0, 0, 0.8)";
  ctx.textAlign = "left";
  ctx.textBaseline = "middle";
  ctx.fillText(this.watermark, 10, 100);
  return canvas;
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 五.完整代码(带翻页)

<template>
  <div class="main-container">
    <input type="file" ref="fielinput" @change="uploadFile" />
    <div ref="canvasCont" class="canvas-container">
      <canvas ref="myCanvas" class="pdf-container"></canvas>
    </div>
    <div class="pagination-wrapper">
      <button @click="clickPre">上一页</button>
      <span>第{{ pageNo }} / {{ pdfPageNumber }}页</span>
      <button @click="clickNext">下一页</button>
    </div>
  </div>
</template>
 
<script>
const pdfJS = require("pdfjs-dist");
 
pdfJS.GlobalWorkerOptions.workerSrc = require("pdfjs-dist/build/pdf.worker.entry");
export default {
  props: {
    watermark: {
      type: String,
      default: "水印文字水印文字水印文字",
    },
  },
  mounted() {},
  data() {
    return {
      pageNo: null,
      pdfPageNumber: null,
      renderingPage: false,
      pdfData: null, // PDF的base64
      scale: 1, // 缩放值
      width: "",
      height: "",
    };
  },
  methods: {
    uploadFile() {
      let inputDom = this.$refs.fielinput;
      let file = inputDom.files[0];
      let reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = () => {
        let data = atob(
          reader.result.substring(reader.result.indexOf(",") + 1)
        );
        this.loadPdfData(data);
      };
    },
    loadPdfData(data) {
      // 引入pdf.js的字体
      let CMAP_URL = "https://unpkg.com/pdfjs-dist@2.0.943/cmaps/";
      //读取base64的pdf流文件
      this.pdfData = pdfJS.getDocument({
        data: data, // PDF base64编码
        cMapUrl: CMAP_URL,
        cMapPacked: true,
      });
      this.renderPage(1);
    },
 
    // 根据页码渲染相应的PDF
    renderPage(num) {
      this.renderingPage = true;
      this.pdfData.promise.then((pdf) => {

        this.pdfPageNumber = pdf.numPages;
 
        pdf.getPage(num).then((page) => {
          // 获取DOM中为预览PDF准备好的canvasDOM对象
          let canvas = this.$refs.myCanvas;
          let ctx = canvas.getContext("2d");
 
          // 获取页面比率
          let ratio = this._getRatio(ctx);
          
          // 根据页面宽度和视口宽度的比率就是内容区的放大比率
          let dialogWidth = this.$refs["canvasCont"].offsetWidth;
          let pageWidth = page.view[2] * ratio;
          let scale = dialogWidth / pageWidth;
          let viewport = page.getViewport({ scale });
          

          // 记录内容区宽高,后期添加水印时需要
          this.width = viewport.width * ratio;
          this.height = viewport.height * ratio;
 
          canvas.width = this.width;
          canvas.height = this.height;
 
          // 缩放比率
          ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
 
          let renderContext = {
            canvasContext: ctx,
            viewport: viewport,
          };
          page.render(renderContext).promise.then(() => {
            this.renderingPage = false;
            this.pageNo = num;
 
            // 添加水印
            this._renderWatermark();
          });
        });
      });
    },
    // 计算角度
    _getRatio(ctx) {
      let dpr = window.devicePixelRatio || 1;
      let bsr =
        ctx.webkitBackingStorePixelRatio ||
        ctx.mozBackingStorePixelRatio ||
        ctx.msBackingStorePixelRatio ||
        ctx.oBackingStorePixelRatio ||
        ctx.backingStorePixelRatio ||
        1;
      return dpr / bsr;
    },
 
    // 在画布上渲染水印
    _renderWatermark() {
      let canvas = this.$refs.myCanvas;
      let ctx = canvas.getContext("2d");
      // 平铺水印
      let pattern = ctx.createPattern(this._initWatermark(), "repeat");
      ctx.rect(0, 0, this.width, this.height);
      ctx.fillStyle = pattern;
      ctx.fill();
    },
 
    // 生成水印图片
    _initWatermark() {
      let canvas = document.createElement("canvas");
      canvas.width = 200;
      canvas.height = 200;
      let ctx = canvas.getContext("2d");
      ctx.rotate((-18 * Math.PI) / 180);
      ctx.font = "10px Vedana";
      ctx.fillStyle = "rgba(0, 0, 0, 0.8)";
      ctx.textAlign = "left";
      ctx.textBaseline = "middle";
      ctx.fillText(this.watermark, 10, 100);
      return canvas;
    },
 
    clickPre() {
      if (!this.renderingPage && this.pageNo && this.pageNo > 1) {
        this.renderPage(this.pageNo - 1);
      }
    },
    clickNext() {
      if (
        !this.renderingPage &&
        this.pdfPageNumber &&
        this.pageNo &&
        this.pageNo < this.pdfPageNumber
      ) {
        this.renderPage(this.pageNo + 1);
      }
    },
  },
};
</script>
 
<style scoped>
.main-container {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.canvas-container {
  width: 100%;
  height: 100%;
  border: 1px dashed black;
  position: relative;
  display: flex;
  justify-content: center;
}
.pdf-container {
  width: 100%;
  height: 100%;
}
 
.pagination-wrapper {
  display: flex;
  justify-content: center;
  align-items: center;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191

# 六.完整代码(滑动)

<template>
  <div class="main-container">
    <input type="file" ref="fielinput" @change="uploadFile" />
    <div ref="canvasCont" class="canvas-container">
      <canvas v-for="pageIndex in pdfPageNumber" 
        :ref="`myCanvas${pageIndex}`" :key="pageIndex" class="pdf-container"></canvas>
    </div>
  </div>
</template>
 
<script>
const pdfJS = require("pdfjs-dist");
 
pdfJS.GlobalWorkerOptions.workerSrc = require("pdfjs-dist/build/pdf.worker.entry");
export default {
  props: {
    watermark: {
      type: String,
      default: "水印文字水印文字水印文字",
    },
  },
  mounted() {},
  data() {
    return {
      pageNo: null,
      pdfPageNumber: null,
      renderingPage: false,
      pdfData: null, // PDF的base64
      scale: 1, // 缩放值
      width: "",
      height: "",
    };
  },
  methods: {
    uploadFile() {
      let inputDom = this.$refs.fielinput;
      let file = inputDom.files[0];
      let reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = () => {
        let data = atob(
          reader.result.substring(reader.result.indexOf(",") + 1)
        );
        this.loadPdfData(data);
      };
    },
    loadPdfData(data) {
      // 引入pdf.js的字体
      let CMAP_URL = "https://unpkg.com/pdfjs-dist@2.0.943/cmaps/";
      //读取base64的pdf流文件
      this.pdfData = pdfJS.getDocument({
        data: data, // PDF base64编码
        cMapUrl: CMAP_URL,
        cMapPacked: true,
      });
      this.renderPage(1);
    },
 
    // 根据页码渲染相应的PDF
    renderPage(num) {
      this.renderingPage = true;
      this.pdfData.promise.then((pdf) => {

        this.pdfPageNumber = pdf.numPages;
 
        pdf.getPage(num).then((page) => {
          // 获取DOM中为预览PDF准备好的canvasDOM对象
          let canvas = this.$refs[`myCanvas${num}`][0];
          let ctx = canvas.getContext("2d");
 
          // 获取页面比率
          let ratio = this._getRatio(ctx);
          
          // 根据页面宽度和视口宽度的比率就是内容区的放大比率
          let dialogWidth = this.$refs["canvasCont"].offsetWidth;
          let pageWidth = page.view[2] * ratio;
          let scale = dialogWidth / pageWidth;
          let viewport = page.getViewport({ scale });
          

          // 记录内容区宽高,后期添加水印时需要
          this.width = viewport.width * ratio;
          this.height = viewport.height * ratio;
 
          canvas.width = this.width;
          canvas.height = this.height;
 
          // 缩放比率
          ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
 
          let renderContext = {
            canvasContext: ctx,
            viewport: viewport,
          };
         
          page.render(renderContext).promise.then(() => {
            this.renderingPage = false;
            this.pageNo = num;
            // 添加水印
            this._renderWatermark(num);
            if(num < this.pdfPageNumber){
              this.renderPage(num+1)
            }
          });
        });
      });
    },
    // 计算角度
    _getRatio(ctx) {
      let dpr = window.devicePixelRatio || 1;
      let bsr =
        ctx.webkitBackingStorePixelRatio ||
        ctx.mozBackingStorePixelRatio ||
        ctx.msBackingStorePixelRatio ||
        ctx.oBackingStorePixelRatio ||
        ctx.backingStorePixelRatio ||
        1;
      return dpr / bsr;
    },
 
    // 在画布上渲染水印
    _renderWatermark(num) {
      let canvas = this.$refs[`myCanvas${num}`][0];
      let ctx = canvas.getContext("2d");
      // 平铺水印
      let pattern = ctx.createPattern(this._initWatermark(), "repeat");
      ctx.rect(0, 0, this.width, this.height);
      ctx.fillStyle = pattern;
      ctx.fill();
    },
 
    // 生成水印图片
    _initWatermark() {
      let canvas = document.createElement("canvas");
      canvas.width = 200;
      canvas.height = 200;
      let ctx = canvas.getContext("2d");
      ctx.rotate((-18 * Math.PI) / 180);
      ctx.font = "10px Vedana";
      ctx.fillStyle = "rgba(0, 0, 0, 0.8)";
      ctx.textAlign = "left";
      ctx.textBaseline = "middle";
      ctx.fillText(this.watermark, 10, 100);
      return canvas;
    },
  },
};
</script>
 
<style scoped>
.main-container {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.canvas-container {
  width: 100%;
  height: 100%;
  border: 1px dashed black;
  position: relative;
  /* display: flex; */
  /* justify-content: center; */
}
.pdf-container {
  width: 100%;
  height: 100%;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168

#

编辑 (opens new window)
上次更新: 2024/6/14 18:24:46
leaflet地图框架
xlsx插件使用

← leaflet地图框架 xlsx插件使用→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式