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>
-
tomcatの7,8,9であれば、どちらのバージョンも大丈夫です。
-
pdfのライブラリをダウンロードします。
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
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 -
日本語フォントをダウンロードします。
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に表示するつもりです。
<%@ 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 +"'");
}
実行結果は以下のどおりです。なにもないからスピードが速いです。
日本語サンプル
「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 +"'");
}
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ページのサイズになって、ちょっと親しい感じですね。
日本語サンプル
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 +"'");
}
メモ書き
ブラウザーのjavaScriptは、ほぼシングルスレッドです。せいぜいsetTimeoutとsetIntervalで軽いマルチスレッドぽくなって、WEBサーバのようなものではありません。Node.jsについて、詳しくないですが、シングルスレッドとよく言われます。NashornエンジンのjavaScriptなら、Javaの特徴を取り込めば立派なマルチスレッドを実現できます。※Efwはその実装の一つです。
pdf-libとpdfmakeは基本的にブラウザー用の感じが強いです。マルチスセーフはあまり重要視していません。上記の例の場合、二つのブラウザーから同時操作するとたまにエラーになります。
そして、今回の例は本当に業務システムに利用したい場合、いろいろ注意が必要です。
- バッチ機能で大量PDF作成の業務なら大丈夫です。
- WEBリアル操作の場面ならシステム構成を考える必要です。
例えば、プールの仕組みを利用したり、同期の仕組みで同時操作数を1に制限したりなど工夫する必要です。
上記サンプルの例は以下のリンクからダウンロードできます。