Completed
Push — master ( 4f6d4a...97a80f )
by Adrien
11:15 queued 03:45
created

Html::generateRowWriteCell()   B

Complexity

Conditions 9
Paths 10

Size

Total Lines 58
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 13.608

Importance

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

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

1083
        $borderStyle = $this->mapBorderStyle(/** @scrutinizer ignore-type */ $pStyle->getBorderStyle());
Loading history...
1084
1085
        return $borderStyle . ' #' . $pStyle->getColor()->getRGB() . (($borderStyle == 'none') ? '' : ' !important');
1086
    }
1087
1088
    /**
1089
     * Create CSS style (Fill).
1090 18
     *
1091
     * @param Fill $pStyle Fill
1092 18
     *
1093
     * @return array
1094
     */
1095 18
    private function createCSSStyleFill(Fill $pStyle)
1096 18
    {
1097 18
        // Construct HTML
1098
        $css = [];
1099
1100 18
        // Create CSS
1101 12
        $value = $pStyle->getFillType() == Fill::FILL_NONE ?
1102 12
            'white' : '#' . $pStyle->getStartColor()->getRGB();
1103
        $css['background-color'] = $value;
1104 7
1105 7
        return $css;
1106
    }
1107 7
1108 4
    /**
1109
     * Generate HTML footer.
1110 3
     */
1111
    public function generateHTMLFooter()
1112
    {
1113
        // Construct HTML
1114
        $html = '';
1115 18
        $html .= '  </body>' . PHP_EOL;
1116 18
        $html .= '</html>' . PHP_EOL;
1117 18
1118 18
        return $html;
1119 12
    }
1120 12
1121
    private function generateTableTagInline($pSheet, $id)
1122
    {
1123
        $style = isset($this->cssStyles['table']) ?
1124
            $this->assembleCSS($this->cssStyles['table']) : '';
1125
1126
        $prntgrid = $pSheet->getPrintGridlines();
1127
        $viewgrid = $this->isPdf ? $prntgrid : $pSheet->getShowGridlines();
1128
        if ($viewgrid && $prntgrid) {
1129 18
            $html = "    <table border='1' cellpadding='1' $id cellspacing='1' style='$style' class='gridlines gridlinesp'>" . PHP_EOL;
1130
        } elseif ($viewgrid) {
1131
            $html = "    <table border='0' cellpadding='0' $id cellspacing='0' style='$style' class='gridlines'>" . PHP_EOL;
1132
        } elseif ($prntgrid) {
1133
            $html = "    <table border='0' cellpadding='0' $id cellspacing='0' style='$style' class='gridlinesp'>" . PHP_EOL;
1134
        } else {
1135 18
            $html = "    <table border='0' cellpadding='1' $id cellspacing='0' style='$style'>" . PHP_EOL;
1136
        }
1137 18
1138
        return $html;
1139
    }
1140
1141
    private function generateTableTag($pSheet, $id, &$html, $sheetIndex)
1142
    {
1143
        if (!$this->useInlineCss) {
1144
            $gridlines = $pSheet->getShowGridlines() ? ' gridlines' : '';
1145
            $gridlinesp = $pSheet->getPrintGridlines() ? ' gridlinesp' : '';
1146
            $html .= "    <table border='0' cellpadding='0' cellspacing='0' $id class='sheet$sheetIndex$gridlines$gridlinesp'>" . PHP_EOL;
1147
        } else {
1148
            $html .= $this->generateTableTagInline($pSheet, $id);
1149
        }
1150 18
    }
1151
1152
    /**
1153 18
     * Generate table header.
1154
     *
1155
     * @param Worksheet $pSheet The worksheet for the table we are writing
1156 18
     * @param bool   $showid whether or not to add id to table tag
1157
     *
1158
     * @return string
1159 18
     */
1160
    private function generateTableHeader($pSheet, $showid = true)
1161
    {
1162
        $sheetIndex = $pSheet->getParent()->getIndex($pSheet);
1163
1164
        // Construct HTML
1165
        $html = '';
1166
        $id = $showid ? "id='sheet$sheetIndex'" : '';
1167
        if ($showid) {
1168
            $html .= "<div style='page: page$sheetIndex'>\n";
1169
        //} elseif ($this->useInlineCss) {
1170
        //    $html .= "<div style='page-break-before: always' ></div>\n";
1171
        } else {
1172
            $html .= "<div style='page: page$sheetIndex' class='scrpgbrk'>\n";
1173
        }
1174
1175
        $this->generateTableTag($pSheet, $id, $html, $sheetIndex);
1176 18
1177 12
        // Write <col> elements
1178
        $highestColumnIndex = Coordinate::columnIndexFromString($pSheet->getHighestColumn()) - 1;
1179 7
        $i = -1;
1180 7
        while ($i++ < $highestColumnIndex) {
1181
            if (!$this->useInlineCss) {
1182 7
                $html .= '        <col class="col' . $i . '" />' . PHP_EOL;
1183
            } else {
1184
                $style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) ?
1185
                    $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) : '';
1186 18
                $html .= '        <col style="' . $style . '" />' . PHP_EOL;
1187 18
            }
1188 18
        }
1189 18
1190 18
        return $html;
1191 12
    }
1192
1193 7
    /**
1194 7
     * Generate table footer.
1195
     */
1196
    private function generateTableFooter()
1197
    {
1198
        return '    </tbody></table>' . PHP_EOL . '</div>' . PHP_EOL;
1199 7
    }
1200
1201
    /**
1202
     * Generate row start.
1203
     *
1204 18
     * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
1205 18
     * @param int $sheetIndex Sheet index (0-based)
1206
     * @param int $pRow row number
1207
     *
1208 18
     * @return string
1209
     */
1210
    private function generateRowStart(Worksheet $pSheet, $sheetIndex, $pRow)
1211 18
    {
1212 18
        $html = '';
1213 18
        if (count($pSheet->getBreaks()) > 0) {
1214
            $breaks = $pSheet->getBreaks();
1215
1216
            // check if a break is needed before this row
1217 18
            if (isset($breaks['A' . $pRow])) {
1218
                // close table: </table>
1219 5
                $html .= $this->generateTableFooter();
1220 5
                if ($this->isPdf && $this->useInlineCss) {
1221
                    $html .= '<div style="page-break-before:always" />';
1222 5
                }
1223 5
1224
                // open table again: <table> + <col> etc.
1225 5
                $html .= $this->generateTableHeader($pSheet, false);
1226
                $html .= '<tbody>' . PHP_EOL;
1227 5
            }
1228
        }
1229
1230
        // Write row start
1231
        if (!$this->useInlineCss) {
1232
            $html .= '          <tr class="row' . $pRow . '">' . PHP_EOL;
1233 5
        } else {
1234 5
            $style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow])
1235
                ? $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]) : '';
1236 5
1237 5
            $html .= '          <tr style="' . $style . '">' . PHP_EOL;
1238
        }
1239 5
1240
        return $html;
1241
    }
1242
1243 5
    private function generateRowCellCss($pSheet, $cellAddress, $pRow, $colNum)
1244
    {
1245
        $cell = ($cellAddress > '') ? $pSheet->getCell($cellAddress) : '';
1246
        $coordinate = Coordinate::stringFromColumnIndex($colNum + 1) . ($pRow + 1);
1247 18
        if (!$this->useInlineCss) {
1248 18
            $cssClass = 'column' . $colNum;
1249 18
        } else {
1250 18
            $cssClass = [];
1251 18
            // The statements below do nothing.
1252
            // Commenting out the code rather than deleting it
1253
            // in case someone can figure out what their intent was.
1254
            //if ($cellType == 'th') {
1255
            //    if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum])) {
1256
            //        $this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum];
1257
            //    }
1258
            //} else {
1259
            //    if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum])) {
1260 18
            //        $this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum];
1261 18
            //    }
1262
            //}
1263 18
            // End of mystery statements.
1264
        }
1265
1266
        return [$cell, $cssClass, $coordinate];
1267
    }
1268
1269
    private function generateRowCellDataValueRich($cell, &$cellData)
1270 18
    {
1271
        // Loop through rich text elements
1272
        $elements = $cell->getValue()->getRichTextElements();
1273 18
        foreach ($elements as $element) {
1274
            // Rich text start?
1275
            if ($element instanceof Run) {
1276 18
                $cellData .= '<span style="' . $this->assembleCSS($this->createCSSStyleFont($element->getFont())) . '">';
1277 12
1278 12
                $cellEnd = '';
1279
                if ($element->getFont()->getSuperscript()) {
1280 7
                    $cellData .= '<sup>';
1281
                    $cellEnd = '</sup>';
1282
                } elseif ($element->getFont()->getSubscript()) {
1283
                    $cellData .= '<sub>';
1284
                    $cellEnd = '</sub>';
1285 7
                }
1286 7
1287
                // Convert UTF8 data to PCDATA
1288
                $cellText = $element->getText();
1289
                $cellData .= htmlspecialchars($cellText);
1290
1291 7
                $cellData .= $cellEnd;
1292 7
1293 7
                $cellData .= '</span>';
1294
            } else {
1295 7
                // Convert UTF8 data to PCDATA
1296
                $cellText = $element->getText();
1297
                $cellData .= htmlspecialchars($cellText);
1298
            }
1299
        }
1300
    }
1301 18
1302 4
    private function generateRowCellDataValue($pSheet, $cell, &$cellData)
1303
    {
1304
        if ($cell->getValue() instanceof RichText) {
1305
            $this->generateRowCellDataValueRich($cell, $cellData);
1306 18
        } else {
1307 18
            $origData = $this->preCalculateFormulas ? $cell->getCalculatedValue() : $cell->getValue();
1308
            $cellData = NumberFormat::toFormattedString(
1309
                $origData,
1310 18
                $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode(),
1311 18
                [$this, 'formatColor']
1312 18
            );
1313 6
            if ($cellData === $origData) {
1314 6
                $cellData = htmlspecialchars($cellData);
1315 6
            }
1316
            if ($pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSuperscript()) {
1317
                $cellData = '<sup>' . $cellData . '</sup>';
1318
            } elseif ($pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSubscript()) {
1319 6
                $cellData = '<sub>' . $cellData . '</sub>';
1320 6
            }
1321 3
        }
1322
    }
1323
1324
    private function generateRowCellData($pSheet, $cell, &$cssClass, $cellType)
1325
    {
1326 18
        $cellData = '&nbsp;';
1327
        if ($cell instanceof Cell) {
1328 18
            $cellData = '';
1329 18
            // Don't know what this does, and no test cases.
1330 12
            //if ($cell->getParent() === null) {
1331
            //    $cell->attach($pSheet);
1332
            //}
1333
            // Value
1334
            $this->generateRowCellDataValue($pSheet, $cell, $cellData);
1335 7
1336 7
            // Converts the cell content so that spaces occuring at beginning of each new line are replaced by &nbsp;
1337 7
            // Example: "  Hello\n to the world" is converted to "&nbsp;&nbsp;Hello\n&nbsp;to the world"
1338 7
            $cellData = preg_replace('/(?m)(?:^|\\G) /', '&nbsp;', $cellData);
1339 7
1340 7
            // convert newline "\n" to '<br>'
1341
            $cellData = nl2br($cellData);
1342
1343 7
            // Extend CSS class?
1344
            if (!$this->useInlineCss) {
1345
                $cssClass .= ' style' . $cell->getXfIndex();
1346
                $cssClass .= ' ' . $cell->getDataType();
1347 7
            } else {
1348 1
                if ($cellType == 'th') {
1349 1
                    if (isset($this->cssStyles['th.style' . $cell->getXfIndex()])) {
1350
                        $cssClass = array_merge($cssClass, $this->cssStyles['th.style' . $cell->getXfIndex()]);
1351
                    }
1352
                } else {
1353 7
                    if (isset($this->cssStyles['td.style' . $cell->getXfIndex()])) {
1354
                        $cssClass = array_merge($cssClass, $this->cssStyles['td.style' . $cell->getXfIndex()]);
1355 18
                    }
1356 6
                }
1357
1358 18
                // General horizontal alignment: Actual horizontal alignment depends on dataType
1359
                $sharedStyle = $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex());
1360
                if ($sharedStyle->getAlignment()->getHorizontal() == Alignment::HORIZONTAL_GENERAL
1361 18
                    && isset($this->cssStyles['.' . $cell->getDataType()]['text-align'])
1362
                ) {
1363 18
                    $cssClass['text-align'] = $this->cssStyles['.' . $cell->getDataType()]['text-align'];
1364
                }
1365
            }
1366 18
        } else {
1367
            // Use default borders for empty cell
1368
            if (is_string($cssClass)) {
1369 18
                $cssClass .= ' style0';
1370
            }
1371
        }
1372
1373
        return $cellData;
1374 18
    }
1375
1376
    private function generateRowIncludeCharts($pSheet, $coordinate)
1377 18
    {
1378
        return $this->includeCharts ? $this->writeChartInCell($pSheet, $coordinate) : '';
1379
    }
1380
1381 18
    private function generateRowSpans($html, $rowSpan, $colSpan)
1382
    {
1383
        $html .= ($colSpan > 1) ? (' colspan="' . $colSpan . '"') : '';
1384
        $html .= ($rowSpan > 1) ? (' rowspan="' . $rowSpan . '"') : '';
1385 18
1386
        return $html;
1387
    }
1388 18
1389
    private function generateRowWriteCell(&$html, $pSheet, $coordinate, $cellType, $cellData, $colSpan, $rowSpan, $cssClass, $colNum, $sheetIndex, $pRow)
1390
    {
1391
        // Image?
1392
        $htmlx = $this->writeImageInCell($pSheet, $coordinate);
1393
        // Chart?
1394
        $htmlx .= $this->generateRowIncludeCharts($pSheet, $coordinate);
1395
        // Column start
1396
        $html .= '            <' . $cellType;
1397
        if (!$this->useInlineCss && !$this->isPdf) {
1398 18
            $html .= ' class="' . $cssClass . '"';
1399
            if ($htmlx) {
1400 18
                $html .= " style='position: relative;'";
1401 18
            }
1402 18
        } else {
1403
            //** Necessary redundant code for the sake of \PhpOffice\PhpSpreadsheet\Writer\Pdf **
1404 18
            // We must explicitly write the width of the <td> element because TCPDF
1405
            // does not recognize e.g. <col style="width:42pt">
1406 18
            if ($this->useInlineCss) {
1407
                $xcssClass = $cssClass;
1408
            } else {
1409
                $html .= ' class="' . $cssClass . '"';
1410
                $xcssClass = [];
1411
            }
1412
            $width = 0;
1413
            $i = $colNum - 1;
1414 4
            $e = $colNum + $colSpan - 1;
1415
            while ($i++ < $e) {
1416 4
                if (isset($this->columnWidths[$sheetIndex][$i])) {
1417
                    $width += $this->columnWidths[$sheetIndex][$i];
1418
                }
1419
            }
1420
            $xcssClass['width'] = $width . 'pt';
1421
1422
            // We must also explicitly write the height of the <td> element because TCPDF
1423
            // does not recognize e.g. <tr style="height:50pt">
1424
            if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]['height'])) {
1425
                $height = $this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]['height'];
1426
                $xcssClass['height'] = $height;
1427
            }
1428
            //** end of redundant code **
1429
1430
            if ($htmlx) {
1431
                $xcssClass['position'] = 'relative';
1432
            }
1433
            $html .= ' style="' . $this->assembleCSS($xcssClass) . '"';
1434
        }
1435
        $html = $this->generateRowSpans($html, $rowSpan, $colSpan);
1436
1437
        $html .= '>';
1438
        $html .= $htmlx;
1439
1440
        $html .= $this->writeComment($pSheet, $coordinate);
1441
1442
        // Cell data
1443
        $html .= $cellData;
1444
1445
        // Column end
1446
        $html .= '</' . $cellType . '>' . PHP_EOL;
1447
    }
1448
1449
    /**
1450
     * Generate row.
1451
     *
1452
     * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
1453
     * @param array $pValues Array containing cells in a row
1454
     * @param int $pRow Row number (0-based)
1455
     * @param string $cellType eg: 'td'
1456
     *
1457
     * @throws WriterException
1458
     *
1459
     * @return string
1460
     */
1461
    private function generateRow(Worksheet $pSheet, array $pValues, $pRow, $cellType)
1462
    {
1463
        // Sheet index
1464
        $sheetIndex = $pSheet->getParent()->getIndex($pSheet);
1465
        $html = $this->generateRowStart($pSheet, $sheetIndex, $pRow);
1466
1467
        // Write cells
1468
        $colNum = 0;
1469
        foreach ($pValues as $cellAddress) {
1470
            [$cell, $cssClass, $coordinate] = $this->generateRowCellCss($pSheet, $cellAddress, $pRow, $colNum);
1471
1472
            $colSpan = 1;
1473
            $rowSpan = 1;
1474 11
1475
            // Cell Data
1476 11
            $cellData = $this->generateRowCellData($pSheet, $cell, $cssClass, $cellType);
1477
1478 11
            // Hyperlink?
1479
            if ($pSheet->hyperlinkExists($coordinate) && !$pSheet->getHyperlink($coordinate)->isInternal()) {
1480
                $cellData = '<a href="' . htmlspecialchars($pSheet->getHyperlink($coordinate)->getUrl()) . '" title="' . htmlspecialchars($pSheet->getHyperlink($coordinate)->getTooltip()) . '">' . $cellData . '</a>';
1481
            }
1482
1483
            // Should the cell be written or is it swallowed by a rowspan or colspan?
1484
            $writeCell = !(isset($this->isSpannedCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum])
1485
                && $this->isSpannedCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum]);
1486
1487
            // Colspan and Rowspan
1488
            $colspan = 1;
0 ignored issues
show
Unused Code introduced by
The assignment to $colspan is dead and can be removed.
Loading history...
1489
            $rowspan = 1;
0 ignored issues
show
Unused Code introduced by
The assignment to $rowspan is dead and can be removed.
Loading history...
1490
            if (isset($this->isBaseCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum])) {
1491
                $spans = $this->isBaseCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum];
1492
                $rowSpan = $spans['rowspan'];
1493
                $colSpan = $spans['colspan'];
1494
1495
                //    Also apply style from last cell in merge to fix borders -
1496
                //        relies on !important for non-none border declarations in createCSSStyleBorder
1497
                $endCellCoord = Coordinate::stringFromColumnIndex($colNum + $colSpan) . ($pRow + $rowSpan);
1498
                if (!$this->useInlineCss) {
1499
                    $cssClass .= ' style' . $pSheet->getCell($endCellCoord)->getXfIndex();
1500
                }
1501
            }
1502
1503
            // Write
1504
            if ($writeCell) {
1505
                $this->generateRowWriteCell($html, $pSheet, $coordinate, $cellType, $cellData, $colSpan, $rowSpan, $cssClass, $colNum, $sheetIndex, $pRow);
1506
            }
1507
1508
            // Next column
1509
            ++$colNum;
1510
        }
1511
1512
        // Write row end
1513 4
        $html .= '          </tr>' . PHP_EOL;
1514
1515
        // Return
1516 4
        return $html;
1517 4
    }
1518
1519 4
    /**
1520 4
     * Takes array where of CSS properties / values and converts to CSS string.
1521
     *
1522
     * @param array $pValue
1523
     *
1524
     * @return string
1525
     */
1526 4
    private function assembleCSS(array $pValue = [])
1527
    {
1528
        $pairs = [];
1529 4
        foreach ($pValue as $property => $value) {
1530
            $pairs[] = $property . ':' . $value;
1531
        }
1532
        $string = implode('; ', $pairs);
1533 4
1534
        return $string;
1535
    }
1536
1537
    /**
1538
     * Get images root.
1539 18
     *
1540
     * @return string
1541
     */
1542
    public function getImagesRoot()
1543
    {
1544 18
        return $this->imagesRoot;
1545 18
    }
1546
1547 18
    /**
1548 18
     * Set images root.
1549
     *
1550 18
     * @param string $pValue
1551
     *
1552
     * @return $this
1553 18
     */
1554 6
    public function setImagesRoot($pValue)
1555 6
    {
1556 6
        $this->imagesRoot = $pValue;
1557
1558 6
        return $this;
1559 6
    }
1560
1561 6
    /**
1562 6
     * Get embed images.
1563
     *
1564
     * @return bool
1565 6
     */
1566 6
    public function getEmbedImages()
1567
    {
1568 6
        return $this->embedImages;
1569
    }
1570 6
1571 6
    /**
1572 6
     * Set embed images.
1573
     *
1574 6
     * @param bool $pValue
1575 6
     *
1576
     * @return $this
1577
     */
1578
    public function setEmbedImages($pValue)
1579 6
    {
1580 6
        $this->embedImages = $pValue;
1581 6
1582 6
        return $this;
1583 6
    }
1584
1585
    /**
1586
     * Get use inline CSS?
1587
     *
1588
     * @return bool
1589
     */
1590
    public function getUseInlineCss()
1591
    {
1592 18
        return $this->useInlineCss;
1593 18
    }
1594 6
1595 6
    /**
1596 4
     * Set use inline CSS?
1597
     *
1598
     * @param bool $pValue
1599
     *
1600
     * @return $this
1601
     */
1602 18
    public function setUseInlineCss($pValue)
1603 4
    {
1604 4
        $this->useInlineCss = $pValue;
1605 4
1606 4
        return $this;
1607 4
    }
1608 4
1609
    /**
1610 4
     * Get use embedded CSS?
1611
     *
1612 4
     * @deprecated no longer used
1613 4
     *
1614
     * @return bool
1615
     *
1616
     * @codeCoverageIgnore
1617
     */
1618
    public function getUseEmbeddedCSS()
1619
    {
1620
        return $this->useEmbeddedCSS;
1621
    }
1622
1623 18
    /**
1624 18
     * Set use embedded CSS?
1625
     *
1626 18
     * @deprecated no longer used
1627
     *
1628 18
     * @param bool $pValue
1629 18
     *
1630
     * @return $this
1631 18
     *
1632 18
     * @codeCoverageIgnore
1633 18
     */
1634 18
    public function setUseEmbeddedCSS($pValue)
1635 18
    {
1636 18
        $this->useEmbeddedCSS = $pValue;
1637 18
1638 18
        return $this;
1639 18
    }
1640 18
1641 18
    /**
1642 18
     * Add color to formatted string as inline style.
1643
     *
1644 18
     * @param string $pValue Plain formatted value without color
1645 18
     * @param string $pFormat Format code
1646
     *
1647 18
     * @return string
1648
     */
1649
    public function formatColor($pValue, $pFormat)
1650
    {
1651
        // Color information, e.g. [Red] is always at the beginning
1652
        $color = null; // initialize
1653
        $matches = [];
1654
1655
        $color_regex = '/^\\[[a-zA-Z]+\\]/';
1656
        if (preg_match($color_regex, $pFormat, $matches)) {
1657
            $color = str_replace(['[', ']'], '', $matches[0]);
1658
            $color = strtolower($color);
1659
        }
1660 18
1661
        // convert to PCDATA
1662 18
        $value = htmlspecialchars($pValue);
1663 18
1664 7
        // color span tag
1665 7
        if ($color !== null) {
1666 7
            $value = '<span style="color:' . $color . '">' . $value . '</span>';
1667
        }
1668
1669 18
        return $value;
1670
    }
1671
1672
    /**
1673
     * Calculate information about HTML colspan and rowspan which is not always the same as Excel's.
1674
     */
1675
    private function calculateSpans()
1676
    {
1677
        if ($this->spansAreCalculated) {
1678
            return;
1679
        }
1680
        // Identify all cells that should be omitted in HTML due to cell merge.
1681
        // In HTML only the upper-left cell should be written and it should have
1682
        //   appropriate rowspan / colspan attribute
1683
        $sheetIndexes = $this->sheetIndex !== null ?
1684
            [$this->sheetIndex] : range(0, $this->spreadsheet->getSheetCount() - 1);
1685
1686
        foreach ($sheetIndexes as $sheetIndex) {
1687
            $sheet = $this->spreadsheet->getSheet($sheetIndex);
1688
1689
            $candidateSpannedRow = [];
1690
1691
            // loop through all Excel merged cells
1692
            foreach ($sheet->getMergeCells() as $cells) {
1693
                [$cells] = Coordinate::splitRange($cells);
0 ignored issues
show
Bug introduced by
$cells of type array is incompatible with the type string expected by parameter $pRange of PhpOffice\PhpSpreadsheet...oordinate::splitRange(). ( Ignorable by Annotation )

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

1693
                [$cells] = Coordinate::splitRange(/** @scrutinizer ignore-type */ $cells);
Loading history...
1694
                $first = $cells[0];
1695
                $last = $cells[1];
1696
1697
                [$fc, $fr] = Coordinate::coordinateFromString($first);
1698
                $fc = Coordinate::columnIndexFromString($fc) - 1;
1699
1700
                [$lc, $lr] = Coordinate::coordinateFromString($last);
1701
                $lc = Coordinate::columnIndexFromString($lc) - 1;
1702
1703
                // loop through the individual cells in the individual merge
1704
                $r = $fr - 1;
1705
                while ($r++ < $lr) {
1706
                    // also, flag this row as a HTML row that is candidate to be omitted
1707
                    $candidateSpannedRow[$r] = $r;
1708
1709
                    $c = $fc - 1;
1710
                    while ($c++ < $lc) {
1711
                        if (!($c == $fc && $r == $fr)) {
1712
                            // not the upper-left cell (should not be written in HTML)
1713
                            $this->isSpannedCell[$sheetIndex][$r][$c] = [
1714
                                'baseCell' => [$fr, $fc],
1715
                            ];
1716
                        } else {
1717
                            // upper-left is the base cell that should hold the colspan/rowspan attribute
1718
                            $this->isBaseCell[$sheetIndex][$r][$c] = [
1719
                                'xlrowspan' => $lr - $fr + 1, // Excel rowspan
1720
                                'rowspan' => $lr - $fr + 1, // HTML rowspan, value may change
1721
                                'xlcolspan' => $lc - $fc + 1, // Excel colspan
1722
                                'colspan' => $lc - $fc + 1, // HTML colspan, value may change
1723
                            ];
1724
                        }
1725
                    }
1726
                }
1727
            }
1728
1729
            $this->calculateSpansOmitRows($sheet, $sheetIndex, $candidateSpannedRow);
1730
1731
            // TODO: Same for columns
1732
        }
1733
1734
        // We have calculated the spans
1735
        $this->spansAreCalculated = true;
1736
    }
1737
1738
    private function calculateSpansOmitRows($sheet, $sheetIndex, $candidateSpannedRow)
1739
    {
1740
        // Identify which rows should be omitted in HTML. These are the rows where all the cells
1741
        //   participate in a merge and the where base cells are somewhere above.
1742
        $countColumns = Coordinate::columnIndexFromString($sheet->getHighestColumn());
1743
        foreach ($candidateSpannedRow as $rowIndex) {
1744
            if (isset($this->isSpannedCell[$sheetIndex][$rowIndex])) {
1745
                if (count($this->isSpannedCell[$sheetIndex][$rowIndex]) == $countColumns) {
1746
                    $this->isSpannedRow[$sheetIndex][$rowIndex] = $rowIndex;
1747
                }
1748
            }
1749
        }
1750
1751
        // For each of the omitted rows we found above, the affected rowspans should be subtracted by 1
1752
        if (isset($this->isSpannedRow[$sheetIndex])) {
1753
            foreach ($this->isSpannedRow[$sheetIndex] as $rowIndex) {
1754
                $adjustedBaseCells = [];
1755
                $c = -1;
1756
                $e = $countColumns - 1;
1757
                while ($c++ < $e) {
1758
                    $baseCell = $this->isSpannedCell[$sheetIndex][$rowIndex][$c]['baseCell'];
1759
1760
                    if (!in_array($baseCell, $adjustedBaseCells)) {
1761
                        // subtract rowspan by 1
1762
                        --$this->isBaseCell[$sheetIndex][$baseCell[0]][$baseCell[1]]['rowspan'];
1763
                        $adjustedBaseCells[] = $baseCell;
1764
                    }
1765
                }
1766
            }
1767
        }
1768
    }
1769
1770
    /**
1771
     * Write a comment in the same format as LibreOffice.
1772
     *
1773
     * @see https://github.com/LibreOffice/core/blob/9fc9bf3240f8c62ad7859947ab8a033ac1fe93fa/sc/source/filter/html/htmlexp.cxx#L1073-L1092
1774
     *
1775
     * @param Worksheet $pSheet
1776
     * @param string $coordinate
1777
     *
1778
     * @return string
1779
     */
1780
    private function writeComment(Worksheet $pSheet, $coordinate)
1781
    {
1782
        $result = '';
1783
        if (!$this->isPdf && isset($pSheet->getComments()[$coordinate])) {
1784
            $result .= '<a class="comment-indicator"></a>';
1785
            $result .= '<div class="comment">' . nl2br($pSheet->getComment($coordinate)->getText()->getPlainText()) . '</div>';
1786
            $result .= PHP_EOL;
1787
        }
1788
1789
        return $result;
1790
    }
1791
1792
    /**
1793
     * Generate @page declarations.
1794
     *
1795
     * @param bool $generateSurroundingHTML
1796
     *
1797
     * @return    string
1798
     */
1799
    private function generatePageDeclarations($generateSurroundingHTML)
1800
    {
1801
        // Ensure that Spans have been calculated?
1802
        $this->calculateSpans();
1803
1804
        // Fetch sheets
1805
        $sheets = [];
1806
        if ($this->sheetIndex === null) {
1807
            $sheets = $this->spreadsheet->getAllSheets();
1808
        } else {
1809
            $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex);
1810
        }
1811
1812
        // Construct HTML
1813
        $htmlPage = $generateSurroundingHTML ? ('<style type="text/css">' . PHP_EOL) : '';
1814
1815
        // Loop all sheets
1816
        $sheetId = 0;
1817
        foreach ($sheets as $pSheet) {
1818
            $htmlPage .= "@page page$sheetId { ";
1819
            $left = StringHelper::formatNumber($pSheet->getPageMargins()->getLeft()) . 'in; ';
1820
            $htmlPage .= 'margin-left: ' . $left;
1821
            $right = StringHelper::FormatNumber($pSheet->getPageMargins()->getRight()) . 'in; ';
1822
            $htmlPage .= 'margin-right: ' . $right;
1823
            $top = StringHelper::FormatNumber($pSheet->getPageMargins()->getTop()) . 'in; ';
1824
            $htmlPage .= 'margin-top: ' . $top;
1825
            $bottom = StringHelper::FormatNumber($pSheet->getPageMargins()->getBottom()) . 'in; ';
1826
            $htmlPage .= 'margin-bottom: ' . $bottom;
1827
            $orientation = $pSheet->getPageSetup()->getOrientation();
1828
            if ($orientation === \PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::ORIENTATION_LANDSCAPE) {
1829
                $htmlPage .= 'size: landscape; ';
1830
            } elseif ($orientation === \PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::ORIENTATION_PORTRAIT) {
1831
                $htmlPage .= 'size: portrait; ';
1832
            }
1833
            $htmlPage .= "}\n";
1834
            ++$sheetId;
1835
        }
1836
        $htmlPage .= <<<EOF
1837
.navigation {page-break-after: always;}
1838
.scrpgbrk, div + div {page-break-before: always;}
1839
@media screen {
1840
  .gridlines td {border: 1px solid black;}
1841
  .gridlines th {border: 1px solid black;}
1842
  body>div {margin-top: 5px;}
1843
  body>div:first-child {margin-top: 0;}
1844
  .scrpgbrk {margin-top: 1px;}
1845
}
1846
@media print {
1847
  .gridlinesp td {border: 1px solid black;}
1848
  .gridlinesp th {border: 1px solid black;}
1849
  .navigation {display: none;}
1850
}
1851
1852
EOF;
1853
        $htmlPage .= $generateSurroundingHTML ? ('</style>' . PHP_EOL) : '';
1854
1855
        return $htmlPage;
1856
    }
1857
}
1858