1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

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

環境

  • efwの最新版4.07.006のjarを利用します。
<dependency>
    <groupId>io.github.efwgrp</groupId>
    <artifactId>efw</artifactId>
    <version>4.07.006</version>
</dependency>
  • jdkは15以上のほうを利用してください。また、最新版のNahsornをダウンロードしてください。
<dependency>
    <groupId>org.openjdk.nashorn</groupId>
    <artifactId>nashorn-core</artifactId>
    <version>15.4</version>
</dependency>

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に表示するつもりです。

<%@ 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を作成します。

var global={};
global.fire=function(){
	load(_eventfolder+"/pdf-lib.min.js");
	efw.register("PDFLib");
}

イベントjsのtest.jsファイルを作成します。この例は、pdf-libのQuick Startの変形です。

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

日本語サンプル

「IPAexフォント」は、和文文字は固定幅、欧文文字は変動幅です。
「IPAフォント」は、欧文文字、和文文字ともに固定幅です。
「IPA Pフォント」は、欧文文字、和文文字ともに変動幅です。
golbal.jsに以下のように設定します。fontを読み込んでメモリに格納して、次回利用時少しでもスピードアップの狙いです。

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に変換してページに設定します。これで日本語を利用できます。

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

pdfmakeを使ってみましょう

pdfmakeの紹介

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

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

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

helloWorldのサンプル

pdf-libサンプルの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は、以下のようにします。

	load(file.getStorageFolder()+"/../event/pdfmake.min.js");
	load(file.getStorageFolder()+"/../event/vfs_fonts.min.js");
    efw.register("pdfMake");

イベントjsの記載は以下のようになります。紹介どおりに座標がないことは明確です。

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

日本語サンプル

global.jsにフォントをロードします。バイナリの配列からBase64に変換して、pdfMakeのvfsに設定します。この変換関数は、https://github.com/bpampuch/pdfmake のソースを参考しました。

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にフォントを利用するように設定します。

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

メモ書き

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

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

そして、今回の例は本当に業務システムに利用したい場合、いろいろ注意が必要です。

  • バッチ機能で大量PDF作成の業務なら大丈夫です。
  • WEBリアル操作の場面ならシステム構成を考える必要です。
    例えば、プールの仕組みを利用したり、同期の仕組みで同時操作数を1に制限したりなど工夫する必要です。

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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?