WebアプリをJava で構築していて、Javaでダウンロードすファイルを作るなら一時ファイル
(ディスクに一旦書き出すこと)
を作らずにダウンロードするものを作るのは容易ですが、言語、実行環境が異なる処理に作らせて
一時ファイルを生成することなくダウンロードするのはそれなりの処理が必要です。
Javaからプロセスで実行する Python が作成する PDF をPythpnの処理で標準出力で出力して
Javaがその出力を受け取ったら HTTPのレスポンスとして出力ストリーム(OutputStream) に出力します。
(まるで、横流し。。。)
Java Wicket Web ページの方からサンプルを紹介します。
Javaからプロセス実行は、
yipuran-core ver4.6 からの ScriptExecutor の
runStream(Supplier<String>, Supplier<Collection<String>>
, Consumer<InputStream>, BiConsumer<String, Throwable>)
メソッドを使って
Script_exec · yipuran/yipuran-core Wiki · GitHub
行います。
AJAX のダウンロード処理として、InputStream でプロセスが出力する標準出力を受け取って
そのまま OutputStream に流してます。
String script = "python /pdf/sample_pdf.py /etc/template.pdf";
が実行する Python の処理プロセスです。
List<String> list = new ArrayList<>(); は、処理プロセスの標準入力に渡す(流す)ものです。
final AJAXDownload download = AJAXDownload.of(out->{ String script = "python /pdf/sample_pdf.py /etc/template.pdf"; // String test1~3 → list List<String> list = new ArrayList<>(); list.add(SJutil.toUnicode(text1)); list.add("\n"); list.add(SJutil.toUnicode(text2)); list.add("\n"); list.add(SJutil.toUnicode(text3)); list.add("\n"); list.add("\n"); int sts = ScriptExecutor.runStream(()->script, ()->list , inst->{ try{ byte[] b = new byte[1024]; int len; while((len=inst.read(b, 0, b.length)) >= 0){ out.write(b, 0, len); } out.flush(); out.close(); }catch(Exception ex){ throw new RuntimeException(ex); } }, (e, x)->{ logger.error(e); logger.error(x.getMessage(), x); }); logger.debug("status = " + sts); }, ()->"application/pdf", ()->"test.pdf"); /* submitボタン click で ↑ を callback */ queue(new Button("submit") .add(AjaxFormSubmitBehavior.onSubmit("click", SerialThrowableConsumer.of(t->{ download.callBackDownload(t); }, (u, x)->{ logger.error(x.getMessage(), x); }))).add(download));
実行する Python の処理 → sample_pdf.py
reportlab、pdfrw だけでなく、PyPDF3 を利用して作成した PdfFileReader でメモリ上に読み込んで
PdfFileWriter で、PDF出力するオブジェクトとして取込み、sys.stdout.buffer.write で
バイナリ標準出力します。print ではだめです。
ファイルとしては生成しないメモリ上での生成として、io.BytesIO() を使用するのが重要です
以下、折角なのでページ番号書き出しと、フォント指定などページ全体を共通に使い回すために
pagenumCanvas.py → from pagenumCanvas import PageNumCanvas
ページコンテンツを書きだす samplepage_pdf.py は、page_pdf.py のクラスを継承します
sample_pdf.py
# -*- coding: utf-8 -*- import sys import io from reportlab.platypus import SimpleDocTemplate, Paragraph from reportlab.lib.styles import getSampleStyleSheet from PyPDF3 import PdfFileWriter, PdfFileReader from pagenumCanvas import PageNumCanvas from samplepage_pdf import SamplePages import webbrowser import os # PDF 作成 def create(template, inputlist): # Samplepage インスタンス、テンプレートと入力リストを渡す。 sample = SamplePages(template, inputlist) packet = io.BytesIO() doc = SimpleDocTemplate(packet) doc.build([Paragraph("", getSampleStyleSheet()['Normal'])] , onFirstPage=sample.page , onLaterPages=sample.page , canvasmaker=PageNumCanvas) new_pdf = PdfFileReader(packet) output = PdfFileWriter() for i in range(new_pdf.getNumPages()): output.addPage(new_pdf.getPage(i)) po = io.BytesIO() output.write(po) sys.stdout.buffer.write(po.getvalue()) ######################### if __name__ == '__main__': argv = sys.argv argvlen = len(argv) if argvlen != 2: sys.stderr.write('Error: argument [1]=template file required!') exit(1) else: inlist = [] try: while True: inp = input('') if inp == '': break inlist.append(inp) except EOFError: pass # 文字フォントセット宣言 #pdfmetrics.registerFont(UnicodeCIDFont('HeiseiKakuGo-W5')) # PDF作成実行 create(argv[1], inlist) exit(0)
pagenumCanvas.py
# -*- coding: UTF-8 -*- from reportlab.pdfgen import canvas from reportlab.lib.units import mm class PageNumCanvas(canvas.Canvas): def __init__(self, *args, **kwargs): canvas.Canvas.__init__(self, *args, **kwargs) self.pages = [] def showPage(self): self.pages.append(dict(self.__dict__)) self._startPage() def save(self): page_count = len(self.pages) for page in self.pages: self.__dict__.update(page) self.draw_page_number(page_count) canvas.Canvas.showPage(self) canvas.Canvas.save(self) def draw_page_number(self, page_count): page = "Page %s of %s" % (self._pageNumber, page_count) self.setFont("Helvetica", 10) self.drawRightString(195 * mm, 272 * mm, page)
samplepage_pdf.py
# -*- coding: utf-8 -*- from page_pdf import Pdfpage from pdfrw import PdfReader from pdfrw.buildxobj import pagexobj from pdfrw.toreportlab import makerl import reportlab.rl_config class SamplePages(Pdfpage): global page_template global inlist center_w = reportlab.rl_config.defaultPageSize[0] / 2.0 center_h = reportlab.rl_config.defaultPageSize[1] / 2.0 def __init__(self, template, inputlist): super().__init__() # テンプレート読込 temp_page = PdfReader(template, decompress=False).pages SamplePages.page_template = pagexobj(temp_page[0]) # 入力リスト取得 → global inlist SamplePages.inlist = inputlist def page(self, canvas, doc): # サンプルなので無理やりループする。 for i in range(4): if i > 0: canvas.showPage() canvas.doForm(makerl(canvas, SamplePages.page_template)) canvas.setFont(super().HeiseiKakugoW5, 9) canvas.saveState() # ----- 目的の canvas 出力 ----- canvas.drawCentredString(SamplePages.center_w, SamplePages.center_h, "あいう テスト") h = 400 for item in SamplePages.inlist: canvas.drawCentredString(SamplePages.center_w, h, item.encode().decode('unicode-escape') ) h -= 20 # ------------------------------ canvas.restoreState()
page_pdf.py
# -*- coding: utf-8 -*- from reportlab.pdfbase.cidfonts import UnicodeCIDFont from reportlab.pdfbase import pdfmetrics class Pdfpage(): global HeiseiKakugoW5 def __init__(self): Pdfpage.HeiseiKakugoW5 = 'HeiseiKakuGo-W5' # 文字フォントセット宣言 pdfmetrics.registerFont(UnicodeCIDFont('HeiseiKakuGo-W5'))