需求:利用pdfjs插件进行pdf预览,并支持对切片数据进行高亮
问题:现有pdfjs插件只支持对关键词搜索高亮、长文本效果不好
介绍
PDF.js 是一个强大的 JavaScript 库,可以在浏览器中渲染 PDF 文件,无需依赖任何第三方插件。它允许开发者在 web 应用中嵌入 PDF 阅读器,提供了一个完整的 PDF 渲染解决方案。PDF.js 是由 Mozilla 开发的,使用纯 JavaScript 实现,使用了 Web Worker 来处理复杂的解析任务【来源:百度】
PDF.js高亮的原理:循环pdf每一页的的文本信息,进行正则匹配,返回关键词在本页的位置以及匹配长度,进行渲染。
- 处理文本内容 #extractText
- 循环匹配文本
- 匹配 #calculateMatch
1、将关键词转成正则表达式 #convertToRegExpString
2、正则匹配文本 #calculateRegExpMatch
3、获取源坐标点、长度 getOriginalIndex - 渲染匹配
本文也只是处理了部分匹配规则,因为切片数据与pdfjs插件解析出来的文本数据并不一定能一一匹配,还有各种各样的异常需要处理
更好的高亮方式我认为是后端返回坐标点,前端添加遮罩的方式。
可以参考文章【PDF.js】2023 最新 PDF.js 在 Vue3 中的使用
使用
PDF.js版本:4.3.136
vue:vue3
vue3中引入pdfjs进行文件预览的方式有多种,这里选用部署在服务器上,远程调用传入url的方式进行预览。具体方式可以参考文章:
PDF.js 前端开发使用指南
pdf.js使用全教程(开发笔记)
<iframe id="pdf-view-iframe" :src="pdfUrl" width="100%" height="100%"></iframe>
pdfUrl的组成:插件地址+pdf文件源地址,例如:
http://localhost:8080/pdfjs/web/viewer.html?file=http.xxxxxxx.test.pdf
高亮-修改源码
1、pdfjs插件接收需要高亮的文本
2、调用搜索方法进行查询
传参方式
接收需要高亮的文本有两种方式,一种是在pdf文件地址上拼接参数。如:
1、url传参
http://localhost:8080/pdfjs/web/viewer.html?file=http.xxxxxxx.test.pdf?mark=你好
这里的mark就是需要高亮的文字,同理这里也可以传入page等
2、postMessage
const iframe = document.getElementById('pdf-view-iframe');
// 高亮文本
iframe.contentWindow?.postMessage({ type: 'HIGHT_LINE', value: targetText }, '*');
这里如果仅使用方法1,则相同文件切片数据切换的时候都会重新刷新页面,用户体验感不好;
仅使用方法2,如果要实现首次渲染就高亮选中的功能,需要等待页面渲染完成(使用定时器或者其他方式)
两种方式结合可以实现:
- 首次加载pdf即可高亮文本
- 切换不同的切片数据可以页面不刷新跳转到高亮页
具体实现
一、使用Iframe载入pdf页面
<template>
<div class="container">
<iframe id="pdf-view-iframe" :src="pdfUrl" width="100%" height="100%"></iframe>
</div>
</template>
二、监听url以及切片文本、进行渲染\高亮
watch(
() => [props.url, props.markText],
(newv, oldv) => {
if ((oldv && newv && newv[0] !== oldv[0]) || !oldv) {
render();
} else {
setHighLight(props.markText);
}
},
{ immediate: true, deep: true },
);
以下的props.markText 都是encodeURIComponent之后的字符串
const render = () => {
let paramsUrl = '';
if (props.markText) {
paramsUrl += `&markText=${props.markText}`;
}
if (props.page) {
paramsUrl += `&page=${props.page}`;
}
pdfUrl.value = fileUrl + encodeURIComponent(props.url) + paramsUrl;
}
};
const setHighLight = (targetText) => {
const iframe = document.getElementById('pdf-view-iframe');
// 高亮文本
iframe.contentWindow?.postMessage({ type: 'HIGHT_LINE', value: targetText }, '*');
};
三、修改源码
1、在web目录下新建messageHandler.js文件处理postMessage消息,并在viewer.html文件引入
window.addEventListener('message', function (message) {
const { data } = message;
const { type, value } = data;
switch (type) {
// 页面切换
case 'SET_PAGE':
window.PDFViewerApplication.page = value;
break;
// 高亮文本
case 'HIGHT_LINE':
const anchor = decodeURIComponent(value);
anchor && setHightlight(anchor);
break;
// 页面高度定位
case 'SET_TOP':
document.getElementById('viewerContainer').scrollTop =
document.getElementById('viewerContainer').scrollTop +
c_urlArray['top'] * 1;
default:
break;
}
});
2、调用 PDFViewerApplication 的find方法进行高亮匹配,我这里将大段的高亮文本切割成35个字一段的文本
const setHightlight = anchor => {
const arr = [];
for (let i = 0; i < anchor.length; i++) {
if (i % 35 === 0) {
arr.push(anchor.substring(i, i + 35));
}
}
const last = arr.length - 1;
if (arr[last].length < 10) {
arr[last - 1] = arr[last - 1] + arr[last];
arr.pop();
}
window.PDFViewerApplication.eventBus.dispatch('find', {
query: arr,
caseSensitive: false,
highlightAll: true,
findPrevious: false,
});
};
3、viewer.mjs 对url上的参数处理,进行高亮匹配 setInitialView函数
// 获取url参数
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split('&');
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');
if (pair[0] == variable) {
return pair[1];
}
}
return false;
}
// 页数 跳转
const page = getQueryVariable('page');
// 关键词检索跳转
const markText = getQueryVariable('markText');
if (page) {
this.pdfViewer.currentPageNumber = Number(page);
}
if (markText) {
const markTextDecode = decodeURIComponent(markText);
setHighlightAllMessage(markTextDecode);
}
在messageHandler.js将setHightlight挂载在window上 供viewer.mjs调用
window.setHighlightAllMessage = setHightlight;
4、修改#convertToRegExpString函数,给每个字后面加上匹配任何数量空格
数字后面也加上
到这里就可以完成切片数据高亮了,如果切片数据不会分布在不同页的话,这里已经可以很好地匹配到了。
如果需要匹配换页的文本,可以修改源码将原本的:在单页匹配改为在全文本匹配,我这里不想过多修改源代码,所以另外写了高亮函数,在scrollIntoView函数中添加代码进行匹配
自定义函数setHighlightHtml,也是在messageHandler.js函数中,将函数挂在window上
messageHandler.js还有自定义的一些函数,这里就不细说了