Toyokumo Tech Blog

トヨクモ株式会社の開発本部のブログです。

プリントクリエイターに縦書き機能を実装しました

こんばんは。開発本部の石川です。 今回は、弊社のkintone連携製品の一つであるプリントクリエイターに「縦書き出力機能」*1*2を追加した軌跡を備忘録として書き残しておこうと思います。

プリントクリエイターとは

サイボウズ社が提供するkintoneに連携するWebサービスで、kintoneに保存されたレコード情報*3からワンクリックでPDF(帳票)を出力する機能を提供しています。

文字列の印字は勿論、画像/バーコードの印字や宛名ラベル形式での出力など、kintoneの印刷機能を強力にサポートする便利な機能をシンプルな操作で利用することができる製品です*4

しかし、日本語では当たり前に行っている「文字を縦に書く」ということに、プリントクリエイターは長年対応していませんでした。 はがきや名刺など、日本語の場合は「縦に書きたい」と思う事がある……というのは勿論把握していましたが、後述する技術的問題から長期間実装を見送っていました。

縦書きに対応したライブラリが無い問題

プリントクリエイターではバックエンドでPHP 8.1を利用しているため、PHPを用いて指定されたレイアウトのPDF生成処理を実行しています。 実際にPDFを生成する処理はTCPDFに依存してるのですが、標準機能として縦書きに対応していません。 ひとまず、縦書きに対応しているPHPライブラリが存在するか捜索をしたのですが、条件に合致するものは残念ながら見つからず*5。。

縦にも横にも文字を書く言語というのは考えてみるとかなり少数派で、需要が少ないであろうことも容易に想像できたことからこの方針は諦め、なんとかして現状の構成を大きく変更しない、実装コストの少ない方法で縦書きを実現する方法を模索する方針に切り替えました。

縦書きを実現する方法の検討

TCPDF改造案 -> ボツ

文字列の描画方向を変更する

まず最初に検討したのは「TCPDFを改造し、縦書き機能を追加してしまう」という方針でした。 PDFは仕様として縦書きをサポートしているため、「TCPDFのどこかにあるだろう、文字の描画方向に関するフラグを変更してしまえば簡単に縦書き対応出来るんじゃね?」という予想でした。

調査の結果、TCPDFは標準エンコードがIdentity-H、つまり横書きであるため、これをIdentity-Vに変更し、強制的に文字が縦に並ぶように変更できることが判明。 すぐに実践してみたのですが……これだと記号がおかしなことになってしまいます。

TCPDFの標準エンコードを強制的に変更して縦書きを実現した例

殆どの文字は横書き/縦書きで同一の文字を使用し文字を並べる方向を変えているだけですが、一部の記号*6はそうじゃないんですね。 これらの記号は横書き/縦書きで「異なる文字(異なる見た目)」なため、大抵の日本語に対応したフォントには両方の情報が組み込まれているのですが、横書き用の記号を入力してしまっているが故におかしな表示になっています。

縦書き用の記号を利用するように

記号の向きが不自然になっているのを解消するため、横書き用の記号が入力された場合は縦書き用の記号へ変換する実装をすることにしました。フォント自体は縦書きの情報を持っているはずなので、綺麗に表示されるはずだと思ったのですが……結果は正しく文字が印字されませんでした*7

縦書き用の記号に置換して表示した例

利用しているフォントはごく普通の日本語フォントデータ(=TTFファイル)で、縦書き用の情報もきちんと持っているのに理想通り表示されません。 調査したところ、TCPDFで埋め込みフォントを利用する場合の仕様が原因であることがわかりました。

TCPDFで埋め込みフォントを利用する場合、以下のような流れになります:

  1. tcpdf_addfont.phpを利用して任意のTTFファイルを読み、TCPDF独自のフォントデータを作成する
  2. PDFを描画する際にTCPDF独自フォントデータを読み込み、PDFに埋め込む

問題は1の独自フォントデータを作成する処理で、TCPDFは縦書きのことを考慮していないがために元のTTFファイルには存在した縦書きに関する情報が独自フォントデータへ引き継がれない仕様であることが判明。 そのため横書き用の文字を強制的に縦書き用の文字へ置換したとしても、上記のように正しく理想通り印字できない……というわけでした。

非埋め込みフォントで縦書きする

先程は埋め込みフォントでやろうと頑張りましたが、TCPDFの独自フォントデータは縦書きの情報を持っていないため失敗しました。 そこで、非埋め込みフォントならTCPDFの独自フォントデータは無関係になるのでうまくいくのでは?と予想し試してみました。

フォント設定ファイルを書き換える方法を参考にさせていただいたところ、一見理想通り表示されているように見えます!……が根本的な問題があります。

この方法で利用しているフォントは「非埋め込みフォント」、すなわち、「環境によってPDFを表示したときの見た目が異なってしまう」ことになります。プリンタで印刷したとき/Windows機で見たとき/Mac機で見たときetc...で表示が異なっているというのは、製品の機能として出すことはできないのです。

縦書き用TCPDF独自フォントデータを作成する

TCPDF独自フォントデータの元であるTTFファイルには縦書きの情報が存在するので、tcpdf_addfont.php等?をうまいこと改造することができれば、「縦書きの情報を持つTCPDF独自フォントデータ」を生成することが出来そうです。 これが仮にできた場合、埋め込みフォントでも縦書きに完全対応することが出来るだろうと予想し調査しました。

……が、TCPDFは縦書きのことを一切考慮していないません。 TCPDFのコードをさっと流し読みしたのですが、これを実現するには相当な改造が必要で影響範囲も図りしれないことが判明、この方針は無謀と判断し、諦めました*8

文字列を1文字ずつ分割して縦に並べる -> 採用

フォントをいじることで対応するのが難しい…と判明してしまったので、最後の手段である「文字を1文字づつ縦に並べる」を採用しました。 かなり力技ではありますが、1. 現状の実装を大きく変更しないため実装コストが低いこと 2. 細かい調整が容易であり、保守が楽であるということ などからこの方針を採用しています:

  1. 表示したい文字列を1文字づつ分割する
  2. 分割した文字列を印字する場所を、以下を考慮した上で決定する
    • 改行
    • 枠からはみ出る場合
    • 文字列を中央/右/左...…などに寄せる設定をしている場合
  3. 決定した座標に文字を印字する
    • 通常の文字の場合はそのまま印字
    • 回転が必要な記号(等)は回転して印字
    • 位置調整が必要な記号(等)は縦書きで自然な位置へ移動して印字

文字列を分割し、1文字づつ縦に並べて縦書きを実現した例

回転/位置調整はTCPDFが用意している関数を利用しています。例えばといった文字を回転させたいときは、以下のように文字が縦に並ぶように算出した座標$x$yを1文字づつ与え、Rotateを使って背景(文字ではない)を回転させてから印字することで、横書き用の記号を縦書きとして自然な形にしています。

<?php

public static function putRotateChar(TCPDF $pdf, float $x, float $y, float $fontSize, string $char): void
{
    $pdf->StartTransform();
    $pdf->Rotate(270, $x + $fontSize, $y + $fontSize);
    $pdf->Text($x, $y, $char);
    $pdf->StopTransform();
}

そして、putRotateCharと同じ要領でといった小さい仮名を印字するputLittleCharといった句読点を印字するputPunctuationCharも定義し、1文字ごとに計算した座標に印字することで、全体としては「日本語を縦書きしている」ように見せています。

<?php

public static function drawChars(TCPDF $pdf, float $x, float $y, float $fontSize, string $char): void
{
    match (true) {
        self::isPipes($char), self::isBrackets($char) => self::putRotateChar($pdf, $x, $y, $fontSize, $char),
        self::isLittleChar($char) => self::putLittleChar($pdf, $x, $y, $fontSize, $char),
        self::isPunctuation($char) => self::putPunctuationChar($pdf, $x, $y, $fontSize, $char),
        default => $pdf->Text($x, $y, $char)
    };
}

一部のフォント*9で綺麗に印字できない場合があるのですが、殆どの場合で日本語として違和感の無い(であろう)縦書きが、埋め込みフォントで出来るようになりました!

まとめ

今回は実装コストを優先した結果、文字列を1文字に分割して座標を計算するという力技な結果となりましたが、実装コストを特に考慮しなければPDFの仕様に準拠した完璧な(?)縦書きを実装することも出来るには出来そうです。 具体的には、PHPのライブラリだと十分な機能を持つものが無さそうなので、TCPDFより便利な機能を搭載した他言語のライブラリへ処理を委譲する……等といった妄想をしたりしています。


トヨクモでは一緒に働いてくれる技術が好きなエンジニアを募集しております。

採用に関する情報を公開しております。 気になった方はこちらからご応募ください。

*1:【帳票作成】プリントクリエイターで縦書き出力ができるようになりました!

*2:プリントクリエイターが待望の縦書きに対応しました!

*3:kintoneはデータベースのようなもので、自由にアプリを作成することができ、それぞれのアプリにレコードとして情報を格納することができます。参考

*4:【kintoneから直接帳票作成】プリントクリエイターとは?

*5:PHPという縛りを無くすなど、縦書き機能を搭載するものも存在するには存在します。しかし、今回は置換/導入コストが高いとして候補から除外しています。

*6:縦書き用の記号をいくつか例に上げると、括弧(︵︶︷︸︹︺︻︼︽︾︿﹀﹁﹂﹃﹄)や句読点(︐︑︒)などが有り、横書き用の記号とは向きや位置が異なるのがわかります。

*7:TCPDFでは印字できない文字が入力されると□のように表示されます。

*8:横書き -> 縦書きへ変換する際の参考情報:はいはい縦書き縦書き・・・・・ッ!?

*9:等幅フォントではない場合この方針だと破綻してしまうのですが、ごく一部の話なので仕様とすることにしました。