xref: /webtrees/app/Report/HtmlRenderer.php (revision b6f35a76f16ee5da672b7d3d886becc6b838498e)
1*b6f35a76SGreg Roach<?php
2*b6f35a76SGreg Roach
3*b6f35a76SGreg Roach/**
4*b6f35a76SGreg Roach * webtrees: online genealogy
5*b6f35a76SGreg Roach * Copyright (C) 2019 webtrees development team
6*b6f35a76SGreg Roach * This program is free software: you can redistribute it and/or modify
7*b6f35a76SGreg Roach * it under the terms of the GNU General Public License as published by
8*b6f35a76SGreg Roach * the Free Software Foundation, either version 3 of the License, or
9*b6f35a76SGreg Roach * (at your option) any later version.
10*b6f35a76SGreg Roach * This program is distributed in the hope that it will be useful,
11*b6f35a76SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
12*b6f35a76SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13*b6f35a76SGreg Roach * GNU General Public License for more details.
14*b6f35a76SGreg Roach * You should have received a copy of the GNU General Public License
15*b6f35a76SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>.
16*b6f35a76SGreg Roach */
17*b6f35a76SGreg Roach
18*b6f35a76SGreg Roachdeclare(strict_types=1);
19*b6f35a76SGreg Roach
20*b6f35a76SGreg Roachnamespace Fisharebest\Webtrees\Report;
21*b6f35a76SGreg Roach
22*b6f35a76SGreg Roachuse Fisharebest\Webtrees\Functions\FunctionsRtl;
23*b6f35a76SGreg Roachuse Fisharebest\Webtrees\I18N;
24*b6f35a76SGreg Roachuse Fisharebest\Webtrees\MediaFile;
25*b6f35a76SGreg Roachuse Fisharebest\Webtrees\Webtrees;
26*b6f35a76SGreg Roachuse League\Flysystem\FilesystemInterface;
27*b6f35a76SGreg Roach
28*b6f35a76SGreg Roachuse function ceil;
29*b6f35a76SGreg Roachuse function count;
30*b6f35a76SGreg Roachuse function explode;
31*b6f35a76SGreg Roachuse function preg_match;
32*b6f35a76SGreg Roachuse function str_replace;
33*b6f35a76SGreg Roachuse function stripos;
34*b6f35a76SGreg Roachuse function substr_count;
35*b6f35a76SGreg Roach
36*b6f35a76SGreg Roach/**
37*b6f35a76SGreg Roach * Class HtmlRenderer
38*b6f35a76SGreg Roach */
39*b6f35a76SGreg Roachclass HtmlRenderer extends AbstractRenderer
40*b6f35a76SGreg Roach{
41*b6f35a76SGreg Roach    /**
42*b6f35a76SGreg Roach     * Cell padding
43*b6f35a76SGreg Roach     *
44*b6f35a76SGreg Roach     * @var float
45*b6f35a76SGreg Roach     */
46*b6f35a76SGreg Roach    public $cPadding = 2;
47*b6f35a76SGreg Roach
48*b6f35a76SGreg Roach    /**
49*b6f35a76SGreg Roach     * Cell height ratio
50*b6f35a76SGreg Roach     *
51*b6f35a76SGreg Roach     * @var float
52*b6f35a76SGreg Roach     */
53*b6f35a76SGreg Roach    public $cellHeightRatio = 1.8;
54*b6f35a76SGreg Roach
55*b6f35a76SGreg Roach    /**
56*b6f35a76SGreg Roach     * Current horizontal position
57*b6f35a76SGreg Roach     *
58*b6f35a76SGreg Roach     * @var float
59*b6f35a76SGreg Roach     */
60*b6f35a76SGreg Roach    public $X = 0.0;
61*b6f35a76SGreg Roach
62*b6f35a76SGreg Roach    /**
63*b6f35a76SGreg Roach     * Current vertical position
64*b6f35a76SGreg Roach     *
65*b6f35a76SGreg Roach     * @var float
66*b6f35a76SGreg Roach     */
67*b6f35a76SGreg Roach    public $Y = 0.0;
68*b6f35a76SGreg Roach
69*b6f35a76SGreg Roach    /**
70*b6f35a76SGreg Roach     * Currently used style name
71*b6f35a76SGreg Roach     *
72*b6f35a76SGreg Roach     * @var string
73*b6f35a76SGreg Roach     */
74*b6f35a76SGreg Roach    public $currentStyle = '';
75*b6f35a76SGreg Roach
76*b6f35a76SGreg Roach    /**
77*b6f35a76SGreg Roach     * Page number counter
78*b6f35a76SGreg Roach     *
79*b6f35a76SGreg Roach     * @var int
80*b6f35a76SGreg Roach     */
81*b6f35a76SGreg Roach    public $pageN = 1;
82*b6f35a76SGreg Roach
83*b6f35a76SGreg Roach    /**
84*b6f35a76SGreg Roach     * Store the page width without left and right margins
85*b6f35a76SGreg Roach     *
86*b6f35a76SGreg Roach     * In HTML, we don't need this
87*b6f35a76SGreg Roach     *
88*b6f35a76SGreg Roach     * @var float
89*b6f35a76SGreg Roach     */
90*b6f35a76SGreg Roach    public $noMarginWidth = 0.0;
91*b6f35a76SGreg Roach
92*b6f35a76SGreg Roach    /**
93*b6f35a76SGreg Roach     * Last cell height
94*b6f35a76SGreg Roach     *
95*b6f35a76SGreg Roach     * @var float
96*b6f35a76SGreg Roach     */
97*b6f35a76SGreg Roach    public $lastCellHeight = 0.0;
98*b6f35a76SGreg Roach
99*b6f35a76SGreg Roach    /**
100*b6f35a76SGreg Roach     * LTR or RTL alignement; "left" on LTR, "right" on RTL
101*b6f35a76SGreg Roach     * Used in <div>
102*b6f35a76SGreg Roach     *
103*b6f35a76SGreg Roach     * @var string
104*b6f35a76SGreg Roach     */
105*b6f35a76SGreg Roach    public $alignRTL = 'left';
106*b6f35a76SGreg Roach
107*b6f35a76SGreg Roach    /**
108*b6f35a76SGreg Roach     * LTR or RTL entity
109*b6f35a76SGreg Roach     *
110*b6f35a76SGreg Roach     * @var string
111*b6f35a76SGreg Roach     */
112*b6f35a76SGreg Roach    public $entityRTL = '&lrm;';
113*b6f35a76SGreg Roach
114*b6f35a76SGreg Roach    /**
115*b6f35a76SGreg Roach     * Largest Font Height is used by TextBox etc.
116*b6f35a76SGreg Roach     *
117*b6f35a76SGreg Roach     * Use this to calculate a the text height.
118*b6f35a76SGreg Roach     * This makes sure that the text fits into the cell/box when different font sizes are used
119*b6f35a76SGreg Roach     *
120*b6f35a76SGreg Roach     * @var float
121*b6f35a76SGreg Roach     */
122*b6f35a76SGreg Roach    public $largestFontHeight = 0;
123*b6f35a76SGreg Roach
124*b6f35a76SGreg Roach    /**
125*b6f35a76SGreg Roach     * Keep track of the highest Y position
126*b6f35a76SGreg Roach     *
127*b6f35a76SGreg Roach     * Used with Header div / Body div / Footer div / "addpage" / The bottom of the last image etc.
128*b6f35a76SGreg Roach     *
129*b6f35a76SGreg Roach     * @var float
130*b6f35a76SGreg Roach     */
131*b6f35a76SGreg Roach    public $maxY = 0;
132*b6f35a76SGreg Roach
133*b6f35a76SGreg Roach    /** @var ReportBaseElement[] Array of elements in the header */
134*b6f35a76SGreg Roach    public $headerElements = [];
135*b6f35a76SGreg Roach
136*b6f35a76SGreg Roach    /** @var ReportBaseElement[] Array of elements in the page header */
137*b6f35a76SGreg Roach    public $pageHeaderElements = [];
138*b6f35a76SGreg Roach
139*b6f35a76SGreg Roach    /** @var ReportBaseElement[] Array of elements in the footer */
140*b6f35a76SGreg Roach    public $footerElements = [];
141*b6f35a76SGreg Roach
142*b6f35a76SGreg Roach    /** @var ReportBaseElement[] Array of elements in the body */
143*b6f35a76SGreg Roach    public $bodyElements = [];
144*b6f35a76SGreg Roach
145*b6f35a76SGreg Roach    /** @var ReportHtmlFootnote[] Array of elements in the footer notes */
146*b6f35a76SGreg Roach    public $printedfootnotes = [];
147*b6f35a76SGreg Roach
148*b6f35a76SGreg Roach    /**
149*b6f35a76SGreg Roach     * HTML Setup - ReportHtml
150*b6f35a76SGreg Roach     *
151*b6f35a76SGreg Roach     * @return void
152*b6f35a76SGreg Roach     */
153*b6f35a76SGreg Roach    public function setup(): void
154*b6f35a76SGreg Roach    {
155*b6f35a76SGreg Roach        parent::setup();
156*b6f35a76SGreg Roach
157*b6f35a76SGreg Roach        // Setting up the correct dimensions if Portrait (default) or Landscape
158*b6f35a76SGreg Roach        if ($this->orientation === 'landscape') {
159*b6f35a76SGreg Roach            $tmpw              = $this->page_width;
160*b6f35a76SGreg Roach            $this->page_width  = $this->page_height;
161*b6f35a76SGreg Roach            $this->page_height = $tmpw;
162*b6f35a76SGreg Roach        }
163*b6f35a76SGreg Roach        // Store the pagewidth without margins
164*b6f35a76SGreg Roach        $this->noMarginWidth = $this->page_width - $this->left_margin - $this->right_margin;
165*b6f35a76SGreg Roach        // If RTL
166*b6f35a76SGreg Roach        if ($this->rtl) {
167*b6f35a76SGreg Roach            $this->alignRTL  = 'right';
168*b6f35a76SGreg Roach            $this->entityRTL = '&rlm;';
169*b6f35a76SGreg Roach        }
170*b6f35a76SGreg Roach        // Change the default HTML font name
171*b6f35a76SGreg Roach        $this->default_font = 'Arial';
172*b6f35a76SGreg Roach
173*b6f35a76SGreg Roach        if ($this->show_generated_by) {
174*b6f35a76SGreg Roach            // The default style name for Generated by.... is 'genby'
175*b6f35a76SGreg Roach            $element = new ReportHtmlCell(0, 10, 0, 'C', '', 'genby', 1, ReportBaseElement::CURRENT_POSITION, ReportBaseElement::CURRENT_POSITION, 0, 0, '', '', true);
176*b6f35a76SGreg Roach            $element->addText($this->generated_by);
177*b6f35a76SGreg Roach            $element->setUrl(Webtrees::URL);
178*b6f35a76SGreg Roach            $this->footerElements[] = $element;
179*b6f35a76SGreg Roach        }
180*b6f35a76SGreg Roach    }
181*b6f35a76SGreg Roach
182*b6f35a76SGreg Roach    /**
183*b6f35a76SGreg Roach     * Add an element.
184*b6f35a76SGreg Roach     *
185*b6f35a76SGreg Roach     * @param ReportBaseElement|string $element
186*b6f35a76SGreg Roach     *
187*b6f35a76SGreg Roach     * @return void
188*b6f35a76SGreg Roach     */
189*b6f35a76SGreg Roach    public function addElement($element): void
190*b6f35a76SGreg Roach    {
191*b6f35a76SGreg Roach        if ($this->processing === 'B') {
192*b6f35a76SGreg Roach            $this->bodyElements[] = $element;
193*b6f35a76SGreg Roach        } elseif ($this->processing === 'H') {
194*b6f35a76SGreg Roach            $this->headerElements[] = $element;
195*b6f35a76SGreg Roach        } elseif ($this->processing === 'F') {
196*b6f35a76SGreg Roach            $this->footerElements[] = $element;
197*b6f35a76SGreg Roach        }
198*b6f35a76SGreg Roach    }
199*b6f35a76SGreg Roach
200*b6f35a76SGreg Roach    /**
201*b6f35a76SGreg Roach     * Generate the page header
202*b6f35a76SGreg Roach     *
203*b6f35a76SGreg Roach     * @return void
204*b6f35a76SGreg Roach     */
205*b6f35a76SGreg Roach    private function runPageHeader(): void
206*b6f35a76SGreg Roach    {
207*b6f35a76SGreg Roach        foreach ($this->pageHeaderElements as $element) {
208*b6f35a76SGreg Roach            if ($element instanceof ReportBaseElement) {
209*b6f35a76SGreg Roach                $element->render($this);
210*b6f35a76SGreg Roach            } elseif ($element === 'footnotetexts') {
211*b6f35a76SGreg Roach                $this->footnotes();
212*b6f35a76SGreg Roach            } elseif ($element === 'addpage') {
213*b6f35a76SGreg Roach                $this->addPage();
214*b6f35a76SGreg Roach            }
215*b6f35a76SGreg Roach        }
216*b6f35a76SGreg Roach    }
217*b6f35a76SGreg Roach
218*b6f35a76SGreg Roach    /**
219*b6f35a76SGreg Roach     * Generate footnotes
220*b6f35a76SGreg Roach     *
221*b6f35a76SGreg Roach     * @return void
222*b6f35a76SGreg Roach     */
223*b6f35a76SGreg Roach    public function footnotes(): void
224*b6f35a76SGreg Roach    {
225*b6f35a76SGreg Roach        $this->currentStyle = '';
226*b6f35a76SGreg Roach        if (!empty($this->printedfootnotes)) {
227*b6f35a76SGreg Roach            foreach ($this->printedfootnotes as $element) {
228*b6f35a76SGreg Roach                $element->renderFootnote($this);
229*b6f35a76SGreg Roach            }
230*b6f35a76SGreg Roach        }
231*b6f35a76SGreg Roach    }
232*b6f35a76SGreg Roach
233*b6f35a76SGreg Roach    /**
234*b6f35a76SGreg Roach     * Run the report.
235*b6f35a76SGreg Roach     *
236*b6f35a76SGreg Roach     * @return void
237*b6f35a76SGreg Roach     */
238*b6f35a76SGreg Roach    public function run(): void
239*b6f35a76SGreg Roach    {
240*b6f35a76SGreg Roach        // Setting up the styles
241*b6f35a76SGreg Roach        echo '<style type="text/css">';
242*b6f35a76SGreg Roach        echo '#bodydiv { font: 10px sans-serif;}';
243*b6f35a76SGreg Roach        foreach ($this->styles as $class => $style) {
244*b6f35a76SGreg Roach            echo '.', $class, ' { ';
245*b6f35a76SGreg Roach            if ($style['font'] === 'dejavusans') {
246*b6f35a76SGreg Roach                $style['font'] = $this->default_font;
247*b6f35a76SGreg Roach            }
248*b6f35a76SGreg Roach            echo 'font-family: ', $style['font'], '; ';
249*b6f35a76SGreg Roach            echo 'font-size: ', $style['size'], 'pt; ';
250*b6f35a76SGreg Roach            // Case-insensitive
251*b6f35a76SGreg Roach            if (stripos($style['style'], 'B') !== false) {
252*b6f35a76SGreg Roach                echo 'font-weight: bold; ';
253*b6f35a76SGreg Roach            }
254*b6f35a76SGreg Roach            if (stripos($style['style'], 'I') !== false) {
255*b6f35a76SGreg Roach                echo 'font-style: italic; ';
256*b6f35a76SGreg Roach            }
257*b6f35a76SGreg Roach            if (stripos($style['style'], 'U') !== false) {
258*b6f35a76SGreg Roach                echo 'text-decoration: underline; ';
259*b6f35a76SGreg Roach            }
260*b6f35a76SGreg Roach            if (stripos($style['style'], 'D') !== false) {
261*b6f35a76SGreg Roach                echo 'text-decoration: line-through; ';
262*b6f35a76SGreg Roach            }
263*b6f35a76SGreg Roach            echo '}', PHP_EOL;
264*b6f35a76SGreg Roach        }
265*b6f35a76SGreg Roach
266*b6f35a76SGreg Roach        //-- header divider
267*b6f35a76SGreg Roach        echo '</style>', PHP_EOL;
268*b6f35a76SGreg Roach        echo '<div id="headermargin" style="position: relative; top: auto; height: ', $this->header_margin, 'pt; width: ', $this->noMarginWidth, 'pt;"></div>';
269*b6f35a76SGreg Roach        echo '<div id="headerdiv" style="position: relative; top: auto; width: ', $this->noMarginWidth, 'pt;">';
270*b6f35a76SGreg Roach        foreach ($this->headerElements as $element) {
271*b6f35a76SGreg Roach            if ($element instanceof ReportBaseElement) {
272*b6f35a76SGreg Roach                $element->render($this);
273*b6f35a76SGreg Roach            } elseif ($element === 'footnotetexts') {
274*b6f35a76SGreg Roach                $this->footnotes();
275*b6f35a76SGreg Roach            } elseif ($element === 'addpage') {
276*b6f35a76SGreg Roach                $this->addPage();
277*b6f35a76SGreg Roach            }
278*b6f35a76SGreg Roach        }
279*b6f35a76SGreg Roach        //-- body
280*b6f35a76SGreg Roach        echo '</div>';
281*b6f35a76SGreg Roach        echo '<script>document.getElementById("headerdiv").style.height="', $this->top_margin - $this->header_margin - 6, 'pt";</script>';
282*b6f35a76SGreg Roach        echo '<div id="bodydiv" style="position: relative; top: auto; width: ', $this->noMarginWidth, 'pt; height: 100%;">';
283*b6f35a76SGreg Roach        $this->Y    = 0;
284*b6f35a76SGreg Roach        $this->maxY = 0;
285*b6f35a76SGreg Roach        $this->runPageHeader();
286*b6f35a76SGreg Roach        foreach ($this->bodyElements as $element) {
287*b6f35a76SGreg Roach            if ($element instanceof ReportBaseElement) {
288*b6f35a76SGreg Roach                $element->render($this);
289*b6f35a76SGreg Roach            } elseif ($element === 'footnotetexts') {
290*b6f35a76SGreg Roach                $this->footnotes();
291*b6f35a76SGreg Roach            } elseif ($element === 'addpage') {
292*b6f35a76SGreg Roach                $this->addPage();
293*b6f35a76SGreg Roach            }
294*b6f35a76SGreg Roach        }
295*b6f35a76SGreg Roach        //-- footer
296*b6f35a76SGreg Roach        echo '</div>';
297*b6f35a76SGreg Roach        echo '<script>document.getElementById("bodydiv").style.height="', $this->maxY, 'pt";</script>';
298*b6f35a76SGreg Roach        echo '<div id="bottommargin" style="position: relative; top: auto; height: ', $this->bottom_margin - $this->footer_margin, 'pt;width:', $this->noMarginWidth, 'pt;"></div>';
299*b6f35a76SGreg Roach        echo '<div id="footerdiv" style="position: relative; top: auto; width: ', $this->noMarginWidth, 'pt;height:auto;">';
300*b6f35a76SGreg Roach        $this->Y    = 0;
301*b6f35a76SGreg Roach        $this->X    = 0;
302*b6f35a76SGreg Roach        $this->maxY = 0;
303*b6f35a76SGreg Roach        foreach ($this->footerElements as $element) {
304*b6f35a76SGreg Roach            if ($element instanceof ReportBaseElement) {
305*b6f35a76SGreg Roach                $element->render($this);
306*b6f35a76SGreg Roach            } elseif ($element === 'footnotetexts') {
307*b6f35a76SGreg Roach                $this->footnotes();
308*b6f35a76SGreg Roach            } elseif ($element === 'addpage') {
309*b6f35a76SGreg Roach                $this->addPage();
310*b6f35a76SGreg Roach            }
311*b6f35a76SGreg Roach        }
312*b6f35a76SGreg Roach        echo '</div>';
313*b6f35a76SGreg Roach        echo '<script>document.getElementById("footerdiv").style.height="', $this->maxY, 'pt";</script>';
314*b6f35a76SGreg Roach        echo '<div id="footermargin" style="position: relative; top: auto; height: ', $this->footer_margin, 'pt;width:', $this->noMarginWidth, 'pt;"></div>';
315*b6f35a76SGreg Roach    }
316*b6f35a76SGreg Roach
317*b6f35a76SGreg Roach    /**
318*b6f35a76SGreg Roach     * Create a new Cell object.
319*b6f35a76SGreg Roach     *
320*b6f35a76SGreg Roach     * @param int    $width   cell width (expressed in points)
321*b6f35a76SGreg Roach     * @param int    $height  cell height (expressed in points)
322*b6f35a76SGreg Roach     * @param mixed  $border  Border style
323*b6f35a76SGreg Roach     * @param string $align   Text alignement
324*b6f35a76SGreg Roach     * @param string $bgcolor Background color code
325*b6f35a76SGreg Roach     * @param string $style   The name of the text style
326*b6f35a76SGreg Roach     * @param int    $ln      Indicates where the current position should go after the call
327*b6f35a76SGreg Roach     * @param mixed  $top     Y-position
328*b6f35a76SGreg Roach     * @param mixed  $left    X-position
329*b6f35a76SGreg Roach     * @param int    $fill    Indicates if the cell background must be painted (1) or transparent (0). Default value: 1
330*b6f35a76SGreg Roach     * @param int    $stretch Stretch carachter mode
331*b6f35a76SGreg Roach     * @param string $bocolor Border color
332*b6f35a76SGreg Roach     * @param string $tcolor  Text color
333*b6f35a76SGreg Roach     * @param bool   $reseth
334*b6f35a76SGreg Roach     *
335*b6f35a76SGreg Roach     * @return ReportBaseCell
336*b6f35a76SGreg Roach     */
337*b6f35a76SGreg Roach    public function createCell($width, $height, $border, $align, $bgcolor, $style, $ln, $top, $left, $fill, $stretch, $bocolor, $tcolor, $reseth): ReportBaseCell
338*b6f35a76SGreg Roach    {
339*b6f35a76SGreg Roach        return new ReportHtmlCell($width, $height, $border, $align, $bgcolor, $style, $ln, $top, $left, $fill, $stretch, $bocolor, $tcolor, $reseth);
340*b6f35a76SGreg Roach    }
341*b6f35a76SGreg Roach
342*b6f35a76SGreg Roach    /**
343*b6f35a76SGreg Roach     * Create a new TextBox object.
344*b6f35a76SGreg Roach     *
345*b6f35a76SGreg Roach     * @param float  $width   Text box width
346*b6f35a76SGreg Roach     * @param float  $height  Text box height
347*b6f35a76SGreg Roach     * @param bool   $border
348*b6f35a76SGreg Roach     * @param string $bgcolor Background color code in HTML
349*b6f35a76SGreg Roach     * @param bool   $newline
350*b6f35a76SGreg Roach     * @param float  $left
351*b6f35a76SGreg Roach     * @param float  $top
352*b6f35a76SGreg Roach     * @param bool   $pagecheck
353*b6f35a76SGreg Roach     * @param string $style
354*b6f35a76SGreg Roach     * @param bool   $fill
355*b6f35a76SGreg Roach     * @param bool   $padding
356*b6f35a76SGreg Roach     * @param bool   $reseth
357*b6f35a76SGreg Roach     *
358*b6f35a76SGreg Roach     * @return ReportBaseTextbox
359*b6f35a76SGreg Roach     */
360*b6f35a76SGreg Roach    public function createTextBox(
361*b6f35a76SGreg Roach        float $width,
362*b6f35a76SGreg Roach        float $height,
363*b6f35a76SGreg Roach        bool $border,
364*b6f35a76SGreg Roach        string $bgcolor,
365*b6f35a76SGreg Roach        bool $newline,
366*b6f35a76SGreg Roach        float $left,
367*b6f35a76SGreg Roach        float $top,
368*b6f35a76SGreg Roach        bool $pagecheck,
369*b6f35a76SGreg Roach        string $style,
370*b6f35a76SGreg Roach        bool $fill,
371*b6f35a76SGreg Roach        bool $padding,
372*b6f35a76SGreg Roach        bool $reseth
373*b6f35a76SGreg Roach    ): ReportBaseTextbox {
374*b6f35a76SGreg Roach        return new ReportHtmlTextbox($width, $height, $border, $bgcolor, $newline, $left, $top, $pagecheck, $style, $fill, $padding, $reseth);
375*b6f35a76SGreg Roach    }
376*b6f35a76SGreg Roach
377*b6f35a76SGreg Roach    /**
378*b6f35a76SGreg Roach     * Create a text element.
379*b6f35a76SGreg Roach     *
380*b6f35a76SGreg Roach     * @param string $style
381*b6f35a76SGreg Roach     * @param string $color
382*b6f35a76SGreg Roach     *
383*b6f35a76SGreg Roach     * @return ReportBaseText
384*b6f35a76SGreg Roach     */
385*b6f35a76SGreg Roach    public function createText(string $style, string $color): ReportBaseText
386*b6f35a76SGreg Roach    {
387*b6f35a76SGreg Roach        return new ReportHtmlText($style, $color);
388*b6f35a76SGreg Roach    }
389*b6f35a76SGreg Roach
390*b6f35a76SGreg Roach    /**
391*b6f35a76SGreg Roach     * Create a new Footnote object.
392*b6f35a76SGreg Roach     *
393*b6f35a76SGreg Roach     * @param string $style Style name
394*b6f35a76SGreg Roach     *
395*b6f35a76SGreg Roach     * @return ReportBaseFootnote
396*b6f35a76SGreg Roach     */
397*b6f35a76SGreg Roach    public function createFootnote($style): ReportBaseFootnote
398*b6f35a76SGreg Roach    {
399*b6f35a76SGreg Roach        return new ReportHtmlFootnote($style);
400*b6f35a76SGreg Roach    }
401*b6f35a76SGreg Roach
402*b6f35a76SGreg Roach    /**
403*b6f35a76SGreg Roach     * Create a new Page Header object
404*b6f35a76SGreg Roach     *
405*b6f35a76SGreg Roach     * @return ReportBasePageHeader
406*b6f35a76SGreg Roach     */
407*b6f35a76SGreg Roach    public function createPageHeader(): ReportBasePageHeader
408*b6f35a76SGreg Roach    {
409*b6f35a76SGreg Roach        return new ReportHtmlPageHeader();
410*b6f35a76SGreg Roach    }
411*b6f35a76SGreg Roach
412*b6f35a76SGreg Roach    /**
413*b6f35a76SGreg Roach     * Create a new image object.
414*b6f35a76SGreg Roach     *
415*b6f35a76SGreg Roach     * @param string $file  Filename
416*b6f35a76SGreg Roach     * @param float  $x
417*b6f35a76SGreg Roach     * @param float  $y
418*b6f35a76SGreg Roach     * @param float  $w     Image width
419*b6f35a76SGreg Roach     * @param float  $h     Image height
420*b6f35a76SGreg Roach     * @param string $align L:left, C:center, R:right or empty to use x/y
421*b6f35a76SGreg Roach     * @param string $ln    T:same line, N:next line
422*b6f35a76SGreg Roach     *
423*b6f35a76SGreg Roach     * @return ReportBaseImage
424*b6f35a76SGreg Roach     */
425*b6f35a76SGreg Roach    public function createImage(string $file, float $x, float $y, float $w, float $h, string $align, string $ln): ReportBaseImage
426*b6f35a76SGreg Roach    {
427*b6f35a76SGreg Roach        return new ReportHtmlImage($file, $x, $y, $w, $h, $align, $ln);
428*b6f35a76SGreg Roach    }
429*b6f35a76SGreg Roach
430*b6f35a76SGreg Roach    /**
431*b6f35a76SGreg Roach     * Create a new image object from Media Object.
432*b6f35a76SGreg Roach     *
433*b6f35a76SGreg Roach     * @param MediaFile           $media_file
434*b6f35a76SGreg Roach     * @param float               $x
435*b6f35a76SGreg Roach     * @param float               $y
436*b6f35a76SGreg Roach     * @param float               $w     Image width
437*b6f35a76SGreg Roach     * @param float               $h     Image height
438*b6f35a76SGreg Roach     * @param string              $align L:left, C:center, R:right or empty to use x/y
439*b6f35a76SGreg Roach     * @param string              $ln    T:same line, N:next line
440*b6f35a76SGreg Roach     * @param FilesystemInterface $data_filesystem
441*b6f35a76SGreg Roach     *
442*b6f35a76SGreg Roach     * @return ReportBaseImage
443*b6f35a76SGreg Roach     */
444*b6f35a76SGreg Roach    public function createImageFromObject(
445*b6f35a76SGreg Roach        MediaFile $media_file,
446*b6f35a76SGreg Roach        float $x,
447*b6f35a76SGreg Roach        float $y,
448*b6f35a76SGreg Roach        float $w,
449*b6f35a76SGreg Roach        float $h,
450*b6f35a76SGreg Roach        string $align,
451*b6f35a76SGreg Roach        string $ln,
452*b6f35a76SGreg Roach        FilesystemInterface $data_filesystem
453*b6f35a76SGreg Roach    ): ReportBaseImage {
454*b6f35a76SGreg Roach        return new ReportHtmlImage($media_file->imageUrl((int) $w, (int) $h, ''), $x, $y, $w, $h, $align, $ln);
455*b6f35a76SGreg Roach    }
456*b6f35a76SGreg Roach
457*b6f35a76SGreg Roach    /**
458*b6f35a76SGreg Roach     * Create a line.
459*b6f35a76SGreg Roach     *
460*b6f35a76SGreg Roach     * @param float $x1
461*b6f35a76SGreg Roach     * @param float $y1
462*b6f35a76SGreg Roach     * @param float $x2
463*b6f35a76SGreg Roach     * @param float $y2
464*b6f35a76SGreg Roach     *
465*b6f35a76SGreg Roach     * @return ReportBaseLine
466*b6f35a76SGreg Roach     */
467*b6f35a76SGreg Roach    public function createLine(float $x1, float $y1, float $x2, float $y2): ReportBaseLine
468*b6f35a76SGreg Roach    {
469*b6f35a76SGreg Roach        return new ReportHtmlLine($x1, $y1, $x2, $y2);
470*b6f35a76SGreg Roach    }
471*b6f35a76SGreg Roach
472*b6f35a76SGreg Roach    /**
473*b6f35a76SGreg Roach     * Create an HTML element.
474*b6f35a76SGreg Roach     *
475*b6f35a76SGreg Roach     * @param string   $tag
476*b6f35a76SGreg Roach     * @param string[] $attrs
477*b6f35a76SGreg Roach     *
478*b6f35a76SGreg Roach     * @return ReportBaseHtml
479*b6f35a76SGreg Roach     */
480*b6f35a76SGreg Roach    public function createHTML(string $tag, array $attrs): ReportBaseHtml
481*b6f35a76SGreg Roach    {
482*b6f35a76SGreg Roach        return new ReportHtmlHtml($tag, $attrs);
483*b6f35a76SGreg Roach    }
484*b6f35a76SGreg Roach
485*b6f35a76SGreg Roach    /**
486*b6f35a76SGreg Roach     * Clear the Header
487*b6f35a76SGreg Roach     *
488*b6f35a76SGreg Roach     * @return void
489*b6f35a76SGreg Roach     */
490*b6f35a76SGreg Roach    public function clearHeader(): void
491*b6f35a76SGreg Roach    {
492*b6f35a76SGreg Roach        $this->headerElements = [];
493*b6f35a76SGreg Roach    }
494*b6f35a76SGreg Roach
495*b6f35a76SGreg Roach    /**
496*b6f35a76SGreg Roach     * Update the Page Number and set a new Y if max Y is larger - ReportHtml
497*b6f35a76SGreg Roach     *
498*b6f35a76SGreg Roach     * @return void
499*b6f35a76SGreg Roach     */
500*b6f35a76SGreg Roach    public function addPage(): void
501*b6f35a76SGreg Roach    {
502*b6f35a76SGreg Roach        $this->pageN++;
503*b6f35a76SGreg Roach
504*b6f35a76SGreg Roach        // Add a little margin to max Y "between pages"
505*b6f35a76SGreg Roach        $this->maxY += 10;
506*b6f35a76SGreg Roach
507*b6f35a76SGreg Roach        // If Y is still heigher by any reason...
508*b6f35a76SGreg Roach        if ($this->maxY < $this->Y) {
509*b6f35a76SGreg Roach            // ... update max Y
510*b6f35a76SGreg Roach            $this->maxY = $this->Y;
511*b6f35a76SGreg Roach        } else {
512*b6f35a76SGreg Roach            // else update Y so that nothing will be overwritten, like images or cells...
513*b6f35a76SGreg Roach            $this->Y = $this->maxY;
514*b6f35a76SGreg Roach        }
515*b6f35a76SGreg Roach    }
516*b6f35a76SGreg Roach
517*b6f35a76SGreg Roach    /**
518*b6f35a76SGreg Roach     * Uppdate max Y to keep track it incase of a pagebreak - ReportHtml
519*b6f35a76SGreg Roach     *
520*b6f35a76SGreg Roach     * @param float $y
521*b6f35a76SGreg Roach     *
522*b6f35a76SGreg Roach     * @return void
523*b6f35a76SGreg Roach     */
524*b6f35a76SGreg Roach    public function addMaxY($y): void
525*b6f35a76SGreg Roach    {
526*b6f35a76SGreg Roach        if ($this->maxY < $y) {
527*b6f35a76SGreg Roach            $this->maxY = $y;
528*b6f35a76SGreg Roach        }
529*b6f35a76SGreg Roach    }
530*b6f35a76SGreg Roach
531*b6f35a76SGreg Roach    /**
532*b6f35a76SGreg Roach     * Add a page header.
533*b6f35a76SGreg Roach     *
534*b6f35a76SGreg Roach     * @param ReportBaseElement $element
535*b6f35a76SGreg Roach     *
536*b6f35a76SGreg Roach     * @return void
537*b6f35a76SGreg Roach     */
538*b6f35a76SGreg Roach    public function addPageHeader($element): void
539*b6f35a76SGreg Roach    {
540*b6f35a76SGreg Roach        $this->pageHeaderElements[] = $element;
541*b6f35a76SGreg Roach    }
542*b6f35a76SGreg Roach
543*b6f35a76SGreg Roach    /**
544*b6f35a76SGreg Roach     * Checks the Footnote and numbers them - ReportHtml
545*b6f35a76SGreg Roach     *
546*b6f35a76SGreg Roach     * @param ReportHtmlFootnote $footnote
547*b6f35a76SGreg Roach     *
548*b6f35a76SGreg Roach     * @return ReportHtmlFootnote|bool object if already numbered, false otherwise
549*b6f35a76SGreg Roach     */
550*b6f35a76SGreg Roach    public function checkFootnote(ReportHtmlFootnote $footnote)
551*b6f35a76SGreg Roach    {
552*b6f35a76SGreg Roach        $ct  = count($this->printedfootnotes);
553*b6f35a76SGreg Roach        $i   = 0;
554*b6f35a76SGreg Roach        $val = $footnote->getValue();
555*b6f35a76SGreg Roach        while ($i < $ct) {
556*b6f35a76SGreg Roach            if ($this->printedfootnotes[$i]->getValue() === $val) {
557*b6f35a76SGreg Roach                // If this footnote already exist then set up the numbers for this object
558*b6f35a76SGreg Roach                $footnote->setNum($i + 1);
559*b6f35a76SGreg Roach                $footnote->setAddlink((string) ($i + 1));
560*b6f35a76SGreg Roach
561*b6f35a76SGreg Roach                return $this->printedfootnotes[$i];
562*b6f35a76SGreg Roach            }
563*b6f35a76SGreg Roach            $i++;
564*b6f35a76SGreg Roach        }
565*b6f35a76SGreg Roach        // If this Footnote has not been set up yet
566*b6f35a76SGreg Roach        $footnote->setNum($ct + 1);
567*b6f35a76SGreg Roach        $footnote->setAddlink((string) ($ct + 1));
568*b6f35a76SGreg Roach        $this->printedfootnotes[] = $footnote;
569*b6f35a76SGreg Roach
570*b6f35a76SGreg Roach        return false;
571*b6f35a76SGreg Roach    }
572*b6f35a76SGreg Roach
573*b6f35a76SGreg Roach    /**
574*b6f35a76SGreg Roach     * Clear the Page Header - ReportHtml
575*b6f35a76SGreg Roach     *
576*b6f35a76SGreg Roach     * @return void
577*b6f35a76SGreg Roach     */
578*b6f35a76SGreg Roach    public function clearPageHeader(): void
579*b6f35a76SGreg Roach    {
580*b6f35a76SGreg Roach        $this->pageHeaderElements = [];
581*b6f35a76SGreg Roach    }
582*b6f35a76SGreg Roach
583*b6f35a76SGreg Roach    /**
584*b6f35a76SGreg Roach     * Count the number of lines - ReportHtml
585*b6f35a76SGreg Roach     *
586*b6f35a76SGreg Roach     * @param string $str
587*b6f35a76SGreg Roach     *
588*b6f35a76SGreg Roach     * @return int Number of lines. 0 if empty line
589*b6f35a76SGreg Roach     */
590*b6f35a76SGreg Roach    public function countLines($str): int
591*b6f35a76SGreg Roach    {
592*b6f35a76SGreg Roach        if ($str === '') {
593*b6f35a76SGreg Roach            return 0;
594*b6f35a76SGreg Roach        }
595*b6f35a76SGreg Roach
596*b6f35a76SGreg Roach        return substr_count($str, "\n") + 1;
597*b6f35a76SGreg Roach    }
598*b6f35a76SGreg Roach
599*b6f35a76SGreg Roach    /**
600*b6f35a76SGreg Roach     * Get the current style.
601*b6f35a76SGreg Roach     *
602*b6f35a76SGreg Roach     * @return string
603*b6f35a76SGreg Roach     */
604*b6f35a76SGreg Roach    public function getCurrentStyle(): string
605*b6f35a76SGreg Roach    {
606*b6f35a76SGreg Roach        return $this->currentStyle;
607*b6f35a76SGreg Roach    }
608*b6f35a76SGreg Roach
609*b6f35a76SGreg Roach    /**
610*b6f35a76SGreg Roach     * Get the current style height.
611*b6f35a76SGreg Roach     *
612*b6f35a76SGreg Roach     * @return float
613*b6f35a76SGreg Roach     */
614*b6f35a76SGreg Roach    public function getCurrentStyleHeight(): float
615*b6f35a76SGreg Roach    {
616*b6f35a76SGreg Roach        if (empty($this->currentStyle)) {
617*b6f35a76SGreg Roach            return $this->default_font_size;
618*b6f35a76SGreg Roach        }
619*b6f35a76SGreg Roach        $style = $this->getStyle($this->currentStyle);
620*b6f35a76SGreg Roach
621*b6f35a76SGreg Roach        return (float) $style['size'];
622*b6f35a76SGreg Roach    }
623*b6f35a76SGreg Roach
624*b6f35a76SGreg Roach    /**
625*b6f35a76SGreg Roach     * Get the current footnotes height.
626*b6f35a76SGreg Roach     *
627*b6f35a76SGreg Roach     * @param float $cellWidth
628*b6f35a76SGreg Roach     *
629*b6f35a76SGreg Roach     * @return float
630*b6f35a76SGreg Roach     */
631*b6f35a76SGreg Roach    public function getFootnotesHeight(float $cellWidth): float
632*b6f35a76SGreg Roach    {
633*b6f35a76SGreg Roach        $h = 0;
634*b6f35a76SGreg Roach        foreach ($this->printedfootnotes as $element) {
635*b6f35a76SGreg Roach            $h += $element->getFootnoteHeight($this, $cellWidth);
636*b6f35a76SGreg Roach        }
637*b6f35a76SGreg Roach
638*b6f35a76SGreg Roach        return $h;
639*b6f35a76SGreg Roach    }
640*b6f35a76SGreg Roach
641*b6f35a76SGreg Roach    /**
642*b6f35a76SGreg Roach     * Get the maximum width from current position to the margin - ReportHtml
643*b6f35a76SGreg Roach     *
644*b6f35a76SGreg Roach     * @return float
645*b6f35a76SGreg Roach     */
646*b6f35a76SGreg Roach    public function getRemainingWidth(): float
647*b6f35a76SGreg Roach    {
648*b6f35a76SGreg Roach        return $this->noMarginWidth - $this->X;
649*b6f35a76SGreg Roach    }
650*b6f35a76SGreg Roach
651*b6f35a76SGreg Roach    /**
652*b6f35a76SGreg Roach     * Get the page height.
653*b6f35a76SGreg Roach     *
654*b6f35a76SGreg Roach     * @return float
655*b6f35a76SGreg Roach     */
656*b6f35a76SGreg Roach    public function getPageHeight(): float
657*b6f35a76SGreg Roach    {
658*b6f35a76SGreg Roach        return $this->page_height - $this->top_margin;
659*b6f35a76SGreg Roach    }
660*b6f35a76SGreg Roach
661*b6f35a76SGreg Roach    /**
662*b6f35a76SGreg Roach     * Get the width of a string.
663*b6f35a76SGreg Roach     *
664*b6f35a76SGreg Roach     * @param string $text
665*b6f35a76SGreg Roach     *
666*b6f35a76SGreg Roach     * @return float
667*b6f35a76SGreg Roach     */
668*b6f35a76SGreg Roach    public function getStringWidth(string $text): float
669*b6f35a76SGreg Roach    {
670*b6f35a76SGreg Roach        $style = $this->getStyle($this->currentStyle);
671*b6f35a76SGreg Roach
672*b6f35a76SGreg Roach        return mb_strlen($text) * ($style['size'] / 2);
673*b6f35a76SGreg Roach    }
674*b6f35a76SGreg Roach
675*b6f35a76SGreg Roach    /**
676*b6f35a76SGreg Roach     * Get a text height in points - ReportHtml
677*b6f35a76SGreg Roach     *
678*b6f35a76SGreg Roach     * @param string $str
679*b6f35a76SGreg Roach     *
680*b6f35a76SGreg Roach     * @return float
681*b6f35a76SGreg Roach     */
682*b6f35a76SGreg Roach    public function getTextCellHeight(string $str): float
683*b6f35a76SGreg Roach    {
684*b6f35a76SGreg Roach        // Count the number of lines to calculate the height
685*b6f35a76SGreg Roach        $nl = $this->countLines($str);
686*b6f35a76SGreg Roach
687*b6f35a76SGreg Roach        // Calculate the cell height
688*b6f35a76SGreg Roach        return ceil(($this->getCurrentStyleHeight() * $this->cellHeightRatio) * $nl);
689*b6f35a76SGreg Roach    }
690*b6f35a76SGreg Roach
691*b6f35a76SGreg Roach    /**
692*b6f35a76SGreg Roach     * Get the current X position - ReportHtml
693*b6f35a76SGreg Roach     *
694*b6f35a76SGreg Roach     * @return float
695*b6f35a76SGreg Roach     */
696*b6f35a76SGreg Roach    public function getX(): float
697*b6f35a76SGreg Roach    {
698*b6f35a76SGreg Roach        return $this->X;
699*b6f35a76SGreg Roach    }
700*b6f35a76SGreg Roach
701*b6f35a76SGreg Roach    /**
702*b6f35a76SGreg Roach     * Get the current Y position - ReportHtml
703*b6f35a76SGreg Roach     *
704*b6f35a76SGreg Roach     * @return float
705*b6f35a76SGreg Roach     */
706*b6f35a76SGreg Roach    public function getY(): float
707*b6f35a76SGreg Roach    {
708*b6f35a76SGreg Roach        return $this->Y;
709*b6f35a76SGreg Roach    }
710*b6f35a76SGreg Roach
711*b6f35a76SGreg Roach    /**
712*b6f35a76SGreg Roach     * Get the current page number - ReportHtml
713*b6f35a76SGreg Roach     *
714*b6f35a76SGreg Roach     * @return int
715*b6f35a76SGreg Roach     */
716*b6f35a76SGreg Roach    public function pageNo(): int
717*b6f35a76SGreg Roach    {
718*b6f35a76SGreg Roach        return $this->pageN;
719*b6f35a76SGreg Roach    }
720*b6f35a76SGreg Roach
721*b6f35a76SGreg Roach    /**
722*b6f35a76SGreg Roach     * Set the current style.
723*b6f35a76SGreg Roach     *
724*b6f35a76SGreg Roach     * @param string $s
725*b6f35a76SGreg Roach     *
726*b6f35a76SGreg Roach     * @void
727*b6f35a76SGreg Roach     */
728*b6f35a76SGreg Roach    public function setCurrentStyle(string $s): void
729*b6f35a76SGreg Roach    {
730*b6f35a76SGreg Roach        $this->currentStyle = $s;
731*b6f35a76SGreg Roach    }
732*b6f35a76SGreg Roach
733*b6f35a76SGreg Roach    /**
734*b6f35a76SGreg Roach     * Set the X position - ReportHtml
735*b6f35a76SGreg Roach     *
736*b6f35a76SGreg Roach     * @param float $x
737*b6f35a76SGreg Roach     *
738*b6f35a76SGreg Roach     * @return void
739*b6f35a76SGreg Roach     */
740*b6f35a76SGreg Roach    public function setX($x): void
741*b6f35a76SGreg Roach    {
742*b6f35a76SGreg Roach        $this->X = $x;
743*b6f35a76SGreg Roach    }
744*b6f35a76SGreg Roach
745*b6f35a76SGreg Roach    /**
746*b6f35a76SGreg Roach     * Set the Y position - ReportHtml
747*b6f35a76SGreg Roach     *
748*b6f35a76SGreg Roach     * Also updates Max Y position
749*b6f35a76SGreg Roach     *
750*b6f35a76SGreg Roach     * @param float $y
751*b6f35a76SGreg Roach     *
752*b6f35a76SGreg Roach     * @return void
753*b6f35a76SGreg Roach     */
754*b6f35a76SGreg Roach    public function setY($y): void
755*b6f35a76SGreg Roach    {
756*b6f35a76SGreg Roach        $this->Y = $y;
757*b6f35a76SGreg Roach        if ($this->maxY < $y) {
758*b6f35a76SGreg Roach            $this->maxY = $y;
759*b6f35a76SGreg Roach        }
760*b6f35a76SGreg Roach    }
761*b6f35a76SGreg Roach
762*b6f35a76SGreg Roach    /**
763*b6f35a76SGreg Roach     * Set the X and Y position - ReportHtml
764*b6f35a76SGreg Roach     *
765*b6f35a76SGreg Roach     * Also updates Max Y position
766*b6f35a76SGreg Roach     *
767*b6f35a76SGreg Roach     * @param float $x
768*b6f35a76SGreg Roach     * @param float $y
769*b6f35a76SGreg Roach     *
770*b6f35a76SGreg Roach     * @return void
771*b6f35a76SGreg Roach     */
772*b6f35a76SGreg Roach    public function setXy($x, $y): void
773*b6f35a76SGreg Roach    {
774*b6f35a76SGreg Roach        $this->setX($x);
775*b6f35a76SGreg Roach        $this->setY($y);
776*b6f35a76SGreg Roach    }
777*b6f35a76SGreg Roach
778*b6f35a76SGreg Roach    /**
779*b6f35a76SGreg Roach     * Wrap text - ReportHtml
780*b6f35a76SGreg Roach     *
781*b6f35a76SGreg Roach     * @param string $str   Text to wrap
782*b6f35a76SGreg Roach     * @param float  $width Width in points the text has to fit into
783*b6f35a76SGreg Roach     *
784*b6f35a76SGreg Roach     * @return string
785*b6f35a76SGreg Roach     */
786*b6f35a76SGreg Roach    public function textWrap(string $str, float $width): string
787*b6f35a76SGreg Roach    {
788*b6f35a76SGreg Roach        // Calculate the line width
789*b6f35a76SGreg Roach        $lw = (int) ($width / ($this->getCurrentStyleHeight() / 2));
790*b6f35a76SGreg Roach        // Wordwrap each line
791*b6f35a76SGreg Roach        $lines = explode("\n", $str);
792*b6f35a76SGreg Roach        // Line Feed counter
793*b6f35a76SGreg Roach        $lfct     = count($lines);
794*b6f35a76SGreg Roach        $wraptext = '';
795*b6f35a76SGreg Roach        foreach ($lines as $line) {
796*b6f35a76SGreg Roach            $wtext = FunctionsRtl::utf8WordWrap($line, $lw, "\n", true);
797*b6f35a76SGreg Roach            $wraptext .= $wtext;
798*b6f35a76SGreg Roach            // Add a new line as long as it’s not the last line
799*b6f35a76SGreg Roach            if ($lfct > 1) {
800*b6f35a76SGreg Roach                $wraptext .= "\n";
801*b6f35a76SGreg Roach            }
802*b6f35a76SGreg Roach            $lfct--;
803*b6f35a76SGreg Roach        }
804*b6f35a76SGreg Roach
805*b6f35a76SGreg Roach        return $wraptext;
806*b6f35a76SGreg Roach    }
807*b6f35a76SGreg Roach
808*b6f35a76SGreg Roach    /**
809*b6f35a76SGreg Roach     * Write text - ReportHtml
810*b6f35a76SGreg Roach     *
811*b6f35a76SGreg Roach     * @param string $text  Text to print
812*b6f35a76SGreg Roach     * @param string $color HTML RGB color code (Ex: #001122)
813*b6f35a76SGreg Roach     * @param bool   $useclass
814*b6f35a76SGreg Roach     *
815*b6f35a76SGreg Roach     * @return void
816*b6f35a76SGreg Roach     */
817*b6f35a76SGreg Roach    public function write($text, $color = '', $useclass = true): void
818*b6f35a76SGreg Roach    {
819*b6f35a76SGreg Roach        $style    = $this->getStyle($this->getCurrentStyle());
820*b6f35a76SGreg Roach        $htmlcode = '<span dir="' . I18N::direction() . '"';
821*b6f35a76SGreg Roach        if ($useclass) {
822*b6f35a76SGreg Roach            $htmlcode .= ' class="' . $style['name'] . '"';
823*b6f35a76SGreg Roach        }
824*b6f35a76SGreg Roach        // Check if Text Color is set and if it’s valid HTML color
825*b6f35a76SGreg Roach        if (preg_match('/#?(..)(..)(..)/', $color)) {
826*b6f35a76SGreg Roach            $htmlcode .= ' style="color:' . $color . ';"';
827*b6f35a76SGreg Roach        }
828*b6f35a76SGreg Roach
829*b6f35a76SGreg Roach        $htmlcode .= '>' . $text . '</span>';
830*b6f35a76SGreg Roach        $htmlcode = str_replace([
831*b6f35a76SGreg Roach            "\n",
832*b6f35a76SGreg Roach            '> ',
833*b6f35a76SGreg Roach            ' <',
834*b6f35a76SGreg Roach        ], [
835*b6f35a76SGreg Roach            '<br>',
836*b6f35a76SGreg Roach            '>&nbsp;',
837*b6f35a76SGreg Roach            '&nbsp;<',
838*b6f35a76SGreg Roach        ], $htmlcode);
839*b6f35a76SGreg Roach        echo $htmlcode;
840*b6f35a76SGreg Roach    }
841*b6f35a76SGreg Roach}
842