一 pdfjs
-
使用背景:
小菜鸡码农的我在公司负责的移动端项目,是一款web App,因为安卓端和IOS的浏览器对于PDF文件的解析支持不同,所以运用iframe标签等等在安卓都无法实现浏览PDF文件的功能,因此在业务的驱动下,我自己开始封装组件了 -
官网: https://mozilla.github.io/pdf.js/examples/
-
放心使用: 参考科普篇 https://blog.csdn.net/goobird/article/details/85673918
现在项目中安装该插件:
npm install pdfjs-dist --save
- 官网示例: https://mozilla.github.io/pdf.js/web/viewer.html
- PDF.js可以实现在html下直接浏览pdf文档,是一款开源的pdf文档读取解析插件。基本上其他相关的PDF插件都是基于pdf.js封装的,能将PDF文件渲染成Canvas。
- PDF.js主要包含两个库文件,一个pdf.js和一个pdf.worker.js,一个负责API解析,一个负责核心解析。
二 实现插件功能的属性和api
可以参考官网的文档: https://github.com/mozilla/pdf.js/blob/master/src/display/api.js
pdf.js渲染PDF文件的流程:Fetch pdf (url / buffer) ——> canvas ——> 渲染
- canvas
<canvas
ref={this.setCanvas}
width={window.innerWidth}
height={window.innerHeight}
/>
//获取canvas实例
setCanvas = (el) => {
this.canvas = el;
}
获取到的el: <canvas width="595" height="841"></canvas>
就是PDF的渲染元素啦
- pdfDoc_,PDFjs获取到的promise实例,在这里返回的数据里有我们需要的页数等等数据
- 获取该页PDF的显示
//使用promise来获取页数
pdfDoc.getPage(num).then((page) =>{
let viewport = page.getViewport({scale: this.state.scale});
canvas.height = viewport.height;
canvas.width = viewport.width;
//将PDF文件转化为canvas context
let renderContext = {
canvasContext: canvas.getContext('2d'),
viewport: viewport
};
let renderTask = page.render(renderContext);
//等待渲染完成
renderTask.promise.then(() =>{
this.setState({
pageRendering: false,
isFirstDisabled: num > 1 ? false : true,
isLastDisabled: num === this.state.Total ? true : false
});
if (this.state.pageNumPending !== null) {
//翻页的新的一页正在加载中
this.renderPage(pageNumPending);
this.setState({
pageNumPending: null
});
}
});
});
三 实现过程中遇到的问题
1.在PDF文件中,部分中文字体解析不到位,导致文字丢失
原本像右边显示的字体,在安卓浏览器渲染不出来!!
解决方案:
componentDidMount() {
const { src } = this.props;
const { pageNum } = this.state;
pdfjs.getDocument({
url: src,
cMapUrl: 'cmaps/',//加载pdf.js提供的字体文件
cMapPacked: true, // 此参数需要设为true
}).promise.then((pdfDoc_) => {
this.setState({
pdfDoc: pdfDoc_,
Total: pdfDoc_._pdfInfo.numPages
},()=>{
//初始化第一页文件
this.renderPage(pageNum);
});
});
}
在获取文件的函数里加上参数:
cMapUrl: ‘cmaps/’,//加载pdf.js提供的字体文件
cMapPacked: true, // 此参数需要设为true
- cMapUrl
-可以选用引入线上的仓库(如:‘https://cdn.jsdelivr.net/npm/pdfjs-dist@2.0.943/cmaps/’)
-在本地的webpack配置中将解析字体文件的文件夹打包成一个模块,具体操作如下:- 在node_modules中的pdfjs-dist有一个专门存放解析字体的文件夹cmaps
- 添加解析PDF的配置,以及修改打包配置文件 ---- 在webpack.pro.js(webpack配置文件做修改)
- 在node_modules中的pdfjs-dist有一个专门存放解析字体的文件夹cmaps
module: {
rules: [
{
test: /.(woff|woff2|eot|ttf|otf|pdf)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name]_[hash:5][ext]'
}
}
],
include: path.join(__dirname, '../src'),
}
]
},
plugins: [
new CopyWebpackPlugin([
{
from: path.join(__dirname, '../node_modules/pdfjs-dist/cmaps/'),
to: path.join(__dirname, '../dist/cmaps'),//这就是使用是引入的路径
},
]),
],
结果不同字体的显示也不会丢失啦!(马赛克是我人为处理的哈哈哈!)
最终版代码:
import React, { Component } from 'react';
import pdfjs from 'pdfjs-dist/build/pdf';
import { Button } from 'antd-mobile';
import PropTypes from 'prop-types';
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry';
import styles from './style.less';
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;
export class PDF extends Component {
canvas = null;
constructor(props) {
super(props);
this.renderPage = this.renderPage.bind(this);
this.queueRenderPage = this.queueRenderPage.bind(this);
this.onPrevPage = this.onPrevPage.bind(this);
this.onNextPage = this.onNextPage.bind(this);
this.state = {
pdfDoc: null,
Total: 0,
PerviousPage: 1,
LastPage: 1,
pageNum: 1,
pageRendering: false,
pageNumPending: null,
scale: 1,
isFirstDisabled: true,
isLastDisabled: false
};
}
componentDidMount() {
const { src } = this.props;
const { pageNum } = this.state;
pdfjs.getDocument({
url: src,
cMapUrl: 'cmaps/',//加载pdf.js提供的字体文件
cMapPacked: true, // 此参数需要设为true
}).promise.then((pdfDoc_) => {
this.setState({
pdfDoc: pdfDoc_,
Total: pdfDoc_._pdfInfo.numPages
},()=>{
//初始化第一页文件
this.renderPage(pageNum);
});
});
}
//获取canvas实例
setCanvas = (el) => {
this.canvas = el;
}
renderPage(num) {
const canvas = this.canvas;
const {
pdfDoc,
pageNumPending
} = this.state;
this.setState({
pageRendering: true,
});
//使用promise来获取页数
pdfDoc.getPage(num).then((page) =>{
let viewport = page.getViewport({scale: this.state.scale});
canvas.height = viewport.height;
canvas.width = viewport.width;
//将PDF文件转化为canvas context
let renderContext = {
canvasContext: canvas.getContext('2d'),
viewport: viewport
};
let renderTask = page.render(renderContext);
//等待渲染完成
renderTask.promise.then(() =>{
this.setState({
pageRendering: false,
isFirstDisabled: num > 1 ? false : true,
isLastDisabled: num === this.state.Total ? true : false
});
if (this.state.pageNumPending !== null) {
//翻页的新的一页正在加载中
this.renderPage(pageNumPending);
this.setState({
pageNumPending: null
});
}
});
});
//改变页数计算
this.setState({
PerviousPage: num
});
}
queueRenderPage(num) {
if (this.state.pageRendering) {
this.setState({
pageNumPending: num
});
} else {
this.renderPage(num);
}
}
onPrevPage() {
if (this.state.PerviousPage <= 1) {
return;
}
let {PerviousPage} = this.state;
this.setState({
PerviousPage: PerviousPage--
},()=>{
this.queueRenderPage(PerviousPage);
});
}
onNextPage() {
if (this.state.PerviousPage >= this.state.pdfDoc.numPages) {
return;
}
let {PerviousPage} = this.state;
this.setState({
PerviousPage: PerviousPage++
},()=>{
this.queueRenderPage(PerviousPage);
});
}
render() {
const { Total, PerviousPage, isFirstDisabled, isLastDisabled } = this.state;
return (
<div>
<div className={styles.pageArea}>
<Button className={styles.pageButton} type="ghost" inline size="small" onClick={this.onPrevPage} disabled={isFirstDisabled}>上一页</Button>
<div className={styles.pageNum}>
<span>{PerviousPage}</span>/<span>{Total}</span>
</div>
<Button className={styles.pageButton} type="ghost" inline size="small" onClick={this.onNextPage} disabled={isLastDisabled}>下一页</Button>
</div>
<canvas
ref={this.setCanvas}
width={window.innerWidth}
height={window.innerHeight}
/>
</div>
);
}
}
PDF.propTypes = {
src: PropTypes.string.isRequired,
children: PropTypes.object
};
使用:
<PDF src={pdfUrl.templateurl}></PDF>
实现的效果: