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