TCPDFで縦書きテキストをフォント埋め込みに対応させる

TCPDFで縦書きを実現するには、フォント設定ファイルの書き換えで実現するのが一般的です。
※ IPA明朝の場合

$type = 'cidfont0';
$dw = 1000;
$enc = 'UniJIS-UFT16-V';
$diff='';
//$file='ipam.z';
//$ctg='ipam.ctg.z';
$cidinfo=array('Registry'=>'Adobe', 'Ordering'=>'Japan1','Supplement'=>5);
include(dirname(__FILE__).'/uni2cid_aj16.php');

この方法ではAdobe ReaderまたはAdobe Acrobatでは、PostScriptで印刷ができません。
「文章を印刷できません。」
「印刷するページが選択されていません。」
と表示されて出力できません。
画像として印刷することはできますが、今一つな感じです。
Illustratorに読み込ませればアウトライン化されるので印刷に使用できるようになりますが、
ページが複数ある場合は現実的ではありません。

ちなみに、「Foxit」ではPostScriptのままで出力できるので、こちらを使用するのも手です。

要は、TCPDFで縦書きテキストを生成する際に指定するCIDフォントは非埋め込みであり、
これがPostScriptで印刷する場合に問題となるというわけです。
では、埋め込んで縦書きを実現してみる方法を探してみましょう。

まず、TCPDFの標準エンコードは「Identity-H」です。
この形式でないとフォントを埋め込めないというか、フォント埋め込み用の独自エンコードかもしれません。
PostScriptを弄っている人なら、末尾の「-H」が気になりますね。
これを変更するには、本体の「tcpdf.php」ファイルを書き換える必要があります。
メソッド「AddFont」に

} elseif ($type == 'TrueTypeUnicode') {
$enc = 'Identity-H';

というコードがあるので、「’Identity-V’」に書き換えてみましょう。
すると、なんということでしょう。
テキストが縦書きになりました。
もちろん、フォント設定ファイルは何も弄りません。
ただし、フォントは埋め込みの状態にしておきます。
非埋め込みでは化けます。

しかしよく見ると括弧やハイフンなどが横書き用のままです。
CIDフォントでは「uni2cid_aj16.php」をincludeすることでマッピングを変更しているのですが、
Identityでマッピングをしているところがまだよくわかりません。
おそらく、これを調整でいれば完全な縦書きを実現できるはずです。

もし参考頂き、対応方法を見つけられましたらご一報いただければ幸いです。
また、余裕があれば調査します。

TCPDFで縦書きテキストをフォント埋め込みに対応させる」への3件のフィードバック

  1. ご参考までに私の書いた_getVertSubst()関数も付けておきます。
    汚いですが。

    diff -uprN TCPDF-master.org/include/tcpdf_fonts.php TCPDF-master/include/tcpdf_fonts.php
    — TCPDF-master.org/include/tcpdf_fonts.php 2016-06-11 02:46:02.000000000 +0900
    +++ TCPDF-master/include/tcpdf_fonts.php 2017-02-14 04:25:48.800745232 +0900
    @@ -773,6 +773,12 @@ class TCPDF_FONTS {
    if (!isset($ctg[0])) {
    $ctg[0] = 0;
    }
    + $vertSubst = self::_getVertSubst($font, $table);
    + foreach ($ctg as $k => $v){
    + if (isset($vertSubst[$v])){
    + $ctg[$k] = $vertSubst[$v];
    + }
    + }
    // get xHeight (height of x)
    $offset = ($table[‘glyf’][‘offset’] + $indexToLoc[$ctg[120]] + 4);
    $yMin = TCPDF_STATIC::_getFWORD($font, $offset);
    @@ -1248,6 +1254,18 @@ class TCPDF_FONTS {
    }
    }
    }
    +
    + $vertSubst = self::_getVertSubst($font, $table);
    + $subsetglyphs_vert = array();
    + foreach($subsetglyphs as $g => $v){
    + if (isset($vertSubst[$g])){
    + $subsetglyphs_vert[$vertSubst[$g]] = $v;
    + }else{
    + $subsetglyphs_vert[$g] = $v;
    + }
    + }
    + $subsetglyphs = $subsetglyphs_vert;
    +
    // include all parts of composite glyphs
    $new_sga = $subsetglyphs;
    while (!empty($new_sga)) {
    @@ -2641,6 +2659,168 @@ class TCPDF_FONTS {
    }
    return $ordarray;
    }
    +
    + public static function _getVertSubst($font, $table) {
    + $vertSubst = array();
    + while (isset($table[‘GSUB’])){
    + // read GSUB header
    + $offset = $table[‘GSUB’][‘offset’];
    + $gsub[‘version’] = TCPDF_STATIC::_getULONG($font, $offset);
    + $offset += 4;
    + if ($gsub[‘version’] != 0x00010000){ // check version
    + unset($gsub);
    + break;
    + }
    + $scriptOffset = $table[‘GSUB’][‘offset’] + TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + $featureOffset = $table[‘GSUB’][‘offset’] + TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + $lookupOffset = $table[‘GSUB’][‘offset’] + TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    +
    + // get feature list
    + $offset = $featureOffset;
    + $count = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + $gsub[‘FeatureList’] = array();
    + for ($i = 0; $i < $count; $i++){
    + $tag = substr($font, $offset, 4);
    + $offset += 4;
    + $gsub['FeatureList'][$tag] = array();
    + $gsub['FeatureList'][$tag]['offset'] = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + }
    +
    + // get feature 'vert'
    + if (isset($gsub['FeatureList']['vert'])){
    + $offset = $featureOffset + $gsub['FeatureList']['vert']['offset'];
    + $gsub['FeatureList']['vert']['FeatureParams'] = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + $gsub['FeatureList']['vert']['LookupCount'] = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + $gsub['FeatureList']['vert']['LookupListIndex'] = array();
    + for ($i = 0; $i < $gsub['FeatureList']['vert']['LookupCount']; $i++){
    + $gsub['FeatureList']['vert']['LookupListIndex'][$i] = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + }
    + }else{
    + break;
    + }
    +
    + // get lookup list
    + $offset = $lookupOffset;
    + $count = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + $gsub['LookupList'] = array();
    + for ($i = 0; $i < $count; $i++){
    + $gsub['LookupList'][$i] = array();
    + $gsub['LookupList'][$i]['offset'] = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + }
    + for ($i = 0; $i < $count; $i++){
    + $offset = $lookupOffset + $gsub['LookupList'][$i]['offset'];
    + $gsub['LookupList'][$i]['LookupType'] = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + $gsub['LookupList'][$i]['LookupFlag'] = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + $gsub['LookupList'][$i]['SubTableCount'] = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + $gsub['LookupList'][$i]['SubTable'] = array();
    + for ($j = 0; $j < $gsub['LookupList'][$i]['SubTableCount']; $j++){
    + $gsub['LookupList'][$i]['SubTable'][$j] = array();
    + $gsub['LookupList'][$i]['SubTable'][$j]['offset'] = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + }
    + }
    +
    + // get 'vert' lookup and construct vertSubst array
    + for ($i = 0; $i < $gsub['FeatureList']['vert']['LookupCount']; $i++){
    + $index = $gsub['FeatureList']['vert']['LookupListIndex'][$i];
    + $lookup = $gsub['LookupList'][$index];
    + for ($j = 0; $j < $lookup['SubTableCount']; $j++){
    + $offset = $lookupOffset + $lookup['offset'] + $lookup['SubTable'][$j]['offset'];
    + $lookup['SubTable'][$j]['SubstFormat'] = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + $lookup['SubTable'][$j]['Coverage'] = array();
    + $lookup['SubTable'][$j]['Coverage']['offset'] = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + switch ($lookup['SubTable'][$j]['SubstFormat']) {
    + case 1: { // Single Substitution Format 1
    + $lookup['SubTable'][$j]['DeltaGlyphID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + break;
    + }
    + case 2: { // Single Substitution Format 2
    + $count = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + $lookup['SubTable'][$j]['Substitute'] = array();
    + for ($k = 0; $k < $count; $k++){
    + $lookup['SubTable'][$j]['Substitute'][$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + }
    + break;
    + }
    + }
    +
    + $offset = $lookupOffset + $lookup['offset'] + $lookup['SubTable'][$j]['offset'] + $lookup['SubTable'][$j]['Coverage']['offset'];
    + $lookup['SubTable'][$j]['Coverage'] = array();
    + $lookup['SubTable'][$j]['Coverage']['CoverageFormat'] = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + $count = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + switch ($lookup['SubTable'][$j]['Coverage']['CoverageFormat']) {
    + case 1: { // Coverage Format 1
    + $lookup['SubTable'][$j]['Coverage']['GlyphArray'] = array();
    + for ($k = 0; $k < $count; $k++){
    + $glyph = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $lookup['SubTable'][$j]['Coverage']['GlyphArray'][$k] = $glyph;
    + $offset += 2;
    + switch ($lookup['SubTable'][$j]['SubstFormat']) {
    + case 1: { // Single Substitution Format 1
    + $vertSubst[$glyph] = $glyph + $lookup['SubTable'][$j]['DeltaGlyphID'];
    + break;
    + }
    + case 2: { // Single Substitution Format 2
    + $vertSubst[$glyph] = $lookup['SubTable'][$j]['Substitute'][$k];
    + break;
    + }
    + }
    + }
    + break;
    + }
    + case 2: { // Coverage Format 2
    + $lookup['SubTable'][$j]['Coverage']['RangeRecord'] = array();
    + for ($k = 0; $k < $count; $k++){
    + $lookup['SubTable'][$j]['Coverage']['RangeRecord'][$k] = array();
    + $lookup['SubTable'][$j]['Coverage']['RangeRecord'][$k]['start'] = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + $lookup['SubTable'][$j]['Coverage']['RangeRecord'][$k]['end'] = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + $lookup['SubTable'][$j]['Coverage']['RangeRecord'][$k]['startCoverageIndex'] = TCPDF_STATIC::_getUSHORT($font, $offset);
    + $offset += 2;
    + for ($glyph = $lookup['SubTable'][$j]['Coverage']['RangeRecord'][$k]['start']; $glyph pdfa_mode) {
    $this->Error(‘All fonts must be embedded in PDF/A mode!’);

    • 大変参考になります。貴重な情報どうもありがとうございます。
      _getVertSubstですが、最後の方が表示されていないような。
      ぜひ全情報をお願いします。

  2. なぜか2つ目のコメントだけ表示されたので、1つ目を再送します。

    記事を参考にさせていただき、やっつけながらも縦書き用グリフを埋め込めるようになりました。本格的に改造される方の参考になるかもと思い、報告します(自分のブログなどないので長文失礼します)。

    縦書き用グリフを利用するには、記事にある、”Identity-H”から”Identity-V”への変更に加え、以下の3つの追加が必要でした。

    1) フォントデータから、GSUBテーブルのvertを読み、横書き用GID(グリフID)から縦書きGIDへの置換テーブルを得る処理。
    http://d.hatena.ne.jp/project_the_tower2/20100509/1273370298
    ↑この記事を参考に、_getVertSubst()という関数を書きました。

    2) tcpdf/include/tcpdf_fonts.phpのTCPDF_FONTS::addTTFfont()において、
    配列$ctg(CIDtoGIDMap)生成処理の直後に、上記置換テーブルを使ってグリフを置換する処理を加える。

    @@ -773,6 +773,12 @@ class TCPDF_FONTS {
    if (!isset($ctg[0])) {
    $ctg[0] = 0;
    }
    + $vertSubst = self::_getVertSubst($font, $table);
    + foreach ($ctg as $k => $v){
    + if (isset($vertSubst[$v])){
    + $ctg[$k] = $vertSubst[$v];
    + }
    + }
    // get xHeight (height of x)
    $offset = ($table[‘glyf’][‘offset’] + $indexToLoc[$ctg[120]] + 4);
    $yMin = TCPDF_STATIC::_getFWORD($font, $offset);

    3) tcpdf/include/tcpdf_fonts.phpのTCPDF_FONTS::_getTrueTypeFontSubset()において、$subsetglyphs(埋め込み対象グリフの配列)作成処理の直後に、上記置換テーブルを使ってグリフを置換する処理を加える

    @@ -1248,6 +1254,18 @@ class TCPDF_FONTS {
    }
    }
    }
    +
    + $vertSubst = self::_getVertSubst($font, $table);
    + $subsetglyphs_vert = array();
    + foreach($subsetglyphs as $g => $v){
    + if (isset($vertSubst[$g])){
    + $subsetglyphs_vert[$vertSubst[$g]] = $v;
    + }else{
    + $subsetglyphs_vert[$g] = $v;
    + }
    + }
    + $subsetglyphs = $subsetglyphs_vert;
    +
    // include all parts of composite glyphs
    $new_sga = $subsetglyphs;
    while (!empty($new_sga)) {

    以上の修正で、「,」,【,】,ーなどが縦書き用に回転され表示されることを確認しました。

    補足:
    2)のaddTTFfont()の処理は、初めてttfファイルを読み込んだときに1度だけ行われ、その結果は、フォント名.ctg.zというファイルとしてtcpdf/fontsディレクトリに保存されます。このファイルが、ユニコードから対応するグリフIDを決定するのに使われるようです。したがって、縦書き用の置換処理を加えると、生成されるフォント名.ctg.zファイルは縦書き専用になります。

    3)の_getTrueTypeFontSubset()の処理は文書出力の度に、使用されている文字に応じた埋め込みサブセットを生成するために行われます。

    2)でユニコードと縦書き用グリフIDが対応付けられたならば、そのまま3)の処理で縦書き用グリフが埋め込まれそうなものですが、tcpdfの場合、3)の処理でもまたフォントを読みに行ってしまうようで、ここでも置換処理が必要でした。

    原理的には3)の処理で、文書ごとに文字とグリフのマップを作り直して埋め込む方が丁寧な気がしますが、tcpdfでは、マップはttfの読み込み時に1度だけ作り、埋め込み時には不要なグリフデータを省いたフォント情報を生成するだけ、という仕組みになっていました。

    上記の修正では縦書き専用のtcpdfになってしまいますし、tcpdf/fontディレクトリのフォントデータもいったん消して縦書き用に作り直す必要があります。縦横使い分けできるようしたいところですが、引数追加など必要そうでした。どうせ本格的な縦書き対応には諸々やることがあると思われるので、ここまでのご報告とします。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です