封装React-PDF预览组件--canvas渲染篇

一 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实例,在这里返回的数据里有我们需要的页数等等数据
    数据解析图1
  • 获取该页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/[email protected]/cmaps/’)
    -在本地的webpack配置中将解析字体文件的文件夹打包成一个模块,具体操作如下:
    1. 在node_modules中的pdfjs-dist有一个专门存放解析字体的文件夹cmaps
      pdfjs插件的文件目录
      字体解析文件夹
    2. 添加解析PDF的配置,以及修改打包配置文件 ---- 在webpack.pro.js(webpack配置文件做修改)
 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>

实现的效果:

合同打开效果
合同打开效果2

猜你喜欢

转载自blog.csdn.net/xiaoyangzhu/article/details/103075742