Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.73% covered (success)
97.73%
172 / 176
97.67% covered (success)
97.67%
42 / 43
CRAP
0.00% covered (danger)
0.00%
0 / 1
Text
97.73% covered (success)
97.73%
172 / 176
97.67% covered (success)
97.67%
42 / 43
88
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 setString
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 setStrings
73.33% covered (success)
73.33%
11 / 15
0.00% covered (danger)
0.00%
0 / 1
6.68
 addStringWithOffset
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 getStringsWithOffset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setSize
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setFillColor
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setStrokeColor
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setStroke
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 setRotation
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setCharWrap
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setLeading
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setAlignment
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setWrap
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setTextStream
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 escape
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 getString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getStrings
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFillColor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getStrokeColor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getStroke
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRotation
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCharWrap
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNumberOfWrappedLines
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLeading
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAlignment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getWrap
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTextStream
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasStrings
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasCharWrap
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasLeading
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasAlignment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasWrap
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasTextStream
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setTextParams
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 startStream
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 endStream
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getStream
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPartialStream
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
9
 getColorStream
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
9
 calculateTextMatrix
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
1 / 1
10
1<?php
2/**
3 * Pop PHP Framework (https://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@noladev.com>
7 * @copyright  Copyright (c) 2009-2026 NOLA Interactive, LLC.
8 * @license    https://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Pdf\Document\Page;
15
16use Pop\Color\Color;
17use Pop\Color\Color\ColorInterface;
18use OutOfRangeException;
19
20/**
21 * Pdf page text class
22 *
23 * @category   Pop
24 * @package    Pop\Pdf
25 * @author     Nick Sagona, III <dev@noladev.com>
26 * @copyright  Copyright (c) 2009-2026 NOLA Interactive, LLC.
27 * @license    https://www.popphp.org/license     New BSD License
28 * @version    5.2.7
29 */
30class Text
31{
32
33    /**
34     * Text string value
35     * @var ?string
36     */
37    protected ?string $string = null;
38
39    /**
40     * Text strings as array for streaming
41     * @var array
42     */
43    protected array $strings = [];
44
45    /**
46     * Text strings with offset values
47     * @var array
48     */
49    protected array $stringsWithOffsets = [];
50
51    /**
52     * Text font size
53     * @var int|float
54     */
55    protected int|float $size = 12;
56
57    /**
58     * Text fill color
59     * @var ?ColorInterface
60     */
61    protected ?ColorInterface $fillColor = null;
62
63    /**
64     * Text stroke color
65     * @var ?ColorInterface
66     */
67    protected ?ColorInterface $strokeColor = null;
68
69    /**
70     * Text stroke
71     * @var array
72     */
73    protected array $stroke = [
74        'width'      => 0,
75        'dashLength' => null,
76        'dashGap'    => null
77    ];
78
79    /**
80     * Basic wrap based on character length
81     * @var int
82     */
83    protected int $charWrap = 0;
84
85    /**
86     * Leading for the lines for a character wrap
87     * @var int
88     */
89    protected int $leading = 0;
90
91    /**
92     * Text alignment object
93     * @var ?Text\Alignment
94     */
95    protected ?Text\Alignment $alignment = null;
96
97    /**
98     * Text wrap object
99     * @var ?Text\Wrap
100     */
101    protected ?Text\Wrap $wrap = null;
102
103    /**
104     * Text stream object
105     * @var ?Text\Stream
106     */
107    protected ?Text\Stream $stream = null;
108
109    /**
110     * Text parameters
111     * @var array
112     */
113    protected array $textParams = [
114        'c'    => 0,
115        'w'    => 0,
116        'h'    => 100,
117        'v'    => 100,
118        'rot'  => 0,
119        'rend' => 0
120    ];
121
122    /**
123     * Constructor
124     *
125     * Instantiate a PDF text object.
126     *
127     * @param ?string $string
128     * @param ?string $size
129     * @param bool    $escape
130     */
131    public function __construct(?string $string = null, ?string $size = null, bool $escape = true)
132    {
133        if ($string !== null) {
134            $this->setString($string, $escape);
135        }
136        if ($size !== null) {
137            $this->setSize($size);
138        }
139    }
140
141    /**
142     * Set the text string
143     *
144     * @param  string $string
145     * @param  bool   $escape
146     * @return Text
147     */
148    public function setString(string $string, bool $escape = true): Text
149    {
150        if (function_exists('mb_strlen')) {
151            if (mb_strlen($string, 'UTF-8') < strlen($string)) {
152                $string = mb_convert_encoding($string, 'UTF-8', mb_detect_encoding($string));
153            }
154        }
155
156        $this->string = ($escape) ? $this->escape($string) : $string;
157
158        return $this;
159    }
160
161    /**
162     * Set the text strings
163     *
164     * @param  array $strings
165     * @param  bool  $excape
166     * @return Text
167     */
168    public function setStrings(array $strings, bool $escape = true): Text
169    {
170        if (function_exists('mb_strlen')) {
171            $strings = array_map(function($value) use ($escape) {
172                if ($value instanceof Text) {
173                    $v = $value->getString();
174                    if (mb_strlen($v, 'UTF-8') < strlen($v)) {
175                        return mb_convert_encoding($v, 'UTF-8', mb_detect_encoding($v));
176                    }
177                    $value->setString($v, $escape);
178                } else if (mb_strlen($value, 'UTF-8') < strlen($value)) {
179                    $value = mb_convert_encoding($value, 'UTF-8', mb_detect_encoding($value));
180                    if ($escape) {
181                        $value = $this->escape($value);
182                    }
183                }
184                return $value;
185            }, $strings);
186
187        }
188        $this->strings = $strings;
189        return $this;
190    }
191
192    /**
193     * Add a string with offset
194     *
195     * @param  string $string
196     * @param  int    $offset
197     * @return Text
198     */
199    public function addStringWithOffset(string $string, int $offset = 0): Text
200    {
201        if (function_exists('mb_strlen')) {
202            if (mb_strlen($string, 'UTF-8') < strlen($string)) {
203                $string = mb_convert_encoding($string, 'UTF-8', mb_detect_encoding($string));
204            }
205        }
206        $this->stringsWithOffsets[] = [
207            'string' => $string,
208            'offset' => $offset
209        ];
210        return $this;
211    }
212
213    /**
214     * Get strings with offset
215     *
216     * @return array
217     */
218    public function getStringsWithOffset(): array
219    {
220        return $this->stringsWithOffsets;
221    }
222
223    /**
224     * Set the text size
225     *
226     * @param  int|float $size
227     * @return Text
228     */
229    public function setSize(int|float $size): Text
230    {
231        $this->size = $size;
232        return $this;
233    }
234
235    /**
236     * Set the text fill color
237     *
238     * @param  ColorInterface $color
239     * @return Text
240     */
241    public function setFillColor(ColorInterface $color): Text
242    {
243        $this->fillColor = $color;
244        return $this;
245    }
246
247    /**
248     * Set the text stroke color
249     *
250     * @param  ColorInterface $color
251     * @return Text
252     */
253    public function setStrokeColor(ColorInterface $color): Text
254    {
255        $this->strokeColor = $color;
256        return $this;
257    }
258
259    /**
260     * Set the text stroke properties
261     *
262     * @param  int  $width
263     * @param  ?int $dashLength
264     * @param  ?int $dashGap
265     * @return Text
266     */
267    public function setStroke(int $width, ?int $dashLength = null, ?int $dashGap = null): Text
268    {
269        $this->stroke = [
270            'width'      => $width,
271            'dashLength' => $dashLength,
272            'dashGap'    => $dashGap
273        ];
274        return $this;
275    }
276
277    /**
278     * Method to set the rotation of the text
279     *
280     * @param  int $rotation
281     * @throws OutOfRangeException
282     * @return Text
283     */
284    public function setRotation(int $rotation): Text
285    {
286        if (abs($rotation) > 90) {
287            throw new OutOfRangeException('Error: The rotation parameter must be between -90 and 90 degrees.');
288        }
289        $this->textParams['rot'] = $rotation;
290        return $this;
291    }
292
293    /**
294     * Method to set the character wrap
295     *
296     * @param  int  $charWrap
297     * @param  ?int $leading
298     * @return Text
299     */
300    public function setCharWrap(int $charWrap, ?int $leading = null): Text
301    {
302        $this->charWrap = $charWrap;
303        if ($leading !== null) {
304            $this->setLeading($leading);
305        }
306        return $this;
307    }
308
309    /**
310     * Method to set the character wrap leading
311     *
312     * @param  int $leading
313     * @return Text
314     */
315    public function setLeading(int $leading): Text
316    {
317        $this->leading = $leading;
318        return $this;
319    }
320
321    /**
322     * Method to set the text alignment
323     *
324     * @param  Text\Alignment $alignment
325     * @return Text
326     */
327    public function setAlignment(Text\Alignment $alignment): Text
328    {
329        $this->alignment = $alignment;
330        return $this;
331    }
332
333    /**
334     * Method to set the text wrap
335     *
336     * @param  Text\Wrap $wrap
337     * @return Text
338     */
339    public function setWrap(Text\Wrap $wrap): Text
340    {
341        $this->wrap = $wrap;
342        return $this;
343    }
344
345    /**
346     * Method to set the text stream
347     *
348     * @param  Text\Stream $stream
349     * @return Text
350     */
351    public function setTextStream(Text\Stream $stream): Text
352    {
353        $this->stream = $stream;
354        return $this;
355    }
356
357    /**
358     * Escape string
359     *
360     * @param  string $subject
361     * @param  mixed  $search
362     * @param  mixed  $replace
363     * @return string
364     */
365    public function escape(string $subject, mixed $search  = null, mixed $replace = null): string
366    {
367        if (($search === null) && ($replace === null)) {
368            $search  = ["\\", '(', ')', "\n", "\r", "\t", "\b", "\f"];
369            $replace = ["\\\\", '\(', '\)', "\\n", "\\r", "\\t", "\\b", "\\f"];
370        }
371        return str_replace($search, $replace, $subject);
372    }
373
374    /**
375     * Get the text string
376     *
377     * @return ?string
378     */
379    public function getString(): ?string
380    {
381        return $this->string;
382    }
383
384    /**
385     * Get the text string array
386     *
387     * @return array
388     */
389    public function getStrings(): array
390    {
391        return $this->strings;
392    }
393
394    /**
395     * Get the text size
396     *
397     * @return int|float
398     */
399    public function getSize(): int|float
400    {
401        return $this->size;
402    }
403
404    /**
405     * Get the text fill color
406     *
407     * @return ?ColorInterface
408     */
409    public function getFillColor(): ?ColorInterface
410    {
411        return $this->fillColor;
412    }
413
414    /**
415     * Get the text stroke color
416     *
417     * @return ?ColorInterface
418     */
419    public function getStrokeColor(): ?ColorInterface
420    {
421        return $this->strokeColor;
422    }
423
424    /**
425     * Get the text stroke properties
426     *
427     * @return array
428     */
429    public function getStroke(): array
430    {
431        return $this->stroke;
432    }
433
434    /**
435     * Get the rotation of the text
436     *
437     * @return int
438     */
439    public function getRotation(): int
440    {
441        return $this->textParams['rot'];
442    }
443
444    /**
445     * Get character wrap
446     *
447     * @return int
448     */
449    public function getCharWrap(): int
450    {
451        return $this->charWrap;
452    }
453
454    /**
455     * Get number of wrapped lines from character wrap
456     *
457     * @return int
458     */
459    public function getNumberOfWrappedLines(): int
460    {
461        return count(explode("\n", wordwrap($this->string, $this->charWrap, "\n")));
462    }
463
464    /**
465     * Get character wrap leading
466     *
467     * @return int
468     */
469    public function getLeading(): int
470    {
471        return $this->leading;
472    }
473
474    /**
475     * Get text alignment
476     *
477     * @return ?Text\Alignment
478     */
479    public function getAlignment(): ?Text\Alignment
480    {
481        return $this->alignment;
482    }
483
484    /**
485     * Get text wrap
486     *
487     * @return ?Text\Wrap
488     */
489    public function getWrap(): ?Text\Wrap
490    {
491        return $this->wrap;
492    }
493
494    /**
495     * Get text stream
496     *
497     * @return ?Text\Stream
498     */
499    public function getTextStream(): ?Text\Stream
500    {
501        return $this->stream;
502    }
503
504    /**
505     * Has text string
506     *
507     * @return bool
508     */
509    public function hasString(): bool
510    {
511        return ($this->string !== null);
512    }
513
514    /**
515     * Has text string array
516     *
517     * @return bool
518     */
519    public function hasStrings(): bool
520    {
521        return !empty($this->strings);
522    }
523
524    /**
525     * Has character wrap
526     *
527     * @return bool
528     */
529    public function hasCharWrap(): bool
530    {
531        return ($this->charWrap > 0);
532    }
533
534    /**
535     * Has character wrap leading
536     *
537     * @return bool
538     */
539    public function hasLeading(): bool
540    {
541        return ($this->leading > 0);
542    }
543
544    /**
545     * Has text alignment
546     *
547     * @return bool
548     */
549    public function hasAlignment(): bool
550    {
551        return ($this->alignment !== null);
552    }
553
554    /**
555     * Has text wrap
556     *
557     * @return bool
558     */
559    public function hasWrap(): bool
560    {
561        return ($this->wrap !== null);
562    }
563
564    /**
565     * Has text stream
566     *
567     * @return bool
568     */
569    public function hasTextStream(): bool
570    {
571        return ($this->stream !== null);
572    }
573
574    /**
575     * Set the text parameters for rendering text content
576     *
577     * @param  int $c    (character spacing)
578     * @param  int $w    (word spacing)
579     * @param  int $h    (horz stretch)
580     * @param  int $v    (vert stretch)
581     * @param  int $rot  (rotation, -90 - 90)
582     * @param  int $rend (render flag, 0 - 7)
583     * @throws OutOfRangeException
584     * @return Text
585     */
586    public function setTextParams(int $c = 0, int $w = 0, int $h = 100, int $v = 100, int $rot = 0, int $rend = 0): Text
587    {
588        // Check the rotation parameter.
589        if (abs($rot) > 90) {
590            throw new OutOfRangeException('Error: The rotation parameter must be between -90 and 90 degrees.');
591        }
592
593        // Check the render parameter.
594        if ((!is_int($rend)) || (($rend > 7) || ($rend < 0))) {
595            throw new OutOfRangeException('Error: The render parameter must be an integer between 0 and 7.');
596        }
597
598        // Set the text parameters.
599        $this->textParams['c']    = $c;
600        $this->textParams['w']    = $w;
601        $this->textParams['h']    = $h;
602        $this->textParams['v']    = $v;
603        $this->textParams['rot']  = $rot;
604        $this->textParams['rend'] = $rend;
605
606        return $this;
607    }
608
609    /**
610     * Start the text stream
611     *
612     * @param  string $fontReference
613     * @param  int    $x
614     * @param  int    $y
615     * @return string
616     */
617    public function startStream(string $fontReference, int $x, int $y): string
618    {
619        $stream        = '';
620        $fontReference = substr($fontReference, 0, strpos($fontReference, ' '));
621
622        $stream .= "\nBT\n    {$fontReference} {$this->size} Tf\n    " . $this->calculateTextMatrix() .
623            " {$x} {$y} Tm\n    " . $this->textParams['c'] . " Tc " . $this->textParams['w'] .
624            " Tw " . $this->textParams['rend'] . " Tr\n";
625
626        return $stream;
627    }
628
629    /**
630     * End the text stream
631     *
632     * @return string
633     */
634    public function endStream(): string
635    {
636        return "ET\n";
637    }
638
639    /**
640     * Get the text stream
641     *
642     * @param  string $fontReference
643     * @param  int    $x
644     * @param  int    $y
645     * @return string
646     */
647    public function getStream(string $fontReference, int $x, int $y): string
648    {
649        return $this->startStream($fontReference, $x, $y) . $this->getPartialStream() . $this->endStream();
650    }
651
652    /**
653     * Get the partial text stream
654     *
655     * @param  ?string $fontReference
656     * @return string
657     */
658    public function getPartialStream(?string $fontReference = null): string
659    {
660        $stream = '';
661
662        if ($fontReference !== null) {
663            $fontReference = substr($fontReference, 0, strpos($fontReference, ' '));
664            $stream       .= "    {$fontReference} {$this->size} Tf\n";
665        }
666
667        $stream .= $this->getColorStream();
668
669        if (count($this->stringsWithOffsets) > 0) {
670            $stream .= "    [({$this->string})";
671            foreach ($this->stringsWithOffsets as $string) {
672                $stream .= " " . (0 - $string['offset']) . " (" . $string['string'] . ")";
673            }
674            $stream .= "]TJ\n";
675        } else {
676            if (($this->hasCharWrap()) && (strlen($this->string) > $this->charWrap)) {
677                if ((int)$this->leading == 0) {
678                    $this->leading = $this->size;
679                }
680
681                $strings = explode("\n", wordwrap($this->string, $this->charWrap, "\n"));
682
683                foreach ($strings as $i => $string) {
684                    $stream .= "    ({$string})Tj\n";
685                    if ($i < count($strings)) {
686                        $stream .= "    0 -" . $this->leading . " Td\n";
687                    }
688                }
689            } else {
690                $stream .= "    ({$this->string})Tj\n";
691            }
692        }
693
694        return $stream;
695    }
696
697    /**
698     * Get the partial color stream
699     *
700     * @return string
701     */
702    public function getColorStream(): string
703    {
704        $stream = '';
705
706        if ($this->fillColor !== null) {
707            if ($this->fillColor instanceof Color\Rgb) {
708                $stream .= '    ' . $this->fillColor->render(Color\Rgb::PERCENT) . " rg\n";
709            } else if ($this->fillColor instanceof Color\Cmyk) {
710                $stream .= '    ' . $this->fillColor->render(Color\Cmyk::PERCENT) . " k\n";
711            } else if ($this->fillColor instanceof Color\Grayscale) {
712                $stream .= '    ' . $this->fillColor->render(Color\Grayscale::PERCENT) . " g\n";
713            }
714        }
715        if ($this->strokeColor !== null) {
716            if ($this->strokeColor instanceof Color\Rgb) {
717                $stream .= '    ' . $this->strokeColor->render(Color\Rgb::PERCENT) . " RG\n";
718            } else if ($this->strokeColor instanceof Color\Cmyk) {
719                $stream .= '    ' . $this->strokeColor->render(Color\Cmyk::PERCENT) . " K\n";
720            } else if ($this->strokeColor instanceof Color\Grayscale) {
721                $stream .= '    ' . $this->strokeColor->render(Color\Grayscale::PERCENT) . " G\n";
722            }
723        }
724
725        return $stream;
726    }
727
728    /**
729     * Calculate text matrix
730     *
731     * @return string
732     */
733    protected function calculateTextMatrix(): string
734    {
735        // Define some variables.
736        $a   = '';
737        $b   = '';
738        $c   = '';
739        $d   = '';
740        $neg = null;
741
742        // Determine is the rotate parameter is negative or not.
743        $neg = ($this->textParams['rot'] < 0) ? true : false;
744
745        // Calculate the text matrix parameters.
746        $rot = abs($this->textParams['rot']);
747
748        if (($rot >= 0) && ($rot <= 45)) {
749            $factor = round(($rot / 45), 2);
750            if ($neg) {
751                $a = 1;
752                $b = '-' . $factor;
753                $c = $factor;
754                $d = 1;
755            } else {
756                $a = 1;
757                $b = $factor;
758                $c = '-' . $factor;
759                $d = 1;
760            }
761        } else if (($rot > 45) && ($rot <= 90)) {
762            $factor = round(((90 - $rot) / 45), 2);
763            if ($neg) {
764                $a = $factor;
765                $b = -1;
766                $c = 1;
767                $d = $factor;
768            } else {
769                $a = $factor;
770                $b = 1;
771                $c = -1;
772                $d = $factor;
773            }
774        }
775
776        // Adjust the text matrix parameters according to the horizontal and vertical scale parameters.
777        if ($this->textParams['h'] != 100) {
778            $a = round(($a * ($this->textParams['h'] / 100)), 2);
779            $b = round(($b * ($this->textParams['h'] / 100)), 2);
780        }
781
782        if ($this->textParams['v'] != 100) {
783            $c = round(($c * ($this->textParams['v'] / 100)), 2);
784            $d = round(($d * ($this->textParams['v'] / 100)), 2);
785        }
786
787        // Return the text matrix.
788        return "{$a} {$b} {$c} {$d}";
789    }
790
791}