Failed Conditions
Pull Request — master (#1694)
by Adrien
08:05
created

Html::generateRowWriteCell()   B

Complexity

Conditions 9
Paths 10

Size

Total Lines 58
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 9

Importance

Changes 0
Metric Value
cc 9
eloc 32
c 0
b 0
f 0
nc 10
nop 11
dl 0
loc 58
rs 8.0555
ccs 31
cts 31
cp 1
crap 9

How to fix   Long Method    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
namespace PhpOffice\PhpSpreadsheet\Writer;
4
5
use HTMLPurifier;
6
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
7
use PhpOffice\PhpSpreadsheet\Cell\Cell;
8
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
9
use PhpOffice\PhpSpreadsheet\Chart\Chart;
10
use PhpOffice\PhpSpreadsheet\RichText\RichText;
11
use PhpOffice\PhpSpreadsheet\RichText\Run;
12
use PhpOffice\PhpSpreadsheet\Settings;
13
use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing;
14
use PhpOffice\PhpSpreadsheet\Shared\File;
15
use PhpOffice\PhpSpreadsheet\Shared\Font as SharedFont;
16
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
17
use PhpOffice\PhpSpreadsheet\Spreadsheet;
18
use PhpOffice\PhpSpreadsheet\Style\Alignment;
19
use PhpOffice\PhpSpreadsheet\Style\Border;
20
use PhpOffice\PhpSpreadsheet\Style\Borders;
21
use PhpOffice\PhpSpreadsheet\Style\Fill;
22
use PhpOffice\PhpSpreadsheet\Style\Font;
23
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
24
use PhpOffice\PhpSpreadsheet\Style\Style;
25
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
26
use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
27
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
28
29
class Html extends BaseWriter
30
{
31
    /**
32
     * Spreadsheet object.
33
     *
34
     * @var Spreadsheet
35
     */
36
    protected $spreadsheet;
37
38
    /**
39
     * Sheet index to write.
40
     *
41
     * @var null|int
42
     */
43
    private $sheetIndex = 0;
44
45
    /**
46
     * Images root.
47
     *
48
     * @var string
49
     */
50
    private $imagesRoot = '';
51
52
    /**
53
     * embed images, or link to images.
54
     *
55
     * @var bool
56
     */
57
    private $embedImages = false;
58
59
    /**
60
     * Use inline CSS?
61
     *
62
     * @var bool
63
     */
64
    private $useInlineCss = false;
65
66
    /**
67
     * Use embedded CSS?
68
     *
69
     * @var bool
70
     */
71
    private $useEmbeddedCSS = true;
72
73
    /**
74
     * Array of CSS styles.
75
     *
76
     * @var array
77
     */
78
    private $cssStyles;
79
80
    /**
81
     * Array of column widths in points.
82
     *
83
     * @var array
84
     */
85
    private $columnWidths;
86
87
    /**
88
     * Default font.
89
     *
90
     * @var Font
91
     */
92
    private $defaultFont;
93
94
    /**
95
     * Flag whether spans have been calculated.
96
     *
97
     * @var bool
98
     */
99
    private $spansAreCalculated = false;
100
101
    /**
102
     * Excel cells that should not be written as HTML cells.
103
     *
104
     * @var array
105
     */
106
    private $isSpannedCell = [];
107
108
    /**
109
     * Excel cells that are upper-left corner in a cell merge.
110
     *
111
     * @var array
112
     */
113
    private $isBaseCell = [];
114
115
    /**
116
     * Excel rows that should not be written as HTML rows.
117
     *
118
     * @var array
119
     */
120
    private $isSpannedRow = [];
121
122
    /**
123
     * Is the current writer creating PDF?
124
     *
125
     * @var bool
126
     */
127
    protected $isPdf = false;
128
129
    /**
130
     * Generate the Navigation block.
131
     *
132
     * @var bool
133
     */
134
    private $generateSheetNavigationBlock = true;
135
136
    /**
137
     * Callback for editing generated html.
138
     *
139
     * @var null|callable
140
     */
141
    private $editHtmlCallback;
142
143
    /**
144
     * Create a new HTML.
145
     */
146 185
    public function __construct(Spreadsheet $spreadsheet)
147
    {
148 185
        $this->spreadsheet = $spreadsheet;
149 185
        $this->defaultFont = $this->spreadsheet->getDefaultStyle()->getFont();
150 185
    }
151
152
    /**
153
     * Save Spreadsheet to file.
154
     *
155
     * @param resource|string $filename
156
     */
157 160
    public function save($filename, int $flags = 0): void
158
    {
159 160
        $this->processFlags($flags);
160
161
        // Open file
162 160
        $this->openFileHandle($filename);
163
164
        // Write html
165 159
        fwrite($this->fileHandle, $this->generateHTMLAll());
166
167
        // Close file
168 159
        $this->maybeCloseFileHandle();
169 159
    }
170
171
    /**
172
     * Save Spreadsheet as html to variable.
173
     *
174
     * @return string
175
     */
176 176
    public function generateHtmlAll()
177
    {
178
        // garbage collect
179 176
        $this->spreadsheet->garbageCollect();
180
181 176
        $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog();
182 176
        Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false);
183 176
        $saveArrayReturnType = Calculation::getArrayReturnType();
184 176
        Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE);
185
186
        // Build CSS
187 176
        $this->buildCSS(!$this->useInlineCss);
188
189 176
        $html = '';
190
191
        // Write headers
192 176
        $html .= $this->generateHTMLHeader(!$this->useInlineCss);
193
194
        // Write navigation (tabs)
195 176
        if ((!$this->isPdf) && ($this->generateSheetNavigationBlock)) {
196 166
            $html .= $this->generateNavigation();
197
        }
198
199
        // Write data
200 176
        $html .= $this->generateSheetData();
201
202
        // Write footer
203 176
        $html .= $this->generateHTMLFooter();
204 176
        $callback = $this->editHtmlCallback;
205 176
        if ($callback) {
206 4
            $html = $callback($html);
207
        }
208
209 176
        Calculation::setArrayReturnType($saveArrayReturnType);
210 176
        Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
211
212 176
        return $html;
213
    }
214
215
    /**
216
     * Set a callback to edit the entire HTML.
217
     *
218
     * The callback must accept the HTML as string as first parameter,
219
     * and it must return the edited HTML as string.
220
     */
221 4
    public function setEditHtmlCallback(?callable $callback): void
222
    {
223 4
        $this->editHtmlCallback = $callback;
224 4
    }
225
226
    const VALIGN_ARR = [
227
        Alignment::VERTICAL_BOTTOM => 'bottom',
228
        Alignment::VERTICAL_TOP => 'top',
229
        Alignment::VERTICAL_CENTER => 'middle',
230
        Alignment::VERTICAL_JUSTIFY => 'middle',
231
    ];
232
233
    /**
234
     * Map VAlign.
235
     *
236
     * @param string $vAlign Vertical alignment
237
     *
238
     * @return string
239
     */
240 176
    private function mapVAlign($vAlign)
241
    {
242 176
        return array_key_exists($vAlign, self::VALIGN_ARR) ? self::VALIGN_ARR[$vAlign] : 'baseline';
243
    }
244
245
    const HALIGN_ARR = [
246
        Alignment::HORIZONTAL_LEFT => 'left',
247
        Alignment::HORIZONTAL_RIGHT => 'right',
248
        Alignment::HORIZONTAL_CENTER => 'center',
249
        Alignment::HORIZONTAL_CENTER_CONTINUOUS => 'center',
250
        Alignment::HORIZONTAL_JUSTIFY => 'justify',
251
    ];
252
253
    /**
254
     * Map HAlign.
255
     *
256
     * @param string $hAlign Horizontal alignment
257
     *
258
     * @return string
259
     */
260 176
    private function mapHAlign($hAlign)
261
    {
262 176
        return array_key_exists($hAlign, self::HALIGN_ARR) ? self::HALIGN_ARR[$hAlign] : '';
263
    }
264
265
    const BORDER_ARR = [
266
        Border::BORDER_NONE => 'none',
267
        Border::BORDER_DASHDOT => '1px dashed',
268
        Border::BORDER_DASHDOTDOT => '1px dotted',
269
        Border::BORDER_DASHED => '1px dashed',
270
        Border::BORDER_DOTTED => '1px dotted',
271
        Border::BORDER_DOUBLE => '3px double',
272
        Border::BORDER_HAIR => '1px solid',
273
        Border::BORDER_MEDIUM => '2px solid',
274
        Border::BORDER_MEDIUMDASHDOT => '2px dashed',
275
        Border::BORDER_MEDIUMDASHDOTDOT => '2px dotted',
276
        Border::BORDER_SLANTDASHDOT => '2px dashed',
277
        Border::BORDER_THICK => '3px solid',
278
    ];
279
280
    /**
281
     * Map border style.
282
     *
283
     * @param int $borderStyle Sheet index
284
     *
285
     * @return string
286
     */
287 176
    private function mapBorderStyle($borderStyle)
288
    {
289 176
        return array_key_exists($borderStyle, self::BORDER_ARR) ? self::BORDER_ARR[$borderStyle] : '1px solid';
290
    }
291
292
    /**
293
     * Get sheet index.
294
     *
295
     * @return int
296
     */
297 9
    public function getSheetIndex()
298
    {
299 9
        return $this->sheetIndex;
300
    }
301
302
    /**
303
     * Set sheet index.
304
     *
305
     * @param int $pValue Sheet index
306
     *
307
     * @return $this
308
     */
309 1
    public function setSheetIndex($pValue)
310
    {
311 1
        $this->sheetIndex = $pValue;
312
313 1
        return $this;
314
    }
315
316
    /**
317
     * Get sheet index.
318
     *
319
     * @return bool
320
     */
321 1
    public function getGenerateSheetNavigationBlock()
322
    {
323 1
        return $this->generateSheetNavigationBlock;
324
    }
325
326
    /**
327
     * Set sheet index.
328
     *
329
     * @param bool $pValue Flag indicating whether the sheet navigation block should be generated or not
330
     *
331
     * @return $this
332
     */
333 1
    public function setGenerateSheetNavigationBlock($pValue)
334
    {
335 1
        $this->generateSheetNavigationBlock = (bool) $pValue;
336
337 1
        return $this;
338
    }
339
340
    /**
341
     * Write all sheets (resets sheetIndex to NULL).
342
     *
343
     * @return $this
344
     */
345 7
    public function writeAllSheets()
346
    {
347 7
        $this->sheetIndex = null;
348
349 7
        return $this;
350
    }
351
352 176
    private static function generateMeta($val, $desc)
353
    {
354 176
        return $val
355 176
            ? ('      <meta name="' . $desc . '" content="' . htmlspecialchars($val, Settings::htmlEntityFlags()) . '" />' . PHP_EOL)
356 176
            : '';
357
    }
358
359
    /**
360
     * Generate HTML header.
361
     *
362
     * @param bool $pIncludeStyles Include styles?
363
     *
364
     * @return string
365
     */
366 176
    public function generateHTMLHeader($pIncludeStyles = false)
367
    {
368
        // Construct HTML
369 176
        $properties = $this->spreadsheet->getProperties();
370 176
        $html = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . PHP_EOL;
371 176
        $html .= '<html xmlns="http://www.w3.org/1999/xhtml">' . PHP_EOL;
372 176
        $html .= '  <head>' . PHP_EOL;
373 176
        $html .= '      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . PHP_EOL;
374 176
        $html .= '      <meta name="generator" content="PhpSpreadsheet, https://github.com/PHPOffice/PhpSpreadsheet" />' . PHP_EOL;
375 176
        $html .= '      <title>' . htmlspecialchars($properties->getTitle(), Settings::htmlEntityFlags()) . '</title>' . PHP_EOL;
376 176
        $html .= self::generateMeta($properties->getCreator(), 'author');
377 176
        $html .= self::generateMeta($properties->getTitle(), 'title');
378 176
        $html .= self::generateMeta($properties->getDescription(), 'description');
379 176
        $html .= self::generateMeta($properties->getSubject(), 'subject');
380 176
        $html .= self::generateMeta($properties->getKeywords(), 'keywords');
381 176
        $html .= self::generateMeta($properties->getCategory(), 'category');
382 176
        $html .= self::generateMeta($properties->getCompany(), 'company');
383 176
        $html .= self::generateMeta($properties->getManager(), 'manager');
384
385 176
        $html .= $pIncludeStyles ? $this->generateStyles(true) : $this->generatePageDeclarations(true);
386
387 176
        $html .= '  </head>' . PHP_EOL;
388 176
        $html .= '' . PHP_EOL;
389 176
        $html .= '  <body>' . PHP_EOL;
390
391 176
        return $html;
392
    }
393
394 176
    private function generateSheetPrep()
395
    {
396
        // Ensure that Spans have been calculated?
397 176
        $this->calculateSpans();
398
399
        // Fetch sheets
400 176
        if ($this->sheetIndex === null) {
401 7
            $sheets = $this->spreadsheet->getAllSheets();
402
        } else {
403 174
            $sheets = [$this->spreadsheet->getSheet($this->sheetIndex)];
404
        }
405
406 176
        return $sheets;
407
    }
408
409 176
    private function generateSheetStarts($sheet, $rowMin)
410
    {
411
        // calculate start of <tbody>, <thead>
412 176
        $tbodyStart = $rowMin;
413 176
        $theadStart = $theadEnd = 0; // default: no <thead>    no </thead>
414 176
        if ($sheet->getPageSetup()->isRowsToRepeatAtTopSet()) {
415 2
            $rowsToRepeatAtTop = $sheet->getPageSetup()->getRowsToRepeatAtTop();
416
417
            // we can only support repeating rows that start at top row
418 2
            if ($rowsToRepeatAtTop[0] == 1) {
419 2
                $theadStart = $rowsToRepeatAtTop[0];
420 2
                $theadEnd = $rowsToRepeatAtTop[1];
421 2
                $tbodyStart = $rowsToRepeatAtTop[1] + 1;
422
            }
423
        }
424
425 176
        return [$theadStart, $theadEnd, $tbodyStart];
426
    }
427
428 176
    private function generateSheetTags($row, $theadStart, $theadEnd, $tbodyStart)
429
    {
430
        // <thead> ?
431 176
        $startTag = ($row == $theadStart) ? ('        <thead>' . PHP_EOL) : '';
432 176
        if (!$startTag) {
433 176
            $startTag = ($row == $tbodyStart) ? ('        <tbody>' . PHP_EOL) : '';
434
        }
435 176
        $endTag = ($row == $theadEnd) ? ('        </thead>' . PHP_EOL) : '';
436 176
        $cellType = ($row >= $tbodyStart) ? 'td' : 'th';
437
438 176
        return [$cellType, $startTag, $endTag];
439
    }
440
441
    /**
442
     * Generate sheet data.
443
     *
444
     * @return string
445
     */
446 176
    public function generateSheetData()
447
    {
448 176
        $sheets = $this->generateSheetPrep();
449
450
        // Construct HTML
451 176
        $html = '';
452
453
        // Loop all sheets
454 176
        $sheetId = 0;
455 176
        foreach ($sheets as $sheet) {
456
            // Write table header
457 176
            $html .= $this->generateTableHeader($sheet);
458
459
            // Get worksheet dimension
460 176
            [$min, $max] = explode(':', $sheet->calculateWorksheetDataDimension());
461 176
            [$minCol, $minRow] = Coordinate::indexesFromString($min);
462 176
            [$maxCol, $maxRow] = Coordinate::indexesFromString($max);
463
464 176
            [$theadStart, $theadEnd, $tbodyStart] = $this->generateSheetStarts($sheet, $minRow);
465
466
            // Loop through cells
467 176
            $row = $minRow - 1;
468 176
            while ($row++ < $maxRow) {
469 176
                [$cellType, $startTag, $endTag] = $this->generateSheetTags($row, $theadStart, $theadEnd, $tbodyStart);
470 176
                $html .= $startTag;
471
472
                // Write row if there are HTML table cells in it
473 176
                if (!isset($this->isSpannedRow[$sheet->getParent()->getIndex($sheet)][$row])) {
474
                    // Start a new rowData
475 176
                    $rowData = [];
476
                    // Loop through columns
477 176
                    $column = $minCol;
478 176
                    while ($column <= $maxCol) {
479
                        // Cell exists?
480 176
                        if ($sheet->cellExistsByColumnAndRow($column, $row)) {
481 175
                            $rowData[$column] = Coordinate::stringFromColumnIndex($column) . $row;
482
                        } else {
483 20
                            $rowData[$column] = '';
484
                        }
485 176
                        ++$column;
486
                    }
487 176
                    $html .= $this->generateRow($sheet, $rowData, $row - 1, $cellType);
488
                }
489
490 176
                $html .= $endTag;
491
            }
492 176
            $html .= $this->extendRowsForChartsAndImages($sheet, $row);
493
494
            // Write table footer
495 176
            $html .= $this->generateTableFooter();
496
            // Writing PDF?
497 176
            if ($this->isPdf && $this->useInlineCss) {
498 4
                if ($this->sheetIndex === null && $sheetId + 1 < $this->spreadsheet->getSheetCount()) {
499 1
                    $html .= '<div style="page-break-before:always" ></div>';
500
                }
501
            }
502
503
            // Next sheet
504 176
            ++$sheetId;
505
        }
506
507 176
        return $html;
508
    }
509
510
    /**
511
     * Generate sheet tabs.
512
     *
513
     * @return string
514
     */
515 166
    public function generateNavigation()
516
    {
517
        // Fetch sheets
518 166
        $sheets = [];
519 166
        if ($this->sheetIndex === null) {
520 4
            $sheets = $this->spreadsheet->getAllSheets();
521
        } else {
522 166
            $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex);
523
        }
524
525
        // Construct HTML
526 166
        $html = '';
527
528
        // Only if there are more than 1 sheets
529 166
        if (count($sheets) > 1) {
530
            // Loop all sheets
531 4
            $sheetId = 0;
532
533 4
            $html .= '<ul class="navigation">' . PHP_EOL;
534
535 4
            foreach ($sheets as $sheet) {
536 4
                $html .= '  <li class="sheet' . $sheetId . '"><a href="#sheet' . $sheetId . '">' . $sheet->getTitle() . '</a></li>' . PHP_EOL;
537 4
                ++$sheetId;
538
            }
539
540 4
            $html .= '</ul>' . PHP_EOL;
541
        }
542
543 166
        return $html;
544
    }
545
546
    /**
547
     * Extend Row if chart is placed after nominal end of row.
548
     * This code should be exercised by sample:
549
     * Chart/32_Chart_read_write_PDF.php.
550
     * However, that test is suppressed due to out-of-date
551
     * Jpgraph code issuing warnings. So, don't measure
552
     * code coverage for this function till that is fixed.
553
     *
554
     * @param int $row Row to check for charts
555
     *
556
     * @return array
557
     *
558
     * @codeCoverageIgnore
559
     */
560
    private function extendRowsForCharts(Worksheet $worksheet, int $row)
561
    {
562
        $rowMax = $row;
563
        $colMax = 'A';
564
        $anyfound = false;
565
        if ($this->includeCharts) {
566
            foreach ($worksheet->getChartCollection() as $chart) {
567
                if ($chart instanceof Chart) {
568
                    $anyfound = true;
569
                    $chartCoordinates = $chart->getTopLeftPosition();
570
                    $chartTL = Coordinate::coordinateFromString($chartCoordinates['cell']);
571
                    $chartCol = Coordinate::columnIndexFromString($chartTL[0]);
572
                    if ($chartTL[1] > $rowMax) {
573
                        $rowMax = $chartTL[1];
574
                        if ($chartCol > Coordinate::columnIndexFromString($colMax)) {
575
                            $colMax = $chartTL[0];
576
                        }
577
                    }
578
                }
579
            }
580
        }
581
582
        return [$rowMax, $colMax, $anyfound];
583
    }
584
585 176
    private function extendRowsForChartsAndImages(Worksheet $worksheet, int $row): string
586
    {
587 176
        [$rowMax, $colMax, $anyfound] = $this->extendRowsForCharts($worksheet, $row);
588
589 176
        foreach ($worksheet->getDrawingCollection() as $drawing) {
590 12
            $anyfound = true;
591 12
            $imageTL = Coordinate::coordinateFromString($drawing->getCoordinates());
592 12
            $imageCol = Coordinate::columnIndexFromString($imageTL[0]);
593 12
            if ($imageTL[1] > $rowMax) {
594 1
                $rowMax = $imageTL[1];
595 1
                if ($imageCol > Coordinate::columnIndexFromString($colMax)) {
596 1
                    $colMax = $imageTL[0];
597
                }
598
            }
599
        }
600
601
        // Don't extend rows if not needed
602 176
        if ($row === $rowMax || !$anyfound) {
603 175
            return '';
604
        }
605
606 1
        $html = '';
607 1
        ++$colMax;
608 1
        ++$row;
609 1
        while ($row <= $rowMax) {
610 1
            $html .= '<tr>';
611 1
            for ($col = 'A'; $col != $colMax; ++$col) {
612 1
                $htmlx = $this->writeImageInCell($worksheet, $col . $row);
613 1
                $htmlx .= $this->includeCharts ? $this->writeChartInCell($worksheet, $col . $row) : '';
614 1
                if ($htmlx) {
615 1
                    $html .= "<td class='style0' style='position: relative;'>$htmlx</td>";
616
                } else {
617 1
                    $html .= "<td class='style0'></td>";
618
                }
619
            }
620 1
            ++$row;
621 1
            $html .= '</tr>' . PHP_EOL;
622
        }
623
624 1
        return $html;
625
    }
626
627
    /**
628
     * Convert Windows file name to file protocol URL.
629
     *
630
     * @param string $filename file name on local system
631
     *
632
     * @return string
633
     */
634 12
    public static function winFileToUrl($filename)
635
    {
636
        // Windows filename
637 12
        if (substr($filename, 1, 2) === ':\\') {
638 1
            $filename = 'file:///' . str_replace('\\', '/', $filename);
639
        }
640
641 12
        return $filename;
642
    }
643
644
    /**
645
     * Generate image tag in cell.
646
     *
647
     * @param Worksheet $worksheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
648
     * @param string $coordinates Cell coordinates
649
     *
650
     * @return string
651
     */
652 176
    private function writeImageInCell(Worksheet $worksheet, $coordinates)
653
    {
654
        // Construct HTML
655 176
        $html = '';
656
657
        // Write images
658 176
        foreach ($worksheet->getDrawingCollection() as $drawing) {
659 12
            if ($drawing->getCoordinates() != $coordinates) {
660 12
                continue;
661
            }
662 12
            $filedesc = $drawing->getDescription();
663 12
            $filedesc = $filedesc ? htmlspecialchars($filedesc, ENT_QUOTES) : 'Embedded image';
664 12
            if ($drawing instanceof Drawing) {
665 11
                $filename = $drawing->getPath();
666
667
                // Strip off eventual '.'
668 11
                $filename = preg_replace('/^[.]/', '', $filename);
669
670
                // Prepend images root
671 11
                $filename = $this->getImagesRoot() . $filename;
672
673
                // Strip off eventual '.' if followed by non-/
674 11
                $filename = preg_replace('@^[.]([^/])@', '$1', $filename);
675
676
                // Convert UTF8 data to PCDATA
677 11
                $filename = htmlspecialchars($filename, Settings::htmlEntityFlags());
678
679 11
                $html .= PHP_EOL;
680 11
                $imageData = self::winFileToUrl($filename);
681
682 11
                if ($this->embedImages && !$this->isPdf) {
683 2
                    $picture = @file_get_contents($filename);
684 2
                    if ($picture !== false) {
685 2
                        $imageDetails = getimagesize($filename);
686
                        // base64 encode the binary data
687 2
                        $base64 = base64_encode($picture);
688 2
                        $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64;
689
                    }
690
                }
691
692
                $html .= '<img style="position: absolute; z-index: 1; left: ' .
693 11
                    $drawing->getOffsetX() . 'px; top: ' . $drawing->getOffsetY() . 'px; width: ' .
694 11
                    $drawing->getWidth() . 'px; height: ' . $drawing->getHeight() . 'px;" src="' .
695 11
                    $imageData . '" alt="' . $filedesc . '" />';
696 1
            } elseif ($drawing instanceof MemoryDrawing) {
697 1
                $imageResource = $drawing->getImageResource();
698 1
                if ($imageResource) {
699 1
                    ob_start(); //  Let's start output buffering.
700 1
                    imagepng($imageResource); //  This will normally output the image, but because of ob_start(), it won't.
701 1
                    $contents = ob_get_contents(); //  Instead, output above is saved to $contents
702 1
                    ob_end_clean(); //  End the output buffer.
703
704 1
                    $dataUri = 'data:image/jpeg;base64,' . base64_encode($contents);
705
706
                    //  Because of the nature of tables, width is more important than height.
707
                    //  max-width: 100% ensures that image doesnt overflow containing cell
708
                    //  width: X sets width of supplied image.
709
                    //  As a result, images bigger than cell will be contained and images smaller will not get stretched
710 1
                    $html .= '<img alt="' . $filedesc . '" src="' . $dataUri . '" style="max-width:100%;width:' . $drawing->getWidth() . 'px;" />';
711
                }
712
            }
713
        }
714
715 176
        return $html;
716
    }
717
718
    /**
719
     * Generate chart tag in cell.
720
     * This code should be exercised by sample:
721
     * Chart/32_Chart_read_write_PDF.php.
722
     * However, that test is suppressed due to out-of-date
723
     * Jpgraph code issuing warnings. So, don't measure
724
     * code coverage for this function till that is fixed.
725
     *
726
     * @codeCoverageIgnore
727
     */
728
    private function writeChartInCell(Worksheet $worksheet, string $coordinates): string
729
    {
730
        // Construct HTML
731
        $html = '';
732
733
        // Write charts
734
        foreach ($worksheet->getChartCollection() as $chart) {
735
            if ($chart instanceof Chart) {
736
                $chartCoordinates = $chart->getTopLeftPosition();
737
                if ($chartCoordinates['cell'] == $coordinates) {
738
                    $chartFileName = File::sysGetTempDir() . '/' . uniqid('', true) . '.png';
739
                    if (!$chart->render($chartFileName)) {
740
                        return '';
741
                    }
742
743
                    $html .= PHP_EOL;
744
                    $imageDetails = getimagesize($chartFileName);
745
                    $filedesc = $chart->getTitle();
746
                    $filedesc = $filedesc ? $filedesc->getCaptionText() : '';
747
                    $filedesc = $filedesc ? htmlspecialchars($filedesc, ENT_QUOTES) : 'Embedded chart';
748
                    if ($fp = fopen($chartFileName, 'rb', 0)) {
749
                        $picture = fread($fp, filesize($chartFileName));
750
                        fclose($fp);
751
                        // base64 encode the binary data
752
                        $base64 = base64_encode($picture);
753
                        $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64;
754
755
                        $html .= '<img style="position: absolute; z-index: 1; left: ' . $chartCoordinates['xOffset'] . 'px; top: ' . $chartCoordinates['yOffset'] . 'px; width: ' . $imageDetails[0] . 'px; height: ' . $imageDetails[1] . 'px;" src="' . $imageData . '" alt="' . $filedesc . '" />' . PHP_EOL;
756
757
                        unlink($chartFileName);
758
                    }
759
                }
760
            }
761
        }
762
763
        // Return
764
        return $html;
765
    }
766
767
    /**
768
     * Generate CSS styles.
769
     *
770
     * @param bool $generateSurroundingHTML Generate surrounding HTML tags? (&lt;style&gt; and &lt;/style&gt;)
771
     *
772
     * @return string
773
     */
774 173
    public function generateStyles($generateSurroundingHTML = true)
775
    {
776
        // Build CSS
777 173
        $css = $this->buildCSS($generateSurroundingHTML);
778
779
        // Construct HTML
780 173
        $html = '';
781
782
        // Start styles
783 173
        if ($generateSurroundingHTML) {
784 173
            $html .= '    <style type="text/css">' . PHP_EOL;
785 173
            $html .= (array_key_exists('html', $css)) ? ('      html { ' . $this->assembleCSS($css['html']) . ' }' . PHP_EOL) : '';
786
        }
787
788
        // Write all other styles
789 173
        foreach ($css as $styleName => $styleDefinition) {
790 173
            if ($styleName != 'html') {
791 173
                $html .= '      ' . $styleName . ' { ' . $this->assembleCSS($styleDefinition) . ' }' . PHP_EOL;
792
            }
793
        }
794 173
        $html .= $this->generatePageDeclarations(false);
795
796
        // End styles
797 173
        if ($generateSurroundingHTML) {
798 173
            $html .= '    </style>' . PHP_EOL;
799
        }
800
801
        // Return
802 173
        return $html;
803
    }
804
805 176
    private function buildCssRowHeights(Worksheet $sheet, array &$css, int $sheetIndex): void
806
    {
807
        // Calculate row heights
808 176
        foreach ($sheet->getRowDimensions() as $rowDimension) {
809 6
            $row = $rowDimension->getRowIndex() - 1;
810
811
            // table.sheetN tr.rowYYYYYY { }
812 6
            $css['table.sheet' . $sheetIndex . ' tr.row' . $row] = [];
813
814 6
            if ($rowDimension->getRowHeight() != -1) {
815 2
                $pt_height = $rowDimension->getRowHeight();
816 2
                $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['height'] = $pt_height . 'pt';
817
            }
818 6
            if ($rowDimension->getVisible() === false) {
819 1
                $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['display'] = 'none';
820 1
                $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['visibility'] = 'hidden';
821
            }
822
        }
823 176
    }
824
825 176
    private function buildCssPerSheet(Worksheet $sheet, array &$css): void
826
    {
827
        // Calculate hash code
828 176
        $sheetIndex = $sheet->getParent()->getIndex($sheet);
829
830
        // Build styles
831
        // Calculate column widths
832 176
        $sheet->calculateColumnWidths();
833
834
        // col elements, initialize
835 176
        $highestColumnIndex = Coordinate::columnIndexFromString($sheet->getHighestColumn()) - 1;
836 176
        $column = -1;
837 176
        while ($column++ < $highestColumnIndex) {
838 176
            $this->columnWidths[$sheetIndex][$column] = 42; // approximation
839 176
            $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = '42pt';
840
        }
841
842
        // col elements, loop through columnDimensions and set width
843 176
        foreach ($sheet->getColumnDimensions() as $columnDimension) {
844 15
            $column = Coordinate::columnIndexFromString($columnDimension->getColumnIndex()) - 1;
845 15
            $width = SharedDrawing::cellDimensionToPixels($columnDimension->getWidth(), $this->defaultFont);
846 15
            $width = SharedDrawing::pixelsToPoints($width);
847 15
            if ($columnDimension->getVisible() === false) {
848 2
                $css['table.sheet' . $sheetIndex . ' .column' . $column]['display'] = 'none';
849
            }
850 15
            if ($width >= 0) {
851 12
                $this->columnWidths[$sheetIndex][$column] = $width;
852 12
                $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = $width . 'pt';
853
            }
854
        }
855
856
        // Default row height
857 176
        $rowDimension = $sheet->getDefaultRowDimension();
858
859
        // table.sheetN tr { }
860 176
        $css['table.sheet' . $sheetIndex . ' tr'] = [];
861
862 176
        if ($rowDimension->getRowHeight() == -1) {
863 175
            $pt_height = SharedFont::getDefaultRowHeightByFont($this->spreadsheet->getDefaultStyle()->getFont());
864
        } else {
865 1
            $pt_height = $rowDimension->getRowHeight();
866
        }
867 176
        $css['table.sheet' . $sheetIndex . ' tr']['height'] = $pt_height . 'pt';
868 176
        if ($rowDimension->getVisible() === false) {
869 1
            $css['table.sheet' . $sheetIndex . ' tr']['display'] = 'none';
870 1
            $css['table.sheet' . $sheetIndex . ' tr']['visibility'] = 'hidden';
871
        }
872
873 176
        $this->buildCssRowHeights($sheet, $css, $sheetIndex);
874 176
    }
875
876
    /**
877
     * Build CSS styles.
878
     *
879
     * @param bool $generateSurroundingHTML Generate surrounding HTML style? (html { })
880
     *
881
     * @return array
882
     */
883 176
    public function buildCSS($generateSurroundingHTML = true)
884
    {
885
        // Cached?
886 176
        if ($this->cssStyles !== null) {
887 173
            return $this->cssStyles;
888
        }
889
890
        // Ensure that spans have been calculated
891 176
        $this->calculateSpans();
892
893
        // Construct CSS
894 176
        $css = [];
895
896
        // Start styles
897 176
        if ($generateSurroundingHTML) {
898
            // html { }
899 173
            $css['html']['font-family'] = 'Calibri, Arial, Helvetica, sans-serif';
900 173
            $css['html']['font-size'] = '11pt';
901 173
            $css['html']['background-color'] = 'white';
902
        }
903
904
        // CSS for comments as found in LibreOffice
905 176
        $css['a.comment-indicator:hover + div.comment'] = [
906
            'background' => '#ffd',
907
            'position' => 'absolute',
908
            'display' => 'block',
909
            'border' => '1px solid black',
910
            'padding' => '0.5em',
911
        ];
912
913 176
        $css['a.comment-indicator'] = [
914
            'background' => 'red',
915
            'display' => 'inline-block',
916
            'border' => '1px solid black',
917
            'width' => '0.5em',
918
            'height' => '0.5em',
919
        ];
920
921 176
        $css['div.comment']['display'] = 'none';
922
923
        // table { }
924 176
        $css['table']['border-collapse'] = 'collapse';
925
926
        // .b {}
927 176
        $css['.b']['text-align'] = 'center'; // BOOL
928
929
        // .e {}
930 176
        $css['.e']['text-align'] = 'center'; // ERROR
931
932
        // .f {}
933 176
        $css['.f']['text-align'] = 'right'; // FORMULA
934
935
        // .inlineStr {}
936 176
        $css['.inlineStr']['text-align'] = 'left'; // INLINE
937
938
        // .n {}
939 176
        $css['.n']['text-align'] = 'right'; // NUMERIC
940
941
        // .s {}
942 176
        $css['.s']['text-align'] = 'left'; // STRING
943
944
        // Calculate cell style hashes
945 176
        foreach ($this->spreadsheet->getCellXfCollection() as $index => $style) {
946 176
            $css['td.style' . $index] = $this->createCSSStyle($style);
947 176
            $css['th.style' . $index] = $this->createCSSStyle($style);
948
        }
949
950
        // Fetch sheets
951 176
        $sheets = [];
952 176
        if ($this->sheetIndex === null) {
953 7
            $sheets = $this->spreadsheet->getAllSheets();
954
        } else {
955 174
            $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex);
956
        }
957
958
        // Build styles per sheet
959 176
        foreach ($sheets as $sheet) {
960 176
            $this->buildCssPerSheet($sheet, $css);
961
        }
962
963
        // Cache
964 176
        if ($this->cssStyles === null) {
965 176
            $this->cssStyles = $css;
966
        }
967
968
        // Return
969 176
        return $css;
970
    }
971
972
    /**
973
     * Create CSS style.
974
     *
975
     * @return array
976
     */
977 176
    private function createCSSStyle(Style $pStyle)
978
    {
979
        // Create CSS
980 176
        return array_merge(
981 176
            $this->createCSSStyleAlignment($pStyle->getAlignment()),
982 176
            $this->createCSSStyleBorders($pStyle->getBorders()),
983 176
            $this->createCSSStyleFont($pStyle->getFont()),
984 176
            $this->createCSSStyleFill($pStyle->getFill())
985
        );
986
    }
987
988
    /**
989
     * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Alignment).
990
     *
991
     * @param Alignment $pStyle \PhpOffice\PhpSpreadsheet\Style\Alignment
992
     *
993
     * @return array
994
     */
995 176
    private function createCSSStyleAlignment(Alignment $pStyle)
996
    {
997
        // Construct CSS
998 176
        $css = [];
999
1000
        // Create CSS
1001 176
        $css['vertical-align'] = $this->mapVAlign($pStyle->getVertical());
1002 176
        $textAlign = $this->mapHAlign($pStyle->getHorizontal());
1003 176
        if ($textAlign) {
1004 9
            $css['text-align'] = $textAlign;
1005 9
            if (in_array($textAlign, ['left', 'right'])) {
1006 8
                $css['padding-' . $textAlign] = (string) ((int) $pStyle->getIndent() * 9) . 'px';
1007
            }
1008
        }
1009
1010 176
        return $css;
1011
    }
1012
1013
    /**
1014
     * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Font).
1015
     *
1016
     * @return array
1017
     */
1018 176
    private function createCSSStyleFont(Font $pStyle)
1019
    {
1020
        // Construct CSS
1021 176
        $css = [];
1022
1023
        // Create CSS
1024 176
        if ($pStyle->getBold()) {
1025 9
            $css['font-weight'] = 'bold';
1026
        }
1027 176
        if ($pStyle->getUnderline() != Font::UNDERLINE_NONE && $pStyle->getStrikethrough()) {
1028 1
            $css['text-decoration'] = 'underline line-through';
1029 176
        } elseif ($pStyle->getUnderline() != Font::UNDERLINE_NONE) {
1030 9
            $css['text-decoration'] = 'underline';
1031 176
        } elseif ($pStyle->getStrikethrough()) {
1032 1
            $css['text-decoration'] = 'line-through';
1033
        }
1034 176
        if ($pStyle->getItalic()) {
1035 8
            $css['font-style'] = 'italic';
1036
        }
1037
1038 176
        $css['color'] = '#' . $pStyle->getColor()->getRGB();
1039 176
        $css['font-family'] = '\'' . $pStyle->getName() . '\'';
1040 176
        $css['font-size'] = $pStyle->getSize() . 'pt';
1041
1042 176
        return $css;
1043
    }
1044
1045
    /**
1046
     * Create CSS style (Borders).
1047
     *
1048
     * @param Borders $pStyle Borders
1049
     *
1050
     * @return array
1051
     */
1052 176
    private function createCSSStyleBorders(Borders $pStyle)
1053
    {
1054
        // Construct CSS
1055 176
        $css = [];
1056
1057
        // Create CSS
1058 176
        $css['border-bottom'] = $this->createCSSStyleBorder($pStyle->getBottom());
1059 176
        $css['border-top'] = $this->createCSSStyleBorder($pStyle->getTop());
1060 176
        $css['border-left'] = $this->createCSSStyleBorder($pStyle->getLeft());
1061 176
        $css['border-right'] = $this->createCSSStyleBorder($pStyle->getRight());
1062
1063 176
        return $css;
1064
    }
1065
1066
    /**
1067
     * Create CSS style (Border).
1068
     *
1069
     * @param Border $pStyle Border
1070
     *
1071
     * @return string
1072
     */
1073 176
    private function createCSSStyleBorder(Border $pStyle)
1074
    {
1075
        //    Create CSS - add !important to non-none border styles for merged cells
1076 176
        $borderStyle = $this->mapBorderStyle($pStyle->getBorderStyle());
0 ignored issues
show
Bug introduced by
$pStyle->getBorderStyle() of type string is incompatible with the type integer expected by parameter $borderStyle of PhpOffice\PhpSpreadsheet...\Html::mapBorderStyle(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1076
        $borderStyle = $this->mapBorderStyle(/** @scrutinizer ignore-type */ $pStyle->getBorderStyle());
Loading history...
1077
1078 176
        return $borderStyle . ' #' . $pStyle->getColor()->getRGB() . (($borderStyle == 'none') ? '' : ' !important');
1079
    }
1080
1081
    /**
1082
     * Create CSS style (Fill).
1083
     *
1084
     * @param Fill $pStyle Fill
1085
     *
1086
     * @return array
1087
     */
1088 176
    private function createCSSStyleFill(Fill $pStyle)
1089
    {
1090
        // Construct HTML
1091 176
        $css = [];
1092
1093
        // Create CSS
1094 176
        $value = $pStyle->getFillType() == Fill::FILL_NONE ?
1095 176
            'white' : '#' . $pStyle->getStartColor()->getRGB();
1096 176
        $css['background-color'] = $value;
1097
1098 176
        return $css;
1099
    }
1100
1101
    /**
1102
     * Generate HTML footer.
1103
     */
1104 176
    public function generateHTMLFooter()
1105
    {
1106
        // Construct HTML
1107 176
        $html = '';
1108 176
        $html .= '  </body>' . PHP_EOL;
1109 176
        $html .= '</html>' . PHP_EOL;
1110
1111 176
        return $html;
1112
    }
1113
1114 6
    private function generateTableTagInline(Worksheet $worksheet, $id)
1115
    {
1116 6
        $style = isset($this->cssStyles['table']) ?
1117 6
            $this->assembleCSS($this->cssStyles['table']) : '';
1118
1119 6
        $prntgrid = $worksheet->getPrintGridlines();
1120 6
        $viewgrid = $this->isPdf ? $prntgrid : $worksheet->getShowGridlines();
1121 6
        if ($viewgrid && $prntgrid) {
1122 1
            $html = "    <table border='1' cellpadding='1' $id cellspacing='1' style='$style' class='gridlines gridlinesp'>" . PHP_EOL;
1123 6
        } elseif ($viewgrid) {
1124 2
            $html = "    <table border='0' cellpadding='0' $id cellspacing='0' style='$style' class='gridlines'>" . PHP_EOL;
1125 5
        } elseif ($prntgrid) {
1126 1
            $html = "    <table border='0' cellpadding='0' $id cellspacing='0' style='$style' class='gridlinesp'>" . PHP_EOL;
1127
        } else {
1128 5
            $html = "    <table border='0' cellpadding='1' $id cellspacing='0' style='$style'>" . PHP_EOL;
1129
        }
1130
1131 6
        return $html;
1132
    }
1133
1134 176
    private function generateTableTag(Worksheet $worksheet, $id, &$html, $sheetIndex): void
1135
    {
1136 176
        if (!$this->useInlineCss) {
1137 173
            $gridlines = $worksheet->getShowGridlines() ? ' gridlines' : '';
1138 173
            $gridlinesp = $worksheet->getPrintGridlines() ? ' gridlinesp' : '';
1139 173
            $html .= "    <table border='0' cellpadding='0' cellspacing='0' $id class='sheet$sheetIndex$gridlines$gridlinesp'>" . PHP_EOL;
1140
        } else {
1141 6
            $html .= $this->generateTableTagInline($worksheet, $id);
1142
        }
1143 176
    }
1144
1145
    /**
1146
     * Generate table header.
1147
     *
1148
     * @param Worksheet $worksheet The worksheet for the table we are writing
1149
     * @param bool $showid whether or not to add id to table tag
1150
     *
1151
     * @return string
1152
     */
1153 176
    private function generateTableHeader(Worksheet $worksheet, $showid = true)
1154
    {
1155 176
        $sheetIndex = $worksheet->getParent()->getIndex($worksheet);
1156
1157
        // Construct HTML
1158 176
        $html = '';
1159 176
        $id = $showid ? "id='sheet$sheetIndex'" : '';
1160 176
        if ($showid) {
1161 176
            $html .= "<div style='page: page$sheetIndex'>\n";
1162
        } else {
1163 2
            $html .= "<div style='page: page$sheetIndex' class='scrpgbrk'>\n";
1164
        }
1165
1166 176
        $this->generateTableTag($worksheet, $id, $html, $sheetIndex);
1167
1168
        // Write <col> elements
1169 176
        $highestColumnIndex = Coordinate::columnIndexFromString($worksheet->getHighestColumn()) - 1;
1170 176
        $i = -1;
1171 176
        while ($i++ < $highestColumnIndex) {
1172 176
            if (!$this->useInlineCss) {
1173 173
                $html .= '        <col class="col' . $i . '" />' . PHP_EOL;
1174
            } else {
1175 6
                $style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) ?
1176 6
                    $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) : '';
1177 6
                $html .= '        <col style="' . $style . '" />' . PHP_EOL;
1178
            }
1179
        }
1180
1181 176
        return $html;
1182
    }
1183
1184
    /**
1185
     * Generate table footer.
1186
     */
1187 176
    private function generateTableFooter()
1188
    {
1189 176
        return '    </tbody></table>' . PHP_EOL . '</div>' . PHP_EOL;
1190
    }
1191
1192
    /**
1193
     * Generate row start.
1194
     *
1195
     * @param int $sheetIndex Sheet index (0-based)
1196
     * @param int $pRow row number
1197
     *
1198
     * @return string
1199
     */
1200 176
    private function generateRowStart(Worksheet $worksheet, $sheetIndex, $pRow)
1201
    {
1202 176
        $html = '';
1203 176
        if (count($worksheet->getBreaks()) > 0) {
1204 2
            $breaks = $worksheet->getBreaks();
1205
1206
            // check if a break is needed before this row
1207 2
            if (isset($breaks['A' . $pRow])) {
1208
                // close table: </table>
1209 2
                $html .= $this->generateTableFooter();
1210 2
                if ($this->isPdf && $this->useInlineCss) {
1211 1
                    $html .= '<div style="page-break-before:always" />';
1212
                }
1213
1214
                // open table again: <table> + <col> etc.
1215 2
                $html .= $this->generateTableHeader($worksheet, false);
1216 2
                $html .= '<tbody>' . PHP_EOL;
1217
            }
1218
        }
1219
1220
        // Write row start
1221 176
        if (!$this->useInlineCss) {
1222 173
            $html .= '          <tr class="row' . $pRow . '">' . PHP_EOL;
1223
        } else {
1224 6
            $style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow])
1225 6
                ? $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]) : '';
1226
1227 6
            $html .= '          <tr style="' . $style . '">' . PHP_EOL;
1228
        }
1229
1230 176
        return $html;
1231
    }
1232
1233 176
    private function generateRowCellCss(Worksheet $worksheet, $cellAddress, $pRow, $colNum)
1234
    {
1235 176
        $cell = ($cellAddress > '') ? $worksheet->getCell($cellAddress) : '';
1236 176
        $coordinate = Coordinate::stringFromColumnIndex($colNum + 1) . ($pRow + 1);
1237 176
        if (!$this->useInlineCss) {
1238 173
            $cssClass = 'column' . $colNum;
1239
        } else {
1240 6
            $cssClass = [];
1241
            // The statements below do nothing.
1242
            // Commenting out the code rather than deleting it
1243
            // in case someone can figure out what their intent was.
1244
            //if ($cellType == 'th') {
1245
            //    if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum])) {
1246
            //        $this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum];
1247
            //    }
1248
            //} else {
1249
            //    if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum])) {
1250
            //        $this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum];
1251
            //    }
1252
            //}
1253
            // End of mystery statements.
1254
        }
1255
1256 176
        return [$cell, $cssClass, $coordinate];
1257
    }
1258
1259 10
    private function generateRowCellDataValueRich($cell, &$cellData): void
1260
    {
1261
        // Loop through rich text elements
1262 10
        $elements = $cell->getValue()->getRichTextElements();
1263 10
        foreach ($elements as $element) {
1264
            // Rich text start?
1265 10
            if ($element instanceof Run) {
1266 10
                $cellData .= '<span style="' . $this->assembleCSS($this->createCSSStyleFont($element->getFont())) . '">';
1267
1268 10
                $cellEnd = '';
1269 10
                if ($element->getFont()->getSuperscript()) {
1270 1
                    $cellData .= '<sup>';
1271 1
                    $cellEnd = '</sup>';
1272 10
                } elseif ($element->getFont()->getSubscript()) {
1273 1
                    $cellData .= '<sub>';
1274 1
                    $cellEnd = '</sub>';
1275
                }
1276
1277
                // Convert UTF8 data to PCDATA
1278 10
                $cellText = $element->getText();
1279 10
                $cellData .= htmlspecialchars($cellText, Settings::htmlEntityFlags());
1280
1281 10
                $cellData .= $cellEnd;
1282
1283 10
                $cellData .= '</span>';
1284
            } else {
1285
                // Convert UTF8 data to PCDATA
1286 9
                $cellText = $element->getText();
1287 9
                $cellData .= htmlspecialchars($cellText, Settings::htmlEntityFlags());
1288
            }
1289
        }
1290 10
    }
1291
1292 175
    private function generateRowCellDataValue(Worksheet $worksheet, $cell, &$cellData): void
1293
    {
1294 175
        if ($cell->getValue() instanceof RichText) {
1295 10
            $this->generateRowCellDataValueRich($cell, $cellData);
1296
        } else {
1297 175
            $origData = $this->preCalculateFormulas ? $cell->getCalculatedValue() : $cell->getValue();
1298 175
            $formatCode = $worksheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode();
1299 175
            if ($formatCode !== null) {
1300 175
                $cellData = NumberFormat::toFormattedString(
1301 175
                    $origData,
1302 175
                    $formatCode,
1303 175
                    [$this, 'formatColor']
1304
                );
1305
            }
1306
1307 175
            if ($cellData === $origData) {
1308 57
                $cellData = htmlspecialchars($cellData ?? '', Settings::htmlEntityFlags());
1309
            }
1310 175
            if ($worksheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSuperscript()) {
1311 1
                $cellData = '<sup>' . $cellData . '</sup>';
1312 175
            } elseif ($worksheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSubscript()) {
1313 1
                $cellData = '<sub>' . $cellData . '</sub>';
1314
            }
1315
        }
1316 175
    }
1317
1318 176
    private function generateRowCellData(Worksheet $worksheet, $cell, &$cssClass, $cellType)
1319
    {
1320 176
        $cellData = '&nbsp;';
1321 176
        if ($cell instanceof Cell) {
1322 175
            $cellData = '';
1323
            // Don't know what this does, and no test cases.
1324
            //if ($cell->getParent() === null) {
1325
            //    $cell->attach($worksheet);
1326
            //}
1327
            // Value
1328 175
            $this->generateRowCellDataValue($worksheet, $cell, $cellData);
1329
1330
            // Converts the cell content so that spaces occuring at beginning of each new line are replaced by &nbsp;
1331
            // Example: "  Hello\n to the world" is converted to "&nbsp;&nbsp;Hello\n&nbsp;to the world"
1332 175
            $cellData = preg_replace('/(?m)(?:^|\\G) /', '&nbsp;', $cellData);
1333
1334
            // convert newline "\n" to '<br>'
1335 175
            $cellData = nl2br($cellData);
1336
1337
            // Extend CSS class?
1338 175
            if (!$this->useInlineCss) {
1339 172
                $cssClass .= ' style' . $cell->getXfIndex();
1340 172
                $cssClass .= ' ' . $cell->getDataType();
1341
            } else {
1342 6
                if ($cellType == 'th') {
1343 1
                    if (isset($this->cssStyles['th.style' . $cell->getXfIndex()])) {
1344 1
                        $cssClass = array_merge($cssClass, $this->cssStyles['th.style' . $cell->getXfIndex()]);
1345
                    }
1346
                } else {
1347 6
                    if (isset($this->cssStyles['td.style' . $cell->getXfIndex()])) {
1348 6
                        $cssClass = array_merge($cssClass, $this->cssStyles['td.style' . $cell->getXfIndex()]);
1349
                    }
1350
                }
1351
1352
                // General horizontal alignment: Actual horizontal alignment depends on dataType
1353 6
                $sharedStyle = $worksheet->getParent()->getCellXfByIndex($cell->getXfIndex());
1354
                if (
1355 6
                    $sharedStyle->getAlignment()->getHorizontal() == Alignment::HORIZONTAL_GENERAL
1356 6
                    && isset($this->cssStyles['.' . $cell->getDataType()]['text-align'])
1357
                ) {
1358 175
                    $cssClass['text-align'] = $this->cssStyles['.' . $cell->getDataType()]['text-align'];
1359
                }
1360
            }
1361
        } else {
1362
            // Use default borders for empty cell
1363 20
            if (is_string($cssClass)) {
1364 18
                $cssClass .= ' style0';
1365
            }
1366
        }
1367
1368 176
        return $cellData;
1369
    }
1370
1371 176
    private function generateRowIncludeCharts(Worksheet $worksheet, $coordinate)
1372
    {
1373 176
        return $this->includeCharts ? $this->writeChartInCell($worksheet, $coordinate) : '';
1374
    }
1375
1376 176
    private function generateRowSpans($html, $rowSpan, $colSpan)
1377
    {
1378 176
        $html .= ($colSpan > 1) ? (' colspan="' . $colSpan . '"') : '';
1379 176
        $html .= ($rowSpan > 1) ? (' rowspan="' . $rowSpan . '"') : '';
1380
1381 176
        return $html;
1382
    }
1383
1384 176
    private function generateRowWriteCell(&$html, Worksheet $worksheet, $coordinate, $cellType, $cellData, $colSpan, $rowSpan, $cssClass, $colNum, $sheetIndex, $pRow): void
1385
    {
1386
        // Image?
1387 176
        $htmlx = $this->writeImageInCell($worksheet, $coordinate);
1388
        // Chart?
1389 176
        $htmlx .= $this->generateRowIncludeCharts($worksheet, $coordinate);
1390
        // Column start
1391 176
        $html .= '            <' . $cellType;
1392 176
        if (!$this->useInlineCss && !$this->isPdf) {
1393 166
            $html .= ' class="' . $cssClass . '"';
1394 166
            if ($htmlx) {
1395 166
                $html .= " style='position: relative;'";
1396
            }
1397
        } else {
1398
            //** Necessary redundant code for the sake of \PhpOffice\PhpSpreadsheet\Writer\Pdf **
1399
            // We must explicitly write the width of the <td> element because TCPDF
1400
            // does not recognize e.g. <col style="width:42pt">
1401 13
            if ($this->useInlineCss) {
1402 6
                $xcssClass = $cssClass;
1403
            } else {
1404 8
                $html .= ' class="' . $cssClass . '"';
1405 8
                $xcssClass = [];
1406
            }
1407 13
            $width = 0;
1408 13
            $i = $colNum - 1;
1409 13
            $e = $colNum + $colSpan - 1;
1410 13
            while ($i++ < $e) {
1411 13
                if (isset($this->columnWidths[$sheetIndex][$i])) {
1412 13
                    $width += $this->columnWidths[$sheetIndex][$i];
1413
                }
1414
            }
1415 13
            $xcssClass['width'] = $width . 'pt';
1416
1417
            // We must also explicitly write the height of the <td> element because TCPDF
1418
            // does not recognize e.g. <tr style="height:50pt">
1419 13
            if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]['height'])) {
1420 1
                $height = $this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]['height'];
1421 1
                $xcssClass['height'] = $height;
1422
            }
1423
            //** end of redundant code **
1424
1425 13
            if ($htmlx) {
1426 5
                $xcssClass['position'] = 'relative';
1427
            }
1428 13
            $html .= ' style="' . $this->assembleCSS($xcssClass) . '"';
1429
        }
1430 176
        $html = $this->generateRowSpans($html, $rowSpan, $colSpan);
1431
1432 176
        $html .= '>';
1433 176
        $html .= $htmlx;
1434
1435 176
        $html .= $this->writeComment($worksheet, $coordinate);
1436
1437
        // Cell data
1438 176
        $html .= $cellData;
1439
1440
        // Column end
1441 176
        $html .= '</' . $cellType . '>' . PHP_EOL;
1442 176
    }
1443
1444
    /**
1445
     * Generate row.
1446
     *
1447
     * @param array $pValues Array containing cells in a row
1448
     * @param int $pRow Row number (0-based)
1449
     * @param string $cellType eg: 'td'
1450
     *
1451
     * @return string
1452
     */
1453 176
    private function generateRow(Worksheet $worksheet, array $pValues, $pRow, $cellType)
1454
    {
1455
        // Sheet index
1456 176
        $sheetIndex = $worksheet->getParent()->getIndex($worksheet);
1457 176
        $html = $this->generateRowStart($worksheet, $sheetIndex, $pRow);
1458
1459
        // Write cells
1460 176
        $colNum = 0;
1461 176
        foreach ($pValues as $cellAddress) {
1462 176
            [$cell, $cssClass, $coordinate] = $this->generateRowCellCss($worksheet, $cellAddress, $pRow, $colNum);
1463
1464 176
            $colSpan = 1;
1465 176
            $rowSpan = 1;
1466
1467
            // Cell Data
1468 176
            $cellData = $this->generateRowCellData($worksheet, $cell, $cssClass, $cellType);
1469
1470
            // Hyperlink?
1471 176
            if ($worksheet->hyperlinkExists($coordinate) && !$worksheet->getHyperlink($coordinate)->isInternal()) {
1472 8
                $cellData = '<a href="' . htmlspecialchars($worksheet->getHyperlink($coordinate)->getUrl(), Settings::htmlEntityFlags()) . '" title="' . htmlspecialchars($worksheet->getHyperlink($coordinate)->getTooltip(), Settings::htmlEntityFlags()) . '">' . $cellData . '</a>';
1473
            }
1474
1475
            // Should the cell be written or is it swallowed by a rowspan or colspan?
1476 176
            $writeCell = !(isset($this->isSpannedCell[$worksheet->getParent()->getIndex($worksheet)][$pRow + 1][$colNum])
1477 176
                && $this->isSpannedCell[$worksheet->getParent()->getIndex($worksheet)][$pRow + 1][$colNum]);
1478
1479
            // Colspan and Rowspan
1480 176
            $colspan = 1;
0 ignored issues
show
Unused Code introduced by
The assignment to $colspan is dead and can be removed.
Loading history...
1481 176
            $rowspan = 1;
0 ignored issues
show
Unused Code introduced by
The assignment to $rowspan is dead and can be removed.
Loading history...
1482 176
            if (isset($this->isBaseCell[$worksheet->getParent()->getIndex($worksheet)][$pRow + 1][$colNum])) {
1483 10
                $spans = $this->isBaseCell[$worksheet->getParent()->getIndex($worksheet)][$pRow + 1][$colNum];
1484 10
                $rowSpan = $spans['rowspan'];
1485 10
                $colSpan = $spans['colspan'];
1486
1487
                //    Also apply style from last cell in merge to fix borders -
1488
                //        relies on !important for non-none border declarations in createCSSStyleBorder
1489 10
                $endCellCoord = Coordinate::stringFromColumnIndex($colNum + $colSpan) . ($pRow + $rowSpan);
1490 10
                if (!$this->useInlineCss) {
1491 9
                    $cssClass .= ' style' . $worksheet->getCell($endCellCoord)->getXfIndex();
1492
                }
1493
            }
1494
1495
            // Write
1496 176
            if ($writeCell) {
1497 176
                $this->generateRowWriteCell($html, $worksheet, $coordinate, $cellType, $cellData, $colSpan, $rowSpan, $cssClass, $colNum, $sheetIndex, $pRow);
1498
            }
1499
1500
            // Next column
1501 176
            ++$colNum;
1502
        }
1503
1504
        // Write row end
1505 176
        $html .= '          </tr>' . PHP_EOL;
1506
1507
        // Return
1508 176
        return $html;
1509
    }
1510
1511
    /**
1512
     * Takes array where of CSS properties / values and converts to CSS string.
1513
     *
1514
     * @return string
1515
     */
1516 176
    private function assembleCSS(array $pValue = [])
1517
    {
1518 176
        $pairs = [];
1519 176
        foreach ($pValue as $property => $value) {
1520 176
            $pairs[] = $property . ':' . $value;
1521
        }
1522 176
        $string = implode('; ', $pairs);
1523
1524 176
        return $string;
1525
    }
1526
1527
    /**
1528
     * Get images root.
1529
     *
1530
     * @return string
1531
     */
1532 11
    public function getImagesRoot()
1533
    {
1534 11
        return $this->imagesRoot;
1535
    }
1536
1537
    /**
1538
     * Set images root.
1539
     *
1540
     * @param string $pValue
1541
     *
1542
     * @return $this
1543
     */
1544 1
    public function setImagesRoot($pValue)
1545
    {
1546 1
        $this->imagesRoot = $pValue;
1547
1548 1
        return $this;
1549
    }
1550
1551
    /**
1552
     * Get embed images.
1553
     *
1554
     * @return bool
1555
     */
1556 1
    public function getEmbedImages()
1557
    {
1558 1
        return $this->embedImages;
1559
    }
1560
1561
    /**
1562
     * Set embed images.
1563
     *
1564
     * @param bool $pValue
1565
     *
1566
     * @return $this
1567
     */
1568 2
    public function setEmbedImages($pValue)
1569
    {
1570 2
        $this->embedImages = $pValue;
1571
1572 2
        return $this;
1573
    }
1574
1575
    /**
1576
     * Get use inline CSS?
1577
     *
1578
     * @return bool
1579
     */
1580 1
    public function getUseInlineCss()
1581
    {
1582 1
        return $this->useInlineCss;
1583
    }
1584
1585
    /**
1586
     * Set use inline CSS?
1587
     *
1588
     * @param bool $pValue
1589
     *
1590
     * @return $this
1591
     */
1592 7
    public function setUseInlineCss($pValue)
1593
    {
1594 7
        $this->useInlineCss = $pValue;
1595
1596 7
        return $this;
1597
    }
1598
1599
    /**
1600
     * Get use embedded CSS?
1601
     *
1602
     * @return bool
1603
     *
1604
     * @codeCoverageIgnore
1605
     *
1606
     * @deprecated no longer used
1607
     */
1608
    public function getUseEmbeddedCSS()
1609
    {
1610
        return $this->useEmbeddedCSS;
1611
    }
1612
1613
    /**
1614
     * Set use embedded CSS?
1615
     *
1616
     * @param bool $pValue
1617
     *
1618
     * @return $this
1619
     *
1620
     * @codeCoverageIgnore
1621
     *
1622
     * @deprecated no longer used
1623
     */
1624
    public function setUseEmbeddedCSS($pValue)
1625
    {
1626
        $this->useEmbeddedCSS = $pValue;
1627
1628
        return $this;
1629
    }
1630
1631
    /**
1632
     * Add color to formatted string as inline style.
1633
     *
1634
     * @param string $pValue Plain formatted value without color
1635
     * @param string $pFormat Format code
1636
     *
1637
     * @return string
1638
     */
1639 127
    public function formatColor($pValue, $pFormat)
1640
    {
1641
        // Color information, e.g. [Red] is always at the beginning
1642 127
        $color = null; // initialize
1643 127
        $matches = [];
1644
1645 127
        $color_regex = '/^\\[[a-zA-Z]+\\]/';
1646 127
        if (preg_match($color_regex, $pFormat, $matches)) {
1647 17
            $color = str_replace(['[', ']'], '', $matches[0]);
1648 17
            $color = strtolower($color);
1649
        }
1650
1651
        // convert to PCDATA
1652 127
        $value = htmlspecialchars($pValue, Settings::htmlEntityFlags());
1653
1654
        // color span tag
1655 127
        if ($color !== null) {
1656 17
            $value = '<span style="color:' . $color . '">' . $value . '</span>';
1657
        }
1658
1659 127
        return $value;
1660
    }
1661
1662
    /**
1663
     * Calculate information about HTML colspan and rowspan which is not always the same as Excel's.
1664
     */
1665 176
    private function calculateSpans(): void
1666
    {
1667 176
        if ($this->spansAreCalculated) {
1668 176
            return;
1669
        }
1670
        // Identify all cells that should be omitted in HTML due to cell merge.
1671
        // In HTML only the upper-left cell should be written and it should have
1672
        //   appropriate rowspan / colspan attribute
1673 176
        $sheetIndexes = $this->sheetIndex !== null ?
1674 176
            [$this->sheetIndex] : range(0, $this->spreadsheet->getSheetCount() - 1);
1675
1676 176
        foreach ($sheetIndexes as $sheetIndex) {
1677 176
            $sheet = $this->spreadsheet->getSheet($sheetIndex);
1678
1679 176
            $candidateSpannedRow = [];
1680
1681
            // loop through all Excel merged cells
1682 176
            foreach ($sheet->getMergeCells() as $cells) {
1683 10
                [$cells] = Coordinate::splitRange($cells);
1684 10
                $first = $cells[0];
1685 10
                $last = $cells[1];
1686
1687 10
                [$fc, $fr] = Coordinate::indexesFromString($first);
1688 10
                $fc = $fc - 1;
1689
1690 10
                [$lc, $lr] = Coordinate::indexesFromString($last);
1691 10
                $lc = $lc - 1;
1692
1693
                // loop through the individual cells in the individual merge
1694 10
                $r = $fr - 1;
1695 10
                while ($r++ < $lr) {
1696
                    // also, flag this row as a HTML row that is candidate to be omitted
1697 10
                    $candidateSpannedRow[$r] = $r;
1698
1699 10
                    $c = $fc - 1;
1700 10
                    while ($c++ < $lc) {
1701 10
                        if (!($c == $fc && $r == $fr)) {
1702
                            // not the upper-left cell (should not be written in HTML)
1703 10
                            $this->isSpannedCell[$sheetIndex][$r][$c] = [
1704 10
                                'baseCell' => [$fr, $fc],
1705
                            ];
1706
                        } else {
1707
                            // upper-left is the base cell that should hold the colspan/rowspan attribute
1708 10
                            $this->isBaseCell[$sheetIndex][$r][$c] = [
1709 10
                                'xlrowspan' => $lr - $fr + 1, // Excel rowspan
1710 10
                                'rowspan' => $lr - $fr + 1, // HTML rowspan, value may change
1711 10
                                'xlcolspan' => $lc - $fc + 1, // Excel colspan
1712 10
                                'colspan' => $lc - $fc + 1, // HTML colspan, value may change
1713
                            ];
1714
                        }
1715
                    }
1716
                }
1717
            }
1718
1719 176
            $this->calculateSpansOmitRows($sheet, $sheetIndex, $candidateSpannedRow);
1720
1721
            // TODO: Same for columns
1722
        }
1723
1724
        // We have calculated the spans
1725 176
        $this->spansAreCalculated = true;
1726 176
    }
1727
1728 176
    private function calculateSpansOmitRows($sheet, $sheetIndex, $candidateSpannedRow): void
1729
    {
1730
        // Identify which rows should be omitted in HTML. These are the rows where all the cells
1731
        //   participate in a merge and the where base cells are somewhere above.
1732 176
        $countColumns = Coordinate::columnIndexFromString($sheet->getHighestColumn());
1733 176
        foreach ($candidateSpannedRow as $rowIndex) {
1734 10
            if (isset($this->isSpannedCell[$sheetIndex][$rowIndex])) {
1735 10
                if (count($this->isSpannedCell[$sheetIndex][$rowIndex]) == $countColumns) {
1736 8
                    $this->isSpannedRow[$sheetIndex][$rowIndex] = $rowIndex;
1737
                }
1738
            }
1739
        }
1740
1741
        // For each of the omitted rows we found above, the affected rowspans should be subtracted by 1
1742 176
        if (isset($this->isSpannedRow[$sheetIndex])) {
1743 8
            foreach ($this->isSpannedRow[$sheetIndex] as $rowIndex) {
1744 8
                $adjustedBaseCells = [];
1745 8
                $c = -1;
1746 8
                $e = $countColumns - 1;
1747 8
                while ($c++ < $e) {
1748 8
                    $baseCell = $this->isSpannedCell[$sheetIndex][$rowIndex][$c]['baseCell'];
1749
1750 8
                    if (!in_array($baseCell, $adjustedBaseCells)) {
1751
                        // subtract rowspan by 1
1752 8
                        --$this->isBaseCell[$sheetIndex][$baseCell[0]][$baseCell[1]]['rowspan'];
1753 8
                        $adjustedBaseCells[] = $baseCell;
1754
                    }
1755
                }
1756
            }
1757
        }
1758 176
    }
1759
1760
    /**
1761
     * Write a comment in the same format as LibreOffice.
1762
     *
1763
     * @see https://github.com/LibreOffice/core/blob/9fc9bf3240f8c62ad7859947ab8a033ac1fe93fa/sc/source/filter/html/htmlexp.cxx#L1073-L1092
1764
     *
1765
     * @param string $coordinate
1766
     *
1767
     * @return string
1768
     */
1769 176
    private function writeComment(Worksheet $worksheet, $coordinate)
1770
    {
1771 176
        $result = '';
1772 176
        if (!$this->isPdf && isset($worksheet->getComments()[$coordinate])) {
1773 16
            $sanitizer = new HTMLPurifier();
1774 16
            $sanitizedString = $sanitizer->purify($worksheet->getComment($coordinate)->getText()->getPlainText());
1775 16
            if ($sanitizedString !== '') {
1776 14
                $result .= '<a class="comment-indicator"></a>';
1777 14
                $result .= '<div class="comment">' . nl2br($sanitizedString) . '</div>';
1778 14
                $result .= PHP_EOL;
1779
            }
1780
        }
1781
1782 176
        return $result;
1783
    }
1784
1785
    /**
1786
     * Generate @page declarations.
1787
     *
1788
     * @param bool $generateSurroundingHTML
1789
     *
1790
     * @return    string
1791
     */
1792 176
    private function generatePageDeclarations($generateSurroundingHTML)
1793
    {
1794
        // Ensure that Spans have been calculated?
1795 176
        $this->calculateSpans();
1796
1797
        // Fetch sheets
1798 176
        $sheets = [];
1799 176
        if ($this->sheetIndex === null) {
1800 7
            $sheets = $this->spreadsheet->getAllSheets();
1801
        } else {
1802 174
            $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex);
1803
        }
1804
1805
        // Construct HTML
1806 176
        $htmlPage = $generateSurroundingHTML ? ('<style type="text/css">' . PHP_EOL) : '';
1807
1808
        // Loop all sheets
1809 176
        $sheetId = 0;
1810 176
        foreach ($sheets as $worksheet) {
1811 176
            $htmlPage .= "@page page$sheetId { ";
1812 176
            $left = StringHelper::formatNumber($worksheet->getPageMargins()->getLeft()) . 'in; ';
1813 176
            $htmlPage .= 'margin-left: ' . $left;
1814 176
            $right = StringHelper::FormatNumber($worksheet->getPageMargins()->getRight()) . 'in; ';
1815 176
            $htmlPage .= 'margin-right: ' . $right;
1816 176
            $top = StringHelper::FormatNumber($worksheet->getPageMargins()->getTop()) . 'in; ';
1817 176
            $htmlPage .= 'margin-top: ' . $top;
1818 176
            $bottom = StringHelper::FormatNumber($worksheet->getPageMargins()->getBottom()) . 'in; ';
1819 176
            $htmlPage .= 'margin-bottom: ' . $bottom;
1820 176
            $orientation = $worksheet->getPageSetup()->getOrientation();
1821 176
            if ($orientation === \PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::ORIENTATION_LANDSCAPE) {
1822 5
                $htmlPage .= 'size: landscape; ';
1823 171
            } elseif ($orientation === \PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::ORIENTATION_PORTRAIT) {
1824 4
                $htmlPage .= 'size: portrait; ';
1825
            }
1826 176
            $htmlPage .= "}\n";
1827 176
            ++$sheetId;
1828
        }
1829
        $htmlPage .= <<<EOF
1830 176
.navigation {page-break-after: always;}
1831
.scrpgbrk, div + div {page-break-before: always;}
1832
@media screen {
1833
  .gridlines td {border: 1px solid black;}
1834
  .gridlines th {border: 1px solid black;}
1835
  body>div {margin-top: 5px;}
1836
  body>div:first-child {margin-top: 0;}
1837
  .scrpgbrk {margin-top: 1px;}
1838
}
1839
@media print {
1840
  .gridlinesp td {border: 1px solid black;}
1841
  .gridlinesp th {border: 1px solid black;}
1842
  .navigation {display: none;}
1843
}
1844
1845
EOF;
1846 176
        $htmlPage .= $generateSurroundingHTML ? ('</style>' . PHP_EOL) : '';
1847
1848 176
        return $htmlPage;
1849
    }
1850
}
1851