【make it easy】pdf-libとpdfmakeをefwに使ってみる

NashornはECMAScript 5.1を100%サポートします。だが、ECMAScript 2015およびその後のバージョンは100%サポートではありません。Efwは、Nashorn拡張となる[nashorn-ext-for-es6](https://github.com/efwGrp/nashorn-ext-for-es6)を取り込んで、最新のECMAScriptに近づいて、人気になるjavaScriptのpdf作成ツールをフレーワークに利用できるようにしました。今回の記事で紹介させていただきます。

# 環境
- efwの最新版4.07.006のjarを利用します。
```xml
<dependency>
    <groupId>io.github.efwgrp</groupId>
    <artifactId>efw</artifactId>
    <version>4.07.006</version>
</dependency>
```
- jdkは15以上のほうを利用してください。また、最新版のNahsornをダウンロードしてください。
```xml
<dependency>
    <groupId>org.openjdk.nashorn</groupId>
    <artifactId>nashorn-core</artifactId>
    <version>15.4</version>
</dependency>
```
- tomcatの7,8,9であれば、どちらのバージョンも大丈夫です。

- pdfのライブラリをダウンロードします。
pdf-lib.min.js：[https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js](https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js)
fontkit.umd.min.js：[https://unpkg.com/@pdf-lib/fontkit@1.1.1/dist/fontkit.umd.min.js](https://unpkg.com/@pdf-lib/fontkit@1.1.1/dist/fontkit.umd.min.js)
pdfmake.min.js：[https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js](https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js)
vfs_fonts.min.js：[https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.min.js](https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.min.js)
- 日本語フォントをダウンロードします。
IPAとIPAXフォント：https://moji.or.jp/ipafont/ipa00303/ と https://moji.or.jp/ipafont/ipaex00401/

# pdf-libを使ってみましょう
## pdf-libの紹介
＜＜本節は、https://pdf-lib.js.org/ の文言の翻訳です。＞＞
PDFファイルを新規ドキュメントを作成するか、既存のPDFドキュメントを変更します。テキスト、画像、ベクトルグラフィックを描画します。自分のフォントを埋め込みます。他のPDFからページを埋め込み、描画することもできます。

TypeScriptで記述され、ローカル依存性のない純粋なJavaScriptにコンパイルされます。ブラウザ、ノード、Deno、さらにはReact Nativeなど、JavaScriptの実行時に適しています。

ページを追加、挿入、削除します。個々のPDFを個々のPDFに分割します。または、複数のPDFを1つのドキュメントに統合します。

新しいフォームを作成するか、入力して既存のフィールドを読み込みます。チェックボックス、ボタン、ラジオグループ、ドロップダウンリスト、オプションリスト、テキストフィールドがサポートされています。

## helloWorldのサンプル
以下はjspです。buttonを押して、pdfをiframeに表示するつもりです。
```jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="efw" uri="efw" %> 
<!DOCTYPE HTML>
<HTML>
<HEAD>
	<META HTTP-EQUIV="CONTENT-TYPE"CONTENT="TEXT/HTML;CHARSET=UTF-8">       
	<TITLE>efw Output Test</TITLE>
	<!-- Efwクライアントの取り込み-->
	<efw:Client lang="jp"/>
</HEAD>
<BODY>
<button onclick="Efw('test');">テスト</button><br>
<iframe id="pdf" src="" style="width:600px;height:500px"></iframe>
</BODY>
</HTML>
```
アプリ起動時javaScriptライブラリをロードするためglobal.jsを作成します。
```javascript
var global={};
global.fire=function(){
	load(_eventfolder+"/pdf-lib.min.js");
	efw.register("PDFLib");
}
```
イベントjsのtest.jsファイルを作成します。この例は、pdf-libのQuick Startの変形です。
```javascript
var test={};
test.paramsFormat={};
test.fire=function(params){
	var pdfDataUri="";
	var pdfDoc=await(PDFLib.PDFDocument.create());
	var page = pdfDoc.addPage([350, 400]);
	page.moveTo(110, 200);
	page.drawText('Hello World!');
	var pdfDataUri=await(pdfDoc.saveAsBase64({ dataUri: true }));
	return new Result().eval("$('#pdf')[0].src='" +pdfDataUri +"'");
}
```
実行結果は以下のどおりです。なにもないからスピードが速いです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476221/bda71339-1b2c-fb70-6a8a-c379971f174a.png)

## 日本語サンプル
「IPAexフォント」は、和文文字は固定幅、欧文文字は変動幅です。
「IPAフォント」は、欧文文字、和文文字ともに固定幅です。
「IPA Pフォント」は、欧文文字、和文文字ともに変動幅です。
golbal.jsに以下のように設定します。fontを読み込んでメモリに格納して、次回利用時少しでもスピードアップの狙いです。
```javascript
var global={};
var fontBytes={};
global.fire=function(){
	load(_eventfolder+"/pdf-lib.min.js");
	load(_eventfolder+"/fontkit.umd.min.js");
	efw.register("PDFLib");
	efw.register("fontkit");
	efw.register("fontBytes");
	
	function byteToUint8Array(byteArray) {
		var uint8Array = new Uint8Array(byteArray.length);
		uint8Array.set(Java.from(byteArray));
		return uint8Array;
	}
	fontBytes["IPAexゴシック"] = byteToUint8Array(file.readAllBytes("font/ipaexg00401.ttf"));
	//fontBytes["IPAex明朝"] = byteToUint8Array(file.readAllBytes("font/ipaexm00401.ttf"));
	//fontBytes["IPAゴシック"] = byteToUint8Array(file.readAllBytes("font/ipag00303.ttf"));
	//fontBytes["IPA明朝"] = byteToUint8Array(file.readAllBytes("font/ipam00303.ttf"));
	//fontBytes["IPA Pゴシック"] = byteToUint8Array(file.readAllBytes("font/ipagp00303.ttf"));
	//fontBytes["IPA P明朝"] = byteToUint8Array(file.readAllBytes("font/ipamp00303.ttf"));
}
```
イベントjsには日本語フォントのバイナリからpdfFontに変換してページに設定します。これで日本語を利用できます。
```javascript
var test={};
test.paramsFormat={
    target:null
};
test.fire=function(params){
	var pdfDoc=await(PDFLib.PDFDocument.create());
	pdfDoc.registerFontkit(fontkit);
	var myfont = await(pdfDoc.embedFont(fontBytes["IPAexゴシック"],{ subset: true }));
	var page = pdfDoc.addPage([500, 400]);
	page.setFont(myfont);
	page.moveTo(110, 200);
	page.drawText('Hello World!こんにちは東京');
	var pdfDataUri=await(pdfDoc.saveAsBase64({ dataUri: true }));
	return new Result().eval("$('#pdf')[0].src='" +pdfDataUri +"'");
}
```
確かにかなと漢字は同じ幅で、英字の幅はばらばらですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476221/c82bde0e-d265-b0b7-8663-f8d75d384c8f.png)

# pdfmakeを使ってみましょう

## pdfmakeの紹介
＜＜本節は http://pdfmake.org/ の文言の翻訳です。＞＞
PDFをブラウザに直接印刷するか、ノードJSバックエンドに委任します。どちらの場合も同じドキュメント定義を使用します。

手動x、y計算を忘れます。文書構造を宣言し、pdfmakeに残りの操作を完了させます。

段落、列、リスト、表、キャンバスなど…を使用して自分のスタイルを宣言し、カスタムフォントを使用してDSLを構築し、フレームを拡張します。

## helloWorldのサンプル
pdf-libサンプルのjspと同じです。
```jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="efw" uri="efw" %> 
<!DOCTYPE HTML>
<HTML>
<HEAD>
	<META HTTP-EQUIV="CONTENT-TYPE"CONTENT="TEXT/HTML;CHARSET=UTF-8">       
	<TITLE>efw Output Test</TITLE>
	<!-- Efwクライアントの取り込み-->
	<efw:Client lang="jp"/>
</HEAD>
<BODY>
<button onclick="Efw('test');">テスト</button><br>
<iframe id="pdf" src="" style="width:600px;height:500px"></iframe>
</BODY>
</HTML>
```
global.jsは、以下のようにします。
```javascript
	load(file.getStorageFolder()+"/../event/pdfmake.min.js");
	load(file.getStorageFolder()+"/../event/vfs_fonts.min.js");
    efw.register("pdfMake");
```
イベントjsの記載は以下のようになります。紹介どおりに座標がないことは明確です。
```javascript
var test={};
test.paramsFormat={};
test.fire=function(params){
	var docDefinition = {
        content: [
            { text:"hello world!", fontSize: 20},
        ],
        
	};
	var pdfDataUri;
	var doc=pdfMake.createPdf(docDefinition);
	pdfDataUri=await(
		new Promise(function(resolve,reject){
			doc.getDataUrl(function(data){
				resolve(data);
			});
		})
	);
	return new Result().eval("$('#pdf')[0].src='" +pdfDataUri +"'");
}
```
作成したpdfはいきなりA4ページのサイズになって、ちょっと親しい感じですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476221/c0a5542b-5872-2511-9882-89cbf15727c0.png)

## 日本語サンプル
global.jsにフォントをロードします。バイナリの配列からBase64に変換して、pdfMakeのvfsに設定します。この変換関数は、https://github.com/bpampuch/pdfmake のソースを参考しました。
```javascript
var global={};
global.fire=function(){
	load(file.getStorageFolder()+"/../event/pdfmake.min.js");
	load(file.getStorageFolder()+"/../event/vfs_fonts.min.js");
	efw.register("pdfMake");
	pdfMake.vfs["ipaexg00401.ttf"]=encodeBytestoBase64forPdfMake(file.readAllBytes("font/ipaexg00401.ttf"));//IPAexゴシック 
//	pdfMake.vfs["ipaexm00401.ttf"]=encodeBytestoBase64forPdfMake(file.readAllBytes("font/ipaexm00401.ttf"));//IPAex明朝
//	pdfMake.vfs["ipag00303.ttf"]=encodeBytestoBase64forPdfMake(file.readAllBytes("font/ipag00303.ttf"));//IPAゴシック
//	pdfMake.vfs["ipam00303.ttf"]=encodeBytestoBase64forPdfMake(file.readAllBytes("font/ipam00303.ttf"));//IPA明朝
//	pdfMake.vfs["ipagp00303.ttf"]=encodeBytestoBase64forPdfMake(file.readAllBytes("font/ipagp00303.ttf"));//IPA Pゴシック
//	pdfMake.vfs["ipamp00303.ttf"]=encodeBytestoBase64forPdfMake(file.readAllBytes("font/ipamp00303.ttf"));//IPA P明朝
}

function encodeBytestoBase64forPdfMake(aBytes) {
	//Byte配列をUint8Arrayに変換する
	var temp = new Uint8Array(aBytes.length);
	temp.set(Java.from(aBytes));
	aBytes=temp;
	//base64の文字列を作成する
	var nMod3 = 2, sB64Enc = '';
	for (var nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) {
		nMod3 = nIdx % 3;
		if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) { sB64Enc += '\r\n'; }
			nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24);
		if (nMod3 === 2 || aBytes.length - nIdx === 1) {
			sB64Enc += String.fromCharCode(uint6ToB64(nUint24 >>> 18 & 63), uint6ToB64(nUint24 >>> 12 & 63), uint6ToB64(nUint24 >>> 6 & 63), uint6ToB64(nUint24 & 63));
			nUint24 = 0;
		}
	}
	return sB64Enc.substr(0, sB64Enc.length - 2 + nMod3) + (nMod3 === 2 ? '' : nMod3 === 1 ? '=' : '==');
	//------------
	function uint6ToB64 (nUint6) {
		return nUint6 < 26 ?
		  nUint6 + 65
		: nUint6 < 52 ?
		  nUint6 + 71
		: nUint6 < 62 ?
		  nUint6 - 4
		: nUint6 === 62 ?
		  43
		: nUint6 === 63 ?
		  47
		:
		  65;
	}
}
```
イベントjsにフォントを利用するように設定します。
```javascript
var test={};
test.paramsFormat={};
test.fire=function(params){
	var docDefinition = {
		content: [
			{ text:"hello world!こんにちは東京", fontSize: 20 },
		],
		defaultStyle: {
			font:"IPAexゴシック"
		}
	};
	var fonts={
		"IPAexゴシック":{
			normal:"ipaexg00401.ttf",
			bold:"ipaexg00401.ttf",
			italics:"ipaexg00401.ttf",
			bolditalics:"ipaexg00401.ttf",
		},
	}
	var pdfDataUri;
	var doc=pdfMake.createPdf(docDefinition,null,fonts);
	pdfDataUri=await(
		new Promise(function(resolve,reject){
			doc.getDataUrl(function(data){
				resolve(data);
			});
		})
	);
	return new Result().eval("$('#pdf')[0].src='" +pdfDataUri +"'");
}
```
結果は以下のどおりです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476221/defc1142-be3d-2558-31dc-fd40eb3bc518.png)

# メモ書き
ブラウザーのjavaScriptは、ほぼシングルスレッドです。せいぜいsetTimeoutとsetIntervalで軽いマルチスレッドぽくなって、WEBサーバのようなものではありません。Node.jsについて、詳しくないですが、シングルスレッドとよく言われます。NashornエンジンのjavaScriptなら、Javaの特徴を取り込めば立派なマルチスレッドを実現できます。※Efwはその実装の一つです。

pdf-libとpdfmakeは基本的にブラウザー用の感じが強いです。マルチスセーフはあまり重要視していません。上記の例の場合、二つのブラウザーから同時操作するとたまにエラーになります。

そして、今回の例は本当に業務システムに利用したい場合、いろいろ注意が必要です。
- バッチ機能で大量PDF作成の業務なら大丈夫です。
- WEBリアル操作の場面ならシステム構成を考える必要です。
例えば、プールの仕組みを利用したり、同期の仕組みで同時操作数を１に制限したりなど工夫する必要です。

上記サンプルの例は以下のリンクからダウンロードできます。

https://github.com/efwGrp/qittaSamples