其他方法实现:vue3中使用 vue-pdf-embed 实现pdf文件预览、翻页、下载等功能

一、文件预览

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>

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>

如果要一次性展示全部页面的话,可以将代码修改成:

<template>
    <div id="pdf-view">
        <canvas v-for="page in state.pdfPages" :key="page" :id="`page-${page}`" />
        <div id="text-view"></div>
    </div>
</template>

修改页面渲染函数:

<script setup>
function renderPage(num) {
    pdfDoc.getPage(num).then((page) => {
        // 绑定 id 值对应的元素
        const canvas = document.getElementById(`page-${num}`);
        ...
        page.render(renderContext);
        // state.pdfPages 为 pdf 文件总页数
        if (num < state.pdfPages) {
            renderPage(num + 1);
        }
    });
}
</script>

注意: 一次性渲染全部页面可能会出现加载缓慢或卡顿的问题,可以需要做相应的优化。

4、可能出现的问题

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

浏览器警告:

浏览器警告

解决方案:

getDocument 方法中追加 cMapUrlcMapPacked 参数:

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

注: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);
            })
    })
}

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%;
}

其他参考资料:https://github.com/mozilla/pdf.js/issues/11509

(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) => {...})})}

参考资料:

三、效果展示

在这里插入图片描述


四、参考资料

功能实现参考资料

pdf.js 相关参考资料

问题解决参考资料

其他

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐