Add-On

次の2つを入れて、LibraOfficeを使えるようにする。

heroku-buildpack-apt

heroku buildpacks:add --index 1 heroku-community/apt

heroku-buildpack-libreoffice-for-heroku-18

heroku buildpacks:add --index 2 https://github.com/BlueTeaLondon/heroku-buildpack-libreoffice-for-heroku-18.git

Aptfile

ルートにAptfileを設置し、次を書き込む。

libsm6
libice6
libxinerama1
libdbus-glib-1-2
libharfbuzz0b
libharfbuzz-icu0
libx11-xcb1
libxcb1

フォント

日本語フォントの使用

ルートに.fontsディレクトリを設置し、その中にIPAexフォントのipaexg.ttfとipaexm.ttfを格納する。

フォントの変換

いろいろな明朝とゴシックがIPAexフォントで開かれるようにする。
このため、ルートに.config/fontconfig/fonts.confファイルを設置し、次のようなエイリアスを必要なだけ書き込む。

<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<alias>
<family>MS ゴシック</family>
<prefer>
<family>IPAexゴシック</family>
</prefer>
</alias>
</fontconfig>

スクリプト

次をsoffice.pyとしておく。

import os
import subprocess
import shutil
import glob

default_user_profile = os.environ['HOME'] + "/.config/libreoffice/4/user"

class PdfConverter:
def __init__(self,
file_in:str,
file_out:str,
timeout_sec:int=15,
user_profile:str=None):
self.file_in = file_in
self.file_out = file_out
self.timeout_sec = timeout_sec
self.user_profile = user_profile
if self.user_profile:
if not os.path.exists(self.user_profile):
shutil.copytree(default_user_profile, self.user_profile)

def __enter__(self):
return self

def __exit__(self):
self.stop()

def start(self):
args = [
'soffice',
'--headless',
'--convert-to',
'pdf',
self.file_in,
'--outdir',
self.file_out,
]
if self.user_profile:
args.append('-env:UserInstallation=file://%s' % self.user_profile)
stdout_str = ""
stderr_str = ""
rc = 0
try:
ret = subprocess.run(args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
timeout=self.timeout_sec,
check=True,
text=True)
rc = ret.returncode
stdout_str = ret.stdout
stderr_str = ret.stderr
except subprocess.CalledProcessError as cpe:
rc = -1
stdout_str = cpe.stdout
stderr_str = cpe.stderr
except subprocess.TimeoutExpired as te:
rc = -2
stdout_str = te.stdout
stderr_str = te.stderr
finally:
if stdout_str:
print(stdout_str)
if stderr_str:
print(stderr_str)
self.stop()
return rc

def stop(self):
tmp_files = self.file_out + '/*.tmp'
for f in glob.glob(tmp_files):
os.remove(f)
print('soffice finished')

if __name__ == "__main__":
pc = PdfConverter('test1.xlsx', './output')
pc.start()

これを例えばflaskで

from soffice import *
from flask import Flask, request, send_file

app = Flask(__name__)

@app.route("/hoge/")
def hoge():
pc = PdfConverter('test1.xlsx', './output')
pc.start()
output_file_path = './output/test1.pdf'
return send_file(output_file_path, as_attachment=True, download_name='test.pdf')

とすればよい