Content Table

在线预览 PDF

下面介绍 2 种在线预览 PDF 的方法,不过都需要 HTML5 的支持,应该问题不大

  • 转换 PDF 为 HTML
  • 使用 HTML5 的 Canvas 在线绘制 PDF

转换 PDF 为 HTML

pdf2htmlEX 是一个开源的库,能将 PDF 转换成 HTML,支持 Mac,Linux,Windows,其 github 地址为 https://github.com/coolwanglu/pdf2htmlEX

安装:

使用:

优点:

  • 开源
  • 转换效果真的很完美
  • 微信等移动端都支持

缺点:

  • 浏览器必须支持 HTML5
  • 转换出来的文件很大,例如 3M 的 PDF 转换出来大概有 30M

Pdf.js 显示 PDF

pdf.js 是一个主要用于 HTML5 平台上在线阅读 PDF 文档的小插件,基于 JS 技术编写而成,无需任何本地技术支持,由 Mozilla Labs 发布的,他们的目标是创建一个通用的,基于标准的网络平台,能够解析和渲染 PDF 文件,并最终发布一个 PDF 阅读器扩展。

下面就用最简单的代码来演示 pdf.js 的使用,显示 PDF 的第一页:

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Canvas 显示 PDF</title>
</head>

<body>
<canvas id="pdf-canvas"></canvas>

<script src="build/pdf.js"></script>
<script src="build/pdf.worker.js"></script>

<script>
// 1. 定义一个 canvas
// 2. 调用 getDocument() 加载 pdf
// 3. 加载完 pdf 后,调用 getPage() 请求 pdf 的第 1 页
// 4. 请求完成后绘制到 canvas 上
pdfjsLib.getDocument({ url: 'foo.pdf' }).then(pdf => {
pdf.getPage(1).then(page => {
var canvas = document.getElementById('pdf-canvas');

// Get viewport at scale = 1*dpi (Mac 高分屏下为 2)
var dpi = window.devicePixelRatio || 1;
var viewport = page.getViewport(1 * dpi);
canvas.width = viewport.width;
canvas.height = viewport.height;

// page is rendered on a <canvas> element
var renderContext = {
canvasContext: canvas.getContext('2d'),
viewport: viewport,
};

page.render(renderContext).then(function() {
// page has rendered
});
});
});
</script>
</body>

</html>

下面的代码一次性显示 PDF 的所有页:

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Canvas 显示 PDF</title>
<style>
canvas {
border: 1px solid #eee;
display: block;
margin-bottom: 6px;
}
</style>
</head>

<body>
<div id="pdf-reader"></div>

<script src="http://cdn.bootcss.com/jquery/1.9.1/jquery.min.js"></script>
<script src="build/pdf.js"></script>
<script src="build/pdf.worker.js"></script>

<script>
function renderPage(pdf, pageNumber) {
pdf.getPage(pageNumber).then(page => {
var canvas = $(`#canvas-${pageNumber}`)[0];

// Get viewport at scale = 1*dpi
var dpi = window.devicePixelRatio || 1;
var viewport = page.getViewport(1 * dpi);

// page is rendered on a <canvas> element
var renderContext = {
canvasContext: canvas.getContext('2d'),
viewport: viewport,
};

page.render(renderContext).then(function() {});
});
}

pdfjsLib.getDocument({ url: 'foo.pdf' }).then(pdf => {
// PDF 加载后
// 1. 请求第一页,使用第一页的大小先预设每一页 canvas 的大小
// 2. 为每一页创建一个 canvas
// 3. 显示 PDF 的每一页

pdf.getPage(1).then(page => {
// [1] 请求第一页,使用第一页的大小先预设每一页 canvas 的大小
var dpi = window.devicePixelRatio || 1;
var viewport = page.getViewport(1 * dpi);

// [2] 为每一页创建一个 canvas
for (var i = 1; i <= pdf.numPages; i++) {
var $canvas = $(`<canvas id="canvas-${i}">`);
$canvas[0].width = viewport.width;
$canvas[0].height = viewport.height;
$canvas.appendTo($('#pdf-reader'));
}

// [3] 显示 PDF 的每一页
setTimeout(() => {
for (var i = 1; i <= pdf.numPages; i++) {
renderPage(pdf, i);
}
}, 0);
});
});
</script>
</body>

</html>

提示: 先创建每一页的 canvas 进行占位,但不显示这一页的内容,当滚动到某一页可见的时候才绘制 PDF 的这一页,加快显示的效率。

上面的例子需要 pdf.js 和 pdf.worker.js 这 2 个文件,去哪里找呢?

打开 https://github.com/mozilla/pdf.js ,根据说明自己编译,或者点击 releases 进行下载。

优点:

  • 开源
  • 微信等移动端都支持
  • 和转换 PDF 为 HTML 的方式比较起来,需要的流量小很多

缺点:

  • 浏览器必须支持 HTML5
  • 使用 Canvas 绘制的 PDF 不能够选择文字,文字不够清晰

Pdf.js 的 PDFViewer

为了解决上面 pdf.js 简单在 Canvas 中绘制的 PDF 不够清晰,使用 Webpack 管理的项目中如 Vue-Cli 创建的项目可以使用 pdf.js web 案例中提供的 PDFViewer 来进行显示 PDF,有以下优点:

  • 根据 PDF 的内容自动决定使用 Canvas 还是文本进行显示
  • 能够选择文本
  • 懒加载,有加载动画
  • 显示 PDF 的所有页

可以参考源码的 examples/components 下的例子,或者按照下面的步骤进行:

  1. 添加依赖: yarn add pdfjs-dist

  2. 复制 node_modules/pdfjs-dist/build/pdf.worker.min.js 到项目的静态资源文件夹下,如 public/static/lib/pdf.worker.min.js

  3. 为了使得 PDF 能够跟随 pdf-viewer-container 自动调整大小,可以使用 CSS-Element-Queries,下载得到 ResizeSensor.js,复制到项目的静态资源文件夹下,如 public/static/lib/ResizeSensor.js,并在 index.html 中引入 <script src="/static/lib/ResizeSensor.js"></script>,如果不需要这个功能,删除下面代码的 35 行处 new ResizeSensor 即可

  4. 使用下面的代码就可以显示 PDF 了:

    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
    <template>
    <div ref="container" class="pdf-viewer-container">
    <div ref="viewer" class="pdfViewer"></div>
    </div>
    </template>

    <script>
    import 'pdfjs-dist/web/pdf_viewer.css';
    import pdfjsLib from 'pdfjs-dist';
    import { PDFViewer } from 'pdfjs-dist/web/pdf_viewer';

    // 提示: 如果没有设置 workerSrc,大多数 PDF 都能正常显示,但是某些 PDF 中文字显示不出来
    // 需要把 pdf.worker.min.js 放到项目的静态文件目录下
    pdfjsLib.GlobalWorkerOptions.workerSrc = '/static/lib/pdf.worker.min.js';

    export default {
    data() {
    return {
    url: '/static/foo.pdf',
    pdf: null,
    };
    },
    mounted() {
    const pdfContainer = this.$refs.container;
    const pdfViewer = new PDFViewer({
    container: pdfContainer,
    viewer: this.$refs.viewer,
    });

    // PDF 的宽度为 container 的宽度
    pdfContainer.addEventListener('pagesinit', () => {
    pdfViewer.currentScaleValue = 'page-width'; // Change pdfViewer's default scale.
    });
    // eslint-disable-next-line
    new ResizeSensor(pdfContainer, () => {
    pdfViewer.currentScaleValue = 'page-width';
    });

    // Loading PDF document
    pdfjsLib.getDocument({ url: this.url, cMapPacked: true }).then(pdf => {
    this.pdf = pdf;
    pdfViewer.setDocument(pdf);
    });
    },
    computed: {
    pageCount() {
    return this.pdf ? this.pdf.numPages : '0';
    }
    }
    };
    </script>

    <style lang="scss">
    .pdf-viewer-container {
    .pdfViewer {
    .page {
    border: none; // 去掉边框
    border-image: none;
    margin-bottom: 40px;

    // 显示页码
    // 每个 .page 上都有页码的属性 data-page-number="3", CSS 中可以通过 attr 读取这个属性
    &::after {
    position: relative;
    display: block;
    width: 100%;
    height: 40px;
    line-height: 40px;
    text-align: center;
    content: '第 ' attr(data-page-number) ' 页';
    }
    }
    }
    }
    </style>
  5. data.url 为 PDF 的路径,修改为自己项目里 PDF 的具体路径

  6. 把上面的 Vue 模板修改为组件,在 props 中定义 url,通过参数传入进来就可以重复使用了

  7. 为了让 PDF 的缩放更舒服一些,还可以使用 Underscore.js 的 _.debounce() 函数进行优化

  8. 在非 Webpack 的项目中,也就是一个普通的页面里,我们也提取出了相关的文件写了个简单的例子,下载 pdf-reader.7z 照做即可 (需要放在 Web 服务中运行)

在线预览 PDF 的其他方式

在线预览 PDF 还有很多种其他方法,可以参考 http://www.open-open.com/news/view/1fc3e18/

思考

需要在线显示 PPT,也是想到要把 PPT 转换为 HTML 然后在线预览,但是找了好久都没有找到一个免费、并且好用的软件来进行转换,但是把 PPT 转换为 PDF 的免费软件却不少,那么是不是可以考虑先把 PPT 转换为 PDF,然后在通过上面的方法在线预览 PDF 呢?