PDF.js Electron Viewer

PDF.js 是基於 HTML5 解析與渲染 PDF 的 JavaScript 庫,由 Mozilla 主導開源。

本文旨在介紹 PDF.jsElectron 里如何開始使用,實際嘗試了用其 API 或嵌入 HTML 的幾種方式。

從零準備項目

項目採用 Electron React Antd PDF.js 來實現,以下是從零準備項目的過程。

Electron React

這裡用 electron-react-boilerplate 模板開始 Electron React 項目。

# 獲取模板
git clone --depth=1 \
//github.com/electron-react-boilerplate/electron-react-boilerplate \
electron-pdf-viewer

cd electron-pdf-viewer

# 設定倉庫
git remote set-url origin [email protected]:ikuokuo/electron-pdf-viewer.git
# 如果想合併成一個初始提交
#  //stackoverflow.com/a/23486788
git config --global alias.squash-all '!f(){ git reset $(git commit-tree HEAD^{tree} -m "${1:-A new start}");};f'
git squash-all "first commit"
git push -u origin main
# 依賴
npm install
# 運行
npm start
# 打包
npm run package

準備編輯器(VSCode):

code --install-extension dbaeumer.vscode-eslint
code --install-extension dzannotti.vscode-babel-coloring
code --install-extension EditorConfig.EditorConfig

其他編輯器,可見 Editor Configuration

Ant Design

添加 antd 依賴:

npm install antd

之後,就可以快速布局頁面了,如下:

PDF.js

添加 pdfjs 依賴:

npm install pdfjs-dist
npm install -D worker-loader

此外,準備 PDF 樣例進 static/,簡單用 Python 提供 HTTP 訪問:

npm run static

用於開發運行,正式運行可用 file:// 地址。

PDF.js 渲染

使用 API

用 API 渲染頁面,可見官方 Examples

1. 導入包

import * as pdfjsLib from 'pdfjs-dist/webpack';

2. 渲染頁面

(async () => {
  // 獲取 doc
  const loadingTask = pdfjsLib.getDocument(url);
  const pdf = await loadingTask.promise;

  console.log(`PDF loaded, n=${pdf.numPages}`);
  setNumPages(pdf.numPages);

  // 獲取 page
  const page = await pdf.getPage(1);

  // 獲取 canvas

  const scale = 1.5;
  const viewport = page.getViewport({ scale });
  // Support HiDPI-screens.
  const outputScale = window.devicePixelRatio || 1;

  const canvas = canvasRef.current;
  if (canvas == null) return;
  const context = canvas.getContext('2d');

  canvas.width = Math.floor(viewport.width * outputScale);
  canvas.height = Math.floor(viewport.height * outputScale);
  canvas.style.width = `${Math.floor(viewport.width)}px`;
  canvas.style.height = `${Math.floor(viewport.height)}px`;

  const transform =
    outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null;

  // 渲染 page
  const renderContext = {
    canvasContext: context,
    transform,
    viewport,
  };
  await page.render(renderContext);
  console.log('Page rendered!');
})();

完整程式碼,見 Pdfjs/index.tsx。效果如下:

使用 Viewer API

用 Viewer API 渲染,其在 pdfjs-dist/web/pdf_viewer 路徑下。

1. 導入包

import * as pdfjsLib from 'pdfjs-dist/webpack';
import { PDFViewer, EventBus } from 'pdfjs-dist/web/pdf_viewer';
import 'pdfjs-dist/web/pdf_viewer.css';

2. 布局頁面

<div className="viewer">
  <div>url={url}</div>
  <div>numPages={numPages}</div>
  <div ref={hrRef} />
  <div ref={containerRef} className="container">
    <div className="pdfViewer" />
  </div>
</div>

要求 absolute 定位:

.viewer {
  position: relative;

  .container {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    overflow: scroll;
  }
}

3. 渲染 PDF

const container = containerRef.current;
if (container == null) return;

if (hrRef.current) {
  container.style.top = `${hrRef.current.offsetTop}px`;
}

// 監聽事件,必須傳參 PDFViewer 為實例
const eventBus = new EventBus(null);
eventBus.on('pagesinit', () => {
  console.log('pagesinit');
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
eventBus.on('pagesloaded', (e: any) => {
  console.log('pagesloaded');
  console.log(e);
  setNumPages(e.pagesCount);
});
eventBus.on('pagerendered', () => {
  console.log('pagerendered');
});

// 創建 PDFViewer
const pdfViewer = new PDFViewer({
  container,
  eventBus,
  linkService: null,
  renderer: 'canvas',
  l10n: null,
});

// 導入 Document
(async () => {
  const loadingTask = pdfjsLib.getDocument(url);
  const pdf = await loadingTask.promise;
  pdfViewer.setDocument(pdf);
})();

完整程式碼,見 PdfViewer/index.tsx。效果如下:

使用 Viewer HTML

PDF.js 提供了在線演示的 viewer.html,不過 pdfjs-dist 里沒有,要自己編譯其源碼。

編譯結果已放進 static/pdfjs/,可用 Electron Window 打開 web/viewer.html?file=x.pdf 或用 iframe 嵌入。

如果自己重新編譯,過程如下:

git clone -b master --depth=1 //github.com/mozilla/pdf.js.git
cd pdf.js

# 安裝依賴
npm install -g gulp-cli
npm install

# 開發運行
gulp server
# //localhost:8888/web/viewer.html

# 編譯發布
gulp generic
# build/generic/

iframe 嵌入的話,也是打開 web/viewer.html?file=x.pdf

<div className="viewerHTML">
  <div>pdfUrl={pdfUrl}</div>
  <div>pdfWebViewerUrl={pdfWebViewerUrl}</div>
  <iframe
    className="pdfViewer"
    title="PdfViewerHTML"
    src={`${pdfWebViewerUrl}?file=${pdfUrl}`}
  />
</div>
.viewerHTML {
  .pdfViewer {
    border: none;
    width: 100%;
    height: 100%;
  }
}

這裡打開的 npm run static 提供的 HTTP 地址,效果如下:

iframe 要打開本地 HTML 試了下沒成,如果想在 Electron + React 下這麼用,還要研究下怎麼弄。

最後

PDF.js 可以說是 Web 渲染 PDF 的不二選擇,很多 PDF Web Viewer 庫都是基於它實現的。

GoCoding 個人實踐的經驗分享,可關注公眾號!