PHP
SVG
tcPDF

TCPDFのsvgのtext,tspanに関するbugifx

 先日書いたようにTCPDFのsvg処理のなかでtext,tspanの扱いに問題があったので簡易的に下記について修正してみました。
 これとdefs/useに関するbugfixを合わせることでTCPDFでまともに処理されるsvgが若干増えると思います。

 なお、font-familyの指定で日本語を表示するために「kozminproregular」を指定すると末尾の「regular」が削除されてしまうため、「fonts/kozminproregular.php」を「fonts/kozminpro.php」にリネームしておくとよさそうです。
 上記以外についても欧文フォント以外については正しく処理されてない部分がありそうなので(文字列の幅を取得する関数は正しくない値が返ってきていたようでした)、日本語はあまり使わない方がよいかもしれません。

tcpdf-text.patch
--- tcpdf_min/tcpdf.php Mon Jul 11 01:11:09 2016
+++ tcpdf/tcpdf.php Wed Jul 26 11:07:05 2017
@@ -24239,6 +24239,7 @@
                break;
            }
            // text
+           case 'tspan_x':
            case 'text':
            case 'tspan': {
                if (isset($this->svgtextmode['text-anchor']) AND !empty($this->svgtext)) {
@@ -24249,6 +24250,15 @@
                if ($invisible) {
                    break;
                }
+
+               if ($name == 'tspan') {
+                   $this->endSVGElementHandler($parser, 'tspan_x');
+               }
+               elseif ($name == 'tspan_x') {
+                   $name = 'tspan';
+                   $svgstyle = $attribs['-prev_style'];
+               }
+
                array_push($this->svgstyles, $svgstyle);
                if (isset($attribs['x'])) {
                    $x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
@@ -24296,6 +24306,9 @@
                $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
                $this->x = $x;
                $this->y = $y;
+               if ($name == 'text') {
+                   $this->startSVGElementHandler($parser, 'tspan_x', array('-prev_style' => $svgstyle));
+               }
                break;
            }
            // use
@@ -24400,6 +24413,7 @@
                $this->StopTransform();
                break;
            }
+           case 'tspan_x':
            case 'text':
            case 'tspan': {
                if ($this->svgtextmode['invisible']) {
@@ -24407,9 +24421,14 @@
                    // If the 'visibility' property is set to hidden on a 'tspan', 'tref' or 'altGlyph' element, then the text is invisible but still takes up space in text layout calculations.
                    break;
                }
+               if ($name == 'text') {
+                   $this->endSVGElementHandler($parser, 'tspan_x');
+               }
                // print text
                $text = $this->svgtext;
                //$text = $this->stringTrim($text);
+
+               if ($text != '') {
                $textlen = $this->GetStringWidth($text);
                if ($this->svgtextmode['text-anchor'] != 'start') {
                    // check if string is RTL text
@@ -24445,10 +24464,15 @@
                // restore previous rendering mode
                $this->textrendermode = $textrendermode;
                $this->textstrokewidth = $textstrokewidth;
+               }
+
                $this->svgtext = '';
                $this->StopTransform();
                if (!$this->svgdefsmode) {
                    array_pop($this->svgstyles);
+               }
+               if ($name == 'tspan') {
+                   $this->startSVGElementHandler($parser, 'tspan_x', array('-prev_style' => end($this->svgstyles)));
                }
                break;
            }

変更内容について

 既存の状態では下記の問題があるようでした:

  • 文字列はタグを閉じるときにのみ直前のcontentを出力してるが、tspanの開始時にもそこまでにあるcontentを出力する必要がある
  • tspanタグを閉じた時は状態を前に戻す必要がある

 この対策として、下記のような修正を加えています:

  • tspan開始時には閉じタグ(tspan_x)の処理をする
  • text,tspan,tspan_x開始時には、その時の$attribsおよび$svgstyleを保存する
    • 座標については無視する調整をしている
  • tspan_x開始時は$svgstyleを保存していたものに変更する
    • 以降の処理はtspanと同様
  • tspanを閉じた時は、保存した$attribsを利用しtspan_xを開始する

(2017-07-25追記)

  • textのアトリビュートがtext終了まで適用されるように修正
    • text開始処理後にtspan_x開始を追加
    • text終了時にtspan_x終了を追加
  • $attribsで実際に利用している部分は座標系のみだったので、スタックに保存しないように修正
    • 空の$attribsに直前の$svgstyleを渡すようにしている

 これで修正は最小限でそこそこ正しく処理される状態になったと思います。

(2017-07-26追記)

  • text出力で文字列が空の時は出力処理をスキップするように修正