やること
- 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をiframe
のsrc
に渡すことでブラウザ上に描画するが、そのまま渡すと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とブラウザーで動く?型はあるか?日本語フォントは使えるのか?という観点でも比較しています。