Pdf
IMPORT MODULE

基于 pdf.js 的PDF预览组件。

默认PDF预览并不是刚需的原因,因此采用一种延迟加载脚本的形式,可以通过全局配置配置来改变默认 pdf.js 类库的根路径。

组件也是受 ng2-pdf-viewer 启发。

使用本地路径

// angular.json
{
  "glob": "**/(build|web)/**",
  "input": "./node_modules/pdfjs-dist/",
  "ignore": ["*.js.map", "*.d.ts"],
  "output": "assets/pdfjs/"
}
// global-config.module.ts
const alainConfig: AlainConfig = {
  pdf: {
    lib: '/assets/pdfjs/'
  }
};

代码演示

基础样例

最简单的用法。

expand codeexpand code
import { Component } from '@angular/core';

@Component({
  selector: 'components-pdf-basic',
  template: `
    <button nz-button nzType="primary" (click)="src = src === one ? two : one">Change File</button>
    <pdf [src]="src" style="height: 300px" (change)="handle($event)"></pdf>
  `,
})
export class ComponentsPdfBasicComponent {
  one = `https://raw.githubusercontent.com/mozilla/pdf.js/master/web/compressed.tracemonkey-pldi-09.pdf`;
  two = `https://raw.githubusercontent.com/mozilla/pdf.js/master/examples/learning/helloworld.pdf`;
  src = this.one;
  handle(ev: any): void {
    console.log(ev);
  }
}

定制化

提供丰富接口用于定制化。

expand codeexpand code
import { ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core';
import { PdfChangeEvent, PdfComponent, PdfZoomScale } from '@delon/abc/pdf';
import { NzUploadFile } from 'ng-zorro-antd/upload';
import { Subject } from 'rxjs';

@Component({
  selector: 'components-pdf-design',
  template: `
    <div nz-row [nzGutter]="16">
      <div nz-col nzSpan="8">
        <div se-container col="1">
          <se label="Url of the pdf file (Press enter to run)">
            <input #url nz-input [ngModel]="src" (keyup.enter)="uploadSrc(url.value)" />
          </se>
          <se label="Local of the pdf file">
            <nz-upload nzAccept=".pdf" [nzBeforeUpload]="beforeUpload">
              <button nz-button><i nz-icon nzType="upload"></i>Select File</button>
            </nz-upload>
          </se>
          <se label="Render Text">
            <nz-switch [(ngModel)]="renderText"></nz-switch>
          </se>
          <se label="Original size">
            <nz-switch [(ngModel)]="originalSize"></nz-switch>
          </se>
          <se *ngIf="originalSize" label="Fit to page">
            <nz-switch [(ngModel)]="fitToPage"></nz-switch>
          </se>
          <se label="Auto size">
            <nz-switch [(ngModel)]="autoReSize"></nz-switch>
          </se>
          <se label="Show All Pages">
            <nz-switch [(ngModel)]="showAll" (ngModelChange)="changeShowAllPages($event)"></nz-switch>
          </se>
          <se *ngIf="!originalSize" label="Zoom Scale">
            <nz-select [(ngModel)]="zoomScale">
              <nz-option nzValue="page-height" nzLabel="Page Height"></nz-option>
              <nz-option nzValue="page-fit" nzLabel="Page Fit"></nz-option>
              <nz-option nzValue="page-width" nzLabel="Page Width"></nz-option>
            </nz-select>
          </se>
          <se label="Zoom">
            <nz-input-number [(ngModel)]="zoom" [nzStep]="0.1"></nz-input-number>
          </se>
          <se *ngIf="showAll" label="Stick to page ">
            <nz-switch [(ngModel)]="stickToPage"></nz-switch>
          </se>
          <se *ngIf="stickToPage" label="Page">
            <nz-pagination [(nzPageIndex)]="pi" [nzPageSize]="1" [nzTotal]="total" nzSimple></nz-pagination>
          </se>
          <se label="Rotation">
            <nz-input-number [(ngModel)]="rotation" [nzStep]="90"></nz-input-number>
          </se>
          <se label="Outline">
            <nz-switch [(ngModel)]="outline"></nz-switch>
          </se>
          <se *ngIf="outline" [label]="null">
            <nz-empty *ngIf="outlineList == null"></nz-empty>
            <ng-template #outlineTpl let-ls let-level="level">
              <li *ngFor="let i of ls" [style.paddingLeft.px]="level * 16">
                <a (click)="navigateTo(i.dest)">{{ i.title }}</a>
                <ul *ngIf="i.items && i.items.length > 0">
                  <ng-container *ngTemplateOutlet="outlineTpl; context: { $implicit: i.items, level: level + 1 }"></ng-container>
                </ul>
              </li>
            </ng-template>
            <ul *ngIf="outlineList">
              <ng-container *ngTemplateOutlet="outlineTpl; context: { $implicit: outlineList, level: 0 }"></ng-container>
            </ul>
          </se>
          <se label="Search pdf">
            <input #qIpt nz-input placeholder="Search..." (input)="search$.next(qIpt.value)" (keyup.enter)="search$.next(qIpt.value)" />
          </se>
        </div>
      </div>
      <div nz-col nzSpan="16" style="background-color: #fafafa; padding: 32px 0;">
        <pdf
          #pdf
          [src]="src"
          [pi]="pi"
          [renderText]="renderText"
          [showAll]="showAll"
          [originalSize]="originalSize"
          [fitToPage]="fitToPage"
          [stickToPage]="stickToPage"
          [zoomScale]="zoomScale"
          [zoom]="zoom"
          [rotation]="rotation"
          [autoReSize]="autoReSize"
          (change)="change($event)"
          style="height: 600px"
        ></pdf>
      </div>
    </div>
  `,
})
export class ComponentsPdfDesignComponent implements OnInit {
  @ViewChild('pdf') private comp!: PdfComponent;
  src = `https://raw.githubusercontent.com/mozilla/pdf.js/master/web/compressed.tracemonkey-pldi-09.pdf`;
  pi = 1;
  total = 0;
  renderText = true;
  stickToPage = true;
  originalSize = true;
  fitToPage = false;
  showAll = true;
  zoomScale: PdfZoomScale = 'page-width';
  rotation = 0;
  zoom = 1;
  autoReSize = true;
  outline = false;
  outlineList: any;
  q = '';
  search$ = new Subject<string>();

  constructor(private cdr: ChangeDetectorRef) {}

  ngOnInit(): void {
    this.search$.subscribe((q: string) => {
      if (q !== this.q) {
        this.q = q;
        this.comp.findController.executeCommand('find', {
          query: this.q,
          highlightAll: true,
        });
      } else {
        this.comp.findController.executeCommand('findagain', {
          query: this.q,
          highlightAll: true,
        });
      }
    });
  }

  change(ev: PdfChangeEvent): void {
    switch (ev.type) {
      case 'loaded':
        this.total = ev.total!;
        this.loadOutline();
        break;
      case 'pi':
        this.pi = ev.pi!;
        break;
    }

    if (ev.type !== 'load-progress') console.log(ev);
  }

  uploadSrc(src: string): void {
    this.src = src;
  }

  changeShowAllPages(_val: boolean): void {
    this.stickToPage = true;
  }

  beforeUpload = (file: NzUploadFile): boolean => {
    const reader = new FileReader();
    reader.onload = (e: any) => {
      this.src = e.target.result;
      this.cdr.detectChanges();
    };
    reader.readAsArrayBuffer(file as any);
    return false;
  };

  loadOutline(): void {
    this.comp.pdf.getOutline().then((outline: any[]) => {
      this.outlineList = outline;
    });
  }

  navigateTo(dest: any): void {
    this.comp.linkService.navigateTo(dest);
  }
}

API

pdf

成员说明类型默认值全局配置
[src]指定文档路径string, object, UInt8Array--
[pi]当前页number1-
[showAll]是否显示全部页booleantrue
[renderText]是否启用文字层,开启后允许文字选择booleantrue
[textLayerMode]文字层渲染模式PdfTextLayerModeENABLE-
[showBorders]是否显示页面边框booleanfalse
[stickToPage]是否视野保持在 pi 页上booleanfalse-
[originalSize]控制文档显示大小,true 按原始大小,false 按容器大小booleantrue
[fitToPage]控制原始尺寸不会超出容器大小booleanfalse
[zoom]控制缩放文档number1-
[zoomScale]缩放文档的计算方式PdfZoomScalepage-width-
[rotation]旋转文档number0-
[autoReSize]是否自动缩放booleantrue
[externalLinkTarget]外部链接打开形式PdfExternalLinkTargetBLANK-
[delay]延迟初始化,单位:毫秒number--
(change)变更时回调EventEmitter<PdfChangeEvent>--

常见问题

为什么有时需要指定高度

当启用显示全部页时,如果要让页码的控制产生有效,需要确保组件的高度是一个有效值。