AngularでPDFを作成する

Angular

やること

  • Angularを使ってクライアントサイドでPDFを作成する
  • PDFライブラリとしてPDF-LIBを使用する
  • 指定したttf(TrueType Font)ファイルのフォントで文字を描画する
  • 描画内容は文字・線・長方形とし、文字は文字列情報だけ別で指定することも可能とする

環境

  • node:14.11.0
  • Angular:10.1.1
  • pdf-lib:1.11.1

インストール

Angularは入っている前提で、pdf-libをインストール

npm install --save pdf-lib

任意のフォントを使用するためには追加でfontkitが必要

npm install --save @pdf-lib/fontkit

ソース

  • PDFデータを生成するServiceを実装する
  • PDFデータはURL.createObjectURLで生成したURLを使いブラウザで表示する
  • Serviceを呼び出すプログラム(今回はComponent)からPDFの描画内容を指定できるよう、パラメータを渡せるようにする

Component

テンプレート作成

ng g component pdf-view  

Html
PDFデータのURLをiframesrcに渡すことでブラウザ上に描画するが、そのまま渡すとAngularのセキュリティ機能がERROR Error: unsafe value used in a resource URL contextなるエラーを吐いてブロックしてしまう。このため、sanitizer.bypassSecurityTrustResourceUrlを通してエラーを回避している。

<div style="display: flex; justify-content: center;">
    <iframe [src]="sanitizer.bypassSecurityTrustResourceUrl(url)" style="width: 95vw; height: 95vh"></iframe>
</div>

Typescript
描画用のパラメータをpdf.serviceに渡して描画用のURLを受け取る。
パラメータの設定内容は基本的にAPIリファレンスに沿っているが、Y座標の指定方法を変更している。具体的には、素のAPIではXY座標の基準点が左下となるが、個人的には左上の方が考えやすいため、左上を基準とした指定値として解釈するようService側で計算を行うこととする。

import { Component, OnInit } from '@angular/core';
import { DomSanitizer } from "@angular/platform-browser";
import { PdfService } from '../pdf.service';

@Component({
  selector: 'app-pdf-view',
  templateUrl: './pdf-view.component.html',
  styleUrls: ['./pdf-view.component.css']
})
export class PdfViewComponent implements OnInit {
  url = ""

  format = {
    text: [
      {value:"ほげほげ", x:50, y:50},
      {value:"もげもげ", x:50, y:100}
    ],
    field: [
      {key:"KEY1", x:50, y:150},
      {key:"KEY2", x:50, y:200}
    ],
    line: [
      {start:{x:50, y:250}, end:{x:300, y:300}, thickness:2},
      {start:{x:300, y:300}, end:{x:50, y:350}, thickness:2}
    ],
    rect: [
      {x:50, y:50, width:100, height:75, borderWidth:1, color: {r:1, g:0, b:0}, opacity: 0.5},
      {x:50, y:150, width:20, height:150, borderWidth:1, color: {r:0, g:0, b:1}, opacity: 0.5},
    ]
  }

  values = {KEY1:"もりもり", KEY2:"ふがふが"}

  constructor(private pdf:PdfService, public sanitizer:DomSanitizer) {
  }

  ngOnInit(): void {
    this.pdf.getUrl(this.format, this.values).then(url => this.url = url)
  }
}

Service

テンプレート作成

ng g service pdf

Typescript
ほとんどが公式のExampleの切り貼り。フォントはIPAフォントを拝借した。前述の通り、描画内容のパラメータ化とY軸指定の反転のためにPDFページに要素を追加する関数(drawXXX)を用意している。

import { Injectable } from '@angular/core';
import { PDFDocument, PDFPage, PDFFont, StandardFonts, degrees, grayscale, rgb } from 'pdf-lib'
import * as fontkit from '@pdf-lib/fontkit';

@Injectable({
  providedIn: 'root'
})
export class PdfService {
  defaultFont:PDFFont = null

  constructor() { }

  async getUrl(format:any, values:any):Promise<string> {
    const pdfDoc:PDFDocument = await PDFDocument.create()
    pdfDoc.registerFontkit(fontkit['default']);
    let font:PDFFont = this.defaultFont
    if (font== null) {
      const fontUrl:string = 'assets/font/ipam.ttf'
      const fontBytes:ArrayBuffer = await fetch(fontUrl).then((res) => res.arrayBuffer())
      font = await pdfDoc.embedFont(fontBytes)
    }
    const page:PDFPage = pdfDoc.addPage()
    page.setFont(font)
    page.setFontSize(20)
    if (format) {
      const { width, height } = page.getSize()
      format.text.forEach(element => this.drawText(page, font, width, height, element))
      format.field.forEach(element => this.drawField(page, font, width, height, element, values[element.key]))
      format.line.forEach(element => this.drawLine(page, font, width, height, element))
      format.rect.forEach(element => this.drawRectangle(page, font, width, height, element))
    }
    const pdfBytes:Uint8Array = await pdfDoc.save()
    const blob:Blob = new Blob([pdfBytes], {type:"application/pdf"})
    return URL.createObjectURL(blob)
  }

  private drawText(page:PDFPage, font, width, height, _element) {
    const element = Object.assign({}, _element);
    const value = element.value
    delete element.value
    element.y = height - _element.y - font.heightAtSize(20)
    page.drawText(value, element)
  }

  private drawField(page:PDFPage, font, width, height, _element, value) {
    const element = Object.assign({}, _element);
    element.y = height - _element.y - font.heightAtSize(20)
    page.drawText(value, element)
  }

  private drawLine(page:PDFPage, font, width, height, _element) {
    const element = Object.assign({}, _element);
    element.start.y = height - _element.start.y
    element.end.y = height - _element.end.y
    page.drawLine(element)
  }

  private drawRectangle(page:PDFPage, font, width, height, _element) {
    const element = Object.assign({}, _element);
    element.y = height - _element.y
    element.height = -_element.height
    if (element.color) {
      element.color = rgb(_element.color.r, _element.color.g, _element.color.b)
    }
    page.drawRectangle(element)
  } 
}

結果

参考

JavascriptでPDFを作成するライブラリまとめと比較
JavascriptでPDFを作成するライブラリをまとめてそれらを用途や使用方法を比較しながら紹介していきます。また、近年のフロントエンドで扱いやすい?Nodeとブラウザーで動く?型はあるか?日本語フォントは使えるのか?という観点でも比較しています。
タイトルとURLをコピーしました