Apache：PDFBoxを使って透かしを入れる

# Apache：PDFBox

javaで一般的なPDF編集ツールというとiTextかと思うが、（商用目的で利用してソースを公開しない場合）iTextの場合ライセンス購入が必要なため、導入においてハードルがある。

初期導入時点であれば（金銭面で）話も通しやすいかもしれないが、既存システムへの導入となると困ることがある。。

ライセンスフリーでPDFの編集を行えるツールとして検討したところ、ApacheからPDFBoxなるものが提供されているとのことで、その実装についてメモ。



## 導入

以下のサイトからダウンロード可能。

https://pdfbox.apache.org/

（Apacheのサイトです）

記事作成時点の最新は2.0.7だが、実装時は2.0.7リリース前であったため、サンプル実装は2.0.6で行っている。



ダウンロードしたjarをプロジェクトに取り込んで導入完了。



## サンプル実装

実装環境は、windows10端末、java1.8。

ちょっとググれば既に日本語で実装サンプルが上がっている。



```java:PDFBoxを使って透かしを入れるサンプル
	public static void main(String[] args) {
		File pdfPath = new File("…/sample/sample.pdf");//元のPDF
		File outPath = new File("…/sample/makepdf.pdf");//編集後のPDF
		String tmpPath = "…/sample/temp.pdf";//一時ファイル

		// 背景として透かす用のPDFを作成する
		PDDocument tmp = new PDDocument();
		PDRectangle rectangle = PDRectangle.A4;//A4サイズ
		PDPage tmpPage = new PDPage(rectangle);
		tmp.addPage(tmpPage);

		File file = new File("/Windows/Fonts/msgothic.ttc");//マルチバイトを扱う場合フォントファイルを読み込む必要がある
		try (TrueTypeCollection collection = new TrueTypeCollection(file)) {
			PDPageContentStream cont = new PDPageContentStream(tmp, tmpPage);
			PDFont font = PDType0Font.load(tmp, collection.getFontByName("MS-Gothic"), true);// MS ゴシックを指定
			float fontSize = 50;// 出力する文字の大きさを指定
			//float size = font.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * fontSize;// 指定フォントにおける1文字の高さを取得
			//cont.transform(new Matrix(1, 0, 0, 1, 20, 100));// 出力領域の位置を指定（Matrix(前半4つ「線形変換を表す２行２列の行列の 00, 01, 10, 11成分に対応します」だそな。。。後半2つ「 X軸、Y軸方向への平行移動の成分」)）
	        PDExtendedGraphicsState state = new PDExtendedGraphicsState();
	        state.setNonStrokingAlphaConstant(0.2f);// 透過率：1は無透過、0は完全に透過
	        cont.stGraphicsStateParameters(state);
	        cont.setNonStrokingColor(255, 0, 0);//色の指定：RGBで行える
			cont.beginText();//出力文字の指定を開始する
			cont.setTextMatrix(Matrix.getRotateInstance(0.7, (rectangle.getWidth()/4), (rectangle.getHeight()/6)*2));//出力領域の位置を指定(arg0:傾き、arg1:X軸の位置、arg2:Y軸の位置)
			cont.setFont(font, fontSize);//フォントを設定
			cont.newLine();//新規行を生成
			cont.showText("これはテストです。");// 出力文字を指定
			cont.endText();//出力文字の指定を終了する
			cont.close();
			tmp.save(tmpPath);//PDFを出力
		} catch (IOException e1) {
			// 必要があれば例外処理
			e1.printStackTrace();
		} finally {
			try {
				tmp.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		HashMap<Integer, String> overlayGuide = new HashMap<Integer, String>();// どのページにどのドキュメントを差し込むかの指定を保持するMAP

		try (PDDocument doc = PDDocument.load(pdfPath)) {//元のPDFを読み込む
			for (int i = 0; i < doc.getPages().getCount(); i++) {//PDFのページ数繰り返す
				overlayGuide.put(i + 1, tmpPath);//1オリジンでどのページにどのドキュメントを重ねるかを指定
			}
			Overlay overlay = new Overlay();
			overlay.setInputPDF(doc);//元のPDFのPDDocumentを指定
			overlay.setOverlayPosition(Overlay.Position.FOREGROUND);//差し込むドキュメントの位置を指定（前面or背面）
			overlay.overlay(overlayGuide);// MAPの設定に従い元のPDFにドキュメントを差し込む
			//PDFファイルを出力
			doc.save(outPath);// 加工後のPDFを出力する
			overlay.close();
			doc.close();
		} catch (InvalidPasswordException e) {
			// 必要があれば例外処理
			e.printStackTrace();
		} catch (IOException e) {
			// 必要があれば例外処理
			e.printStackTrace();
		} finally {
			// 一時ファイルを削除する
			File fl = new File(tmpPath);
			fl.deleteOnExit();
		}
	}
```

＃コメントアウト行は個人的なメモのため、読み飛ばして頂きたい。。



実行すると、社外秘とかにありがちな斜めになった文字が元PDFの全ページに重ねられて表示されます。



## 実装内容について簡単に



実装内容について簡単に説明。

透かし用の文字を記入したPDFを作成。

このPDFを透かしを反映したいPDFの全ページに重ねるようにして、編集後のPDFを出力している。

このページを重ねる役割を担っているのが、Overlayオブジェクト。

どのページにどのドキュメントを重ねるかを指定するのはMAPで行う。

このため、あるページでは透かしを入れる、あるページでは入れないなどの出しわけが可能。



```java:Overlayオブジェクトの呼び出し
	Overlay overlay = new Overlay();
	overlay.setInputPDF(doc);//元のPDFのPDDocumentを指定
	overlay.setOverlayPosition(Overlay.Position.FOREGROUND);//差し込むドキュメントの位置を指定（前面or背面）
	overlay.overlay(overlayGuide);// MAPの設定に従い元のPDFにドキュメントを差し込む
```



透かし用PDFの透かし設定は、PDExtendedGraphicsStateオブジェクトのsetNonStrokingAlphaConstant()で行っている。

引数はフロート値で、1は無透過、0は完全に透過なので、その間の数値で透過率を指定する。

```java:透かし設定
	PDPageContentStream cont = new PDPageContentStream(tmp, tmpPage);
	PDExtendedGraphicsState state = new PDExtendedGraphicsState();
	state.setNonStrokingAlphaConstant(0.2f);// 透過率：1は無透過、0は完全に透過
	cont.setGraphicsStateParameters(state);
```



その他、理解できた範囲でコメントをつけているので、参考にして頂ければ幸い。

別の実装方法もあるかと思うのでお試し下さい。。。