Passed
Push — master ( d12d76...a43d60 )
by Greg
05:47
created

HtmlRenderer::createTextBox()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 12
dl 0
loc 15
rs 10
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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