Failed Conditions
Push — master ( a2bb82...a189d9 )
by Adrien
10:27 queued 01:00
created

Worksheet::writeSetup()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 55
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 5.0026

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
eloc 41
c 2
b 0
f 0
nc 8
nop 0
dl 0
loc 55
rs 8.9528
ccs 40
cts 42
cp 0.9524
crap 5.0026

How to fix   Long Method   

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:

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
4
5
use GdImage;
6
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
7
use PhpOffice\PhpSpreadsheet\Cell\DataType;
8
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
9
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
10
use PhpOffice\PhpSpreadsheet\RichText\RichText;
11
use PhpOffice\PhpSpreadsheet\RichText\Run;
12
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
13
use PhpOffice\PhpSpreadsheet\Shared\Xls;
14
use PhpOffice\PhpSpreadsheet\Style\Alignment;
15
use PhpOffice\PhpSpreadsheet\Style\Border;
16
use PhpOffice\PhpSpreadsheet\Style\Color;
17
use PhpOffice\PhpSpreadsheet\Style\Conditional;
18
use PhpOffice\PhpSpreadsheet\Style\Fill;
19
use PhpOffice\PhpSpreadsheet\Style\Protection;
20
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
21
use PhpOffice\PhpSpreadsheet\Worksheet\SheetView;
22
use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
23
24
// Original file header of PEAR::Spreadsheet_Excel_Writer_Worksheet (used as the base for this class):
25
// -----------------------------------------------------------------------------------------
26
// /*
27
// *  Module written/ported by Xavier Noguer <[email protected]>
28
// *
29
// *  The majority of this is _NOT_ my code.  I simply ported it from the
30
// *  PERL Spreadsheet::WriteExcel module.
31
// *
32
// *  The author of the Spreadsheet::WriteExcel module is John McNamara
33
// *  <[email protected]>
34
// *
35
// *  I _DO_ maintain this code, and John McNamara has nothing to do with the
36
// *  porting of this code to PHP.  Any questions directly related to this
37
// *  class library should be directed to me.
38
// *
39
// *  License Information:
40
// *
41
// *    Spreadsheet_Excel_Writer:  A library for generating Excel Spreadsheets
42
// *    Copyright (c) 2002-2003 Xavier Noguer [email protected]
43
// *
44
// *    This library is free software; you can redistribute it and/or
45
// *    modify it under the terms of the GNU Lesser General Public
46
// *    License as published by the Free Software Foundation; either
47
// *    version 2.1 of the License, or (at your option) any later version.
48
// *
49
// *    This library is distributed in the hope that it will be useful,
50
// *    but WITHOUT ANY WARRANTY; without even the implied warranty of
51
// *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
52
// *    Lesser General Public License for more details.
53
// *
54
// *    You should have received a copy of the GNU Lesser General Public
55
// *    License along with this library; if not, write to the Free Software
56
// *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
57
// */
58
class Worksheet extends BIFFwriter
59
{
60
    /**
61
     * Formula parser.
62
     *
63
     * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser
64
     */
65
    private $parser;
66
67
    /**
68
     * Maximum number of characters for a string (LABEL record in BIFF5).
69
     *
70
     * @var int
71
     */
72
    private $xlsStringMaxLength;
73
74
    /**
75
     * Array containing format information for columns.
76
     *
77
     * @var array
78
     */
79
    private $columnInfo;
80
81
    /**
82
     * Array containing the selected area for the worksheet.
83
     *
84
     * @var array
85
     */
86
    private $selection;
87
88
    /**
89
     * The active pane for the worksheet.
90
     *
91
     * @var int
92
     */
93
    private $activePane;
94
95
    /**
96
     * Whether to use outline.
97
     *
98
     * @var int
99
     */
100
    private $outlineOn;
101
102
    /**
103
     * Auto outline styles.
104
     *
105
     * @var bool
106
     */
107
    private $outlineStyle;
108
109
    /**
110
     * Whether to have outline summary below.
111
     *
112
     * @var bool
113
     */
114
    private $outlineBelow;
115
116
    /**
117
     * Whether to have outline summary at the right.
118
     *
119
     * @var bool
120
     */
121
    private $outlineRight;
122
123
    /**
124
     * Reference to the total number of strings in the workbook.
125
     *
126
     * @var int
127
     */
128
    private $stringTotal;
129
130
    /**
131
     * Reference to the number of unique strings in the workbook.
132
     *
133
     * @var int
134
     */
135
    private $stringUnique;
136
137
    /**
138
     * Reference to the array containing all the unique strings in the workbook.
139
     *
140
     * @var array
141
     */
142
    private $stringTable;
143
144
    /**
145
     * Color cache.
146
     */
147
    private $colors;
148
149
    /**
150
     * Index of first used row (at least 0).
151
     *
152
     * @var int
153
     */
154
    private $firstRowIndex;
155
156
    /**
157
     * Index of last used row. (no used rows means -1).
158
     *
159
     * @var int
160
     */
161
    private $lastRowIndex;
162
163
    /**
164
     * Index of first used column (at least 0).
165
     *
166
     * @var int
167
     */
168
    private $firstColumnIndex;
169
170
    /**
171
     * Index of last used column (no used columns means -1).
172
     *
173
     * @var int
174
     */
175
    private $lastColumnIndex;
176
177
    /**
178
     * Sheet object.
179
     *
180
     * @var \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
181
     */
182
    public $phpSheet;
183
184
    /**
185
     * Count cell style Xfs.
186
     *
187
     * @var int
188
     */
189
    private $countCellStyleXfs;
190
191
    /**
192
     * Escher object corresponding to MSODRAWING.
193
     *
194
     * @var \PhpOffice\PhpSpreadsheet\Shared\Escher
195
     */
196
    private $escher;
197
198
    /**
199
     * Array of font hashes associated to FONT records index.
200
     *
201
     * @var array
202
     */
203
    public $fontHashIndex;
204
205
    /**
206
     * @var bool
207
     */
208
    private $preCalculateFormulas;
209
210
    /**
211
     * @var int
212
     */
213
    private $printHeaders;
214
215
    /**
216
     * Constructor.
217
     *
218
     * @param int $str_total Total number of strings
219
     * @param int $str_unique Total number of unique strings
220
     * @param array &$str_table String Table
221
     * @param array &$colors Colour Table
222
     * @param Parser $parser The formula parser created for the Workbook
223
     * @param bool $preCalculateFormulas Flag indicating whether formulas should be calculated or just written
224
     * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $phpSheet The worksheet to write
225
     */
226 55
    public function __construct(&$str_total, &$str_unique, &$str_table, &$colors, Parser $parser, $preCalculateFormulas, \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $phpSheet)
227
    {
228
        // It needs to call its parent's constructor explicitly
229 55
        parent::__construct();
230
231 55
        $this->preCalculateFormulas = $preCalculateFormulas;
232 55
        $this->stringTotal = &$str_total;
233 55
        $this->stringUnique = &$str_unique;
234 55
        $this->stringTable = &$str_table;
235 55
        $this->colors = &$colors;
236 55
        $this->parser = $parser;
237
238 55
        $this->phpSheet = $phpSheet;
239
240 55
        $this->xlsStringMaxLength = 255;
241 55
        $this->columnInfo = [];
242 55
        $this->selection = [0, 0, 0, 0];
243 55
        $this->activePane = 3;
244
245 55
        $this->printHeaders = 0;
246
247 55
        $this->outlineStyle = 0;
248 55
        $this->outlineBelow = 1;
249 55
        $this->outlineRight = 1;
250 55
        $this->outlineOn = 1;
251
252 55
        $this->fontHashIndex = [];
253
254
        // calculate values for DIMENSIONS record
255 55
        $minR = 1;
256 55
        $minC = 'A';
257
258 55
        $maxR = $this->phpSheet->getHighestRow();
259 55
        $maxC = $this->phpSheet->getHighestColumn();
260
261
        // Determine lowest and highest column and row
262 55
        $this->lastRowIndex = ($maxR > 65535) ? 65535 : $maxR;
263
264 55
        $this->firstColumnIndex = Coordinate::columnIndexFromString($minC);
265 55
        $this->lastColumnIndex = Coordinate::columnIndexFromString($maxC);
266
267
//        if ($this->firstColumnIndex > 255) $this->firstColumnIndex = 255;
268 55
        if ($this->lastColumnIndex > 255) {
269
            $this->lastColumnIndex = 255;
270
        }
271
272 55
        $this->countCellStyleXfs = count($phpSheet->getParent()->getCellStyleXfCollection());
273 55
    }
274
275
    /**
276
     * Add data to the beginning of the workbook (note the reverse order)
277
     * and to the end of the workbook.
278
     *
279
     * @see \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook::storeWorkbook()
280
     */
281 55
    public function close(): void
282
    {
283 55
        $phpSheet = $this->phpSheet;
284
285
        // Storing selected cells and active sheet because it changes while parsing cells with formulas.
286 55
        $selectedCells = $this->phpSheet->getSelectedCells();
287 55
        $activeSheetIndex = $this->phpSheet->getParent()->getActiveSheetIndex();
288
289
        // Write BOF record
290 55
        $this->storeBof(0x0010);
291
292
        // Write PRINTHEADERS
293 55
        $this->writePrintHeaders();
294
295
        // Write PRINTGRIDLINES
296 55
        $this->writePrintGridlines();
297
298
        // Write GRIDSET
299 55
        $this->writeGridset();
300
301
        // Calculate column widths
302 55
        $phpSheet->calculateColumnWidths();
303
304
        // Column dimensions
305 55
        if (($defaultWidth = $phpSheet->getDefaultColumnDimension()->getWidth()) < 0) {
306 54
            $defaultWidth = \PhpOffice\PhpSpreadsheet\Shared\Font::getDefaultColumnWidthByFont($phpSheet->getParent()->getDefaultStyle()->getFont());
307
        }
308
309 55
        $columnDimensions = $phpSheet->getColumnDimensions();
310 55
        $maxCol = $this->lastColumnIndex - 1;
311 55
        for ($i = 0; $i <= $maxCol; ++$i) {
312 55
            $hidden = 0;
313 55
            $level = 0;
314 55
            $xfIndex = 15; // there are 15 cell style Xfs
315
316 55
            $width = $defaultWidth;
317
318 55
            $columnLetter = Coordinate::stringFromColumnIndex($i + 1);
319 55
            if (isset($columnDimensions[$columnLetter])) {
320 20
                $columnDimension = $columnDimensions[$columnLetter];
321 20
                if ($columnDimension->getWidth() >= 0) {
322 20
                    $width = $columnDimension->getWidth();
323
                }
324 20
                $hidden = $columnDimension->getVisible() ? 0 : 1;
325 20
                $level = $columnDimension->getOutlineLevel();
326 20
                $xfIndex = $columnDimension->getXfIndex() + 15; // there are 15 cell style Xfs
327
            }
328
329
            // Components of columnInfo:
330
            // $firstcol first column on the range
331
            // $lastcol  last column on the range
332
            // $width    width to set
333
            // $xfIndex  The optional cell style Xf index to apply to the columns
334
            // $hidden   The optional hidden atribute
335
            // $level    The optional outline level
336 55
            $this->columnInfo[] = [$i, $i, $width, $xfIndex, $hidden, $level];
337
        }
338
339
        // Write GUTS
340 55
        $this->writeGuts();
341
342
        // Write DEFAULTROWHEIGHT
343 55
        $this->writeDefaultRowHeight();
344
        // Write WSBOOL
345 55
        $this->writeWsbool();
346
        // Write horizontal and vertical page breaks
347 55
        $this->writeBreaks();
348
        // Write page header
349 55
        $this->writeHeader();
350
        // Write page footer
351 55
        $this->writeFooter();
352
        // Write page horizontal centering
353 55
        $this->writeHcenter();
354
        // Write page vertical centering
355 55
        $this->writeVcenter();
356
        // Write left margin
357 55
        $this->writeMarginLeft();
358
        // Write right margin
359 55
        $this->writeMarginRight();
360
        // Write top margin
361 55
        $this->writeMarginTop();
362
        // Write bottom margin
363 55
        $this->writeMarginBottom();
364
        // Write page setup
365 55
        $this->writeSetup();
366
        // Write sheet protection
367 55
        $this->writeProtect();
368
        // Write SCENPROTECT
369 55
        $this->writeScenProtect();
370
        // Write OBJECTPROTECT
371 55
        $this->writeObjectProtect();
372
        // Write sheet password
373 55
        $this->writePassword();
374
        // Write DEFCOLWIDTH record
375 55
        $this->writeDefcol();
376
377
        // Write the COLINFO records if they exist
378 55
        if (!empty($this->columnInfo)) {
379 55
            $colcount = count($this->columnInfo);
380 55
            for ($i = 0; $i < $colcount; ++$i) {
381 55
                $this->writeColinfo($this->columnInfo[$i]);
382
            }
383
        }
384 55
        $autoFilterRange = $phpSheet->getAutoFilter()->getRange();
385 55
        if (!empty($autoFilterRange)) {
386
            // Write AUTOFILTERINFO
387 3
            $this->writeAutoFilterInfo();
388
        }
389
390
        // Write sheet dimensions
391 55
        $this->writeDimensions();
392
393
        // Row dimensions
394 55
        foreach ($phpSheet->getRowDimensions() as $rowDimension) {
395 39
            $xfIndex = $rowDimension->getXfIndex() + 15; // there are 15 cellXfs
396 39
            $this->writeRow($rowDimension->getRowIndex() - 1, $rowDimension->getRowHeight(), $xfIndex, ($rowDimension->getVisible() ? '0' : '1'), $rowDimension->getOutlineLevel());
397
        }
398
399
        // Write Cells
400 55
        foreach ($phpSheet->getCoordinates() as $coordinate) {
401 52
            $cell = $phpSheet->getCell($coordinate);
402 52
            $row = $cell->getRow() - 1;
403 52
            $column = Coordinate::columnIndexFromString($cell->getColumn()) - 1;
404
405
            // Don't break Excel break the code!
406 52
            if ($row > 65535 || $column > 255) {
407
                throw new WriterException('Rows or columns overflow! Excel5 has limit to 65535 rows and 255 columns. Use XLSX instead.');
408
            }
409
410
            // Write cell value
411 52
            $xfIndex = $cell->getXfIndex() + 15; // there are 15 cell style Xfs
412
413 52
            $cVal = $cell->getValue();
414 52
            if ($cVal instanceof RichText) {
415 9
                $arrcRun = [];
416 9
                $str_len = StringHelper::countCharacters($cVal->getPlainText(), 'UTF-8');
417 9
                $str_pos = 0;
418 9
                $elements = $cVal->getRichTextElements();
419 9
                foreach ($elements as $element) {
420
                    // FONT Index
421 9
                    if ($element instanceof Run) {
422 9
                        $str_fontidx = $this->fontHashIndex[$element->getFont()->getHashCode()];
423
                    } else {
424 8
                        $str_fontidx = 0;
425
                    }
426 9
                    $arrcRun[] = ['strlen' => $str_pos, 'fontidx' => $str_fontidx];
427
                    // Position FROM
428 9
                    $str_pos += StringHelper::countCharacters($element->getText(), 'UTF-8');
429
                }
430 9
                $this->writeRichTextString($row, $column, $cVal->getPlainText(), $xfIndex, $arrcRun);
431
            } else {
432 51
                switch ($cell->getDatatype()) {
433
                    case DataType::TYPE_STRING:
434
                    case DataType::TYPE_NULL:
435 44
                        if ($cVal === '' || $cVal === null) {
436 23
                            $this->writeBlank($row, $column, $xfIndex);
437
                        } else {
438 40
                            $this->writeString($row, $column, $cVal, $xfIndex);
439
                        }
440
441 44
                        break;
442
                    case DataType::TYPE_NUMERIC:
443 28
                        $this->writeNumber($row, $column, $cVal, $xfIndex);
444
445 28
                        break;
446
                    case DataType::TYPE_FORMULA:
447 21
                        $calculatedValue = $this->preCalculateFormulas ?
448 21
                            $cell->getCalculatedValue() : null;
449 21
                        if (self::WRITE_FORMULA_EXCEPTION == $this->writeFormula($row, $column, $cVal, $xfIndex, $calculatedValue)) {
450 6
                            if ($calculatedValue === null) {
451
                                $calculatedValue = $cell->getCalculatedValue();
452
                            }
453 6
                            $calctype = gettype($calculatedValue);
454
                            switch ($calctype) {
455 6
                                case 'integer':
456 5
                                case 'double':
457 5
                                    $this->writeNumber($row, $column, $calculatedValue, $xfIndex);
458
459 5
                                    break;
460 5
                                case 'string':
461 5
                                    $this->writeString($row, $column, $calculatedValue, $xfIndex);
462
463 5
                                    break;
464 1
                                case 'boolean':
465 1
                                    $this->writeBoolErr($row, $column, $calculatedValue, 0, $xfIndex);
466
467 1
                                    break;
468
                                default:
469
                                    $this->writeString($row, $column, $cVal, $xfIndex);
470
                            }
471
                        }
472
473 21
                        break;
474
                    case DataType::TYPE_BOOL:
475 8
                        $this->writeBoolErr($row, $column, $cVal, 0, $xfIndex);
476
477 8
                        break;
478
                    case DataType::TYPE_ERROR:
479 1
                        $this->writeBoolErr($row, $column, self::mapErrorCode($cVal), 1, $xfIndex);
480
481 1
                        break;
482
                }
483
            }
484
        }
485
486
        // Append
487 55
        $this->writeMsoDrawing();
488
489
        // Restoring active sheet.
490 55
        $this->phpSheet->getParent()->setActiveSheetIndex($activeSheetIndex);
491
492
        // Write WINDOW2 record
493 55
        $this->writeWindow2();
494
495
        // Write PLV record
496 55
        $this->writePageLayoutView();
497
498
        // Write ZOOM record
499 55
        $this->writeZoom();
500 55
        if ($phpSheet->getFreezePane()) {
501 6
            $this->writePanes();
502
        }
503
504
        // Restoring selected cells.
505 55
        $this->phpSheet->setSelectedCells($selectedCells);
506
507
        // Write SELECTION record
508 55
        $this->writeSelection();
509
510
        // Write MergedCellsTable Record
511 55
        $this->writeMergedCells();
512
513
        // Hyperlinks
514 55
        foreach ($phpSheet->getHyperLinkCollection() as $coordinate => $hyperlink) {
515 9
            [$column, $row] = Coordinate::coordinateFromString($coordinate);
516
517 9
            $url = $hyperlink->getUrl();
518
519 9
            if (strpos($url, 'sheet://') !== false) {
520
                // internal to current workbook
521 6
                $url = str_replace('sheet://', 'internal:', $url);
522 9
            } elseif (preg_match('/^(http:|https:|ftp:|mailto:)/', $url)) {
523
                // URL
524
            } else {
525
                // external (local file)
526
                $url = 'external:' . $url;
527
            }
528
529 9
            $this->writeUrl($row - 1, Coordinate::columnIndexFromString($column) - 1, $url);
530
        }
531
532 55
        $this->writeDataValidity();
533 55
        $this->writeSheetLayout();
534
535
        // Write SHEETPROTECTION record
536 55
        $this->writeSheetProtection();
537 55
        $this->writeRangeProtection();
538
539 55
        $arrConditionalStyles = $phpSheet->getConditionalStylesCollection();
540 55
        if (!empty($arrConditionalStyles)) {
541 2
            $arrConditional = [];
542
            // @TODO CFRule & CFHeader
543
            // Write CFHEADER record
544 2
            $this->writeCFHeader();
545
            // Write ConditionalFormattingTable records
546 2
            foreach ($arrConditionalStyles as $cellCoordinate => $conditionalStyles) {
547 2
                foreach ($conditionalStyles as $conditional) {
548
                    if (
549 2
                        $conditional->getConditionType() == Conditional::CONDITION_EXPRESSION
550 2
                        || $conditional->getConditionType() == Conditional::CONDITION_CELLIS
551
                    ) {
552 2
                        if (!isset($arrConditional[$conditional->getHashCode()])) {
553
                            // This hash code has been handled
554 2
                            $arrConditional[$conditional->getHashCode()] = true;
555
556
                            // Write CFRULE record
557 2
                            $this->writeCFRule($conditional);
558
                        }
559
                    }
560
                }
561
            }
562
        }
563
564 55
        $this->storeEof();
565 55
    }
566
567
    /**
568
     * Write a cell range address in BIFF8
569
     * always fixed range
570
     * See section 2.5.14 in OpenOffice.org's Documentation of the Microsoft Excel File Format.
571
     *
572
     * @param string $range E.g. 'A1' or 'A1:B6'
573
     *
574
     * @return string Binary data
575
     */
576 7
    private function writeBIFF8CellRangeAddressFixed($range)
577
    {
578 7
        $explodes = explode(':', $range);
579
580
        // extract first cell, e.g. 'A1'
581 7
        $firstCell = $explodes[0];
582
583
        // extract last cell, e.g. 'B6'
584 7
        if (count($explodes) == 1) {
585 2
            $lastCell = $firstCell;
586
        } else {
587 5
            $lastCell = $explodes[1];
588
        }
589
590 7
        $firstCellCoordinates = Coordinate::coordinateFromString($firstCell); // e.g. [0, 1]
591 7
        $lastCellCoordinates = Coordinate::coordinateFromString($lastCell); // e.g. [1, 6]
592
593 7
        return pack('vvvv', $firstCellCoordinates[1] - 1, $lastCellCoordinates[1] - 1, Coordinate::columnIndexFromString($firstCellCoordinates[0]) - 1, Coordinate::columnIndexFromString($lastCellCoordinates[0]) - 1);
594
    }
595
596
    /**
597
     * Retrieves data from memory in one chunk, or from disk in $buffer
598
     * sized chunks.
599
     *
600
     * @return string The data
601
     */
602 55
    public function getData()
603
    {
604 55
        $buffer = 4096;
605
606
        // Return data stored in memory
607 55
        if (isset($this->_data)) {
608 55
            $tmp = $this->_data;
609 55
            $this->_data = null;
610
611 55
            return $tmp;
612
        }
613
614
        // No data to return
615
        return false;
616
    }
617
618
    /**
619
     * Set the option to print the row and column headers on the printed page.
620
     *
621
     * @param int $print Whether to print the headers or not. Defaults to 1 (print).
622
     */
623
    public function printRowColHeaders($print = 1): void
624
    {
625
        $this->printHeaders = $print;
626
    }
627
628
    /**
629
     * This method sets the properties for outlining and grouping. The defaults
630
     * correspond to Excel's defaults.
631
     *
632
     * @param bool $visible
633
     * @param bool $symbols_below
634
     * @param bool $symbols_right
635
     * @param bool $auto_style
636
     */
637
    public function setOutline($visible = true, $symbols_below = true, $symbols_right = true, $auto_style = false): void
638
    {
639
        $this->outlineOn = $visible;
640
        $this->outlineBelow = $symbols_below;
641
        $this->outlineRight = $symbols_right;
642
        $this->outlineStyle = $auto_style;
643
644
        // Ensure this is a boolean vale for Window2
645
        if ($this->outlineOn) {
646
            $this->outlineOn = 1;
647
        }
648
    }
649
650
    /**
651
     * Write a double to the specified row and column (zero indexed).
652
     * An integer can be written as a double. Excel will display an
653
     * integer. $format is optional.
654
     *
655
     * Returns  0 : normal termination
656
     *         -2 : row or column out of range
657
     *
658
     * @param int $row Zero indexed row
659
     * @param int $col Zero indexed column
660
     * @param float $num The number to write
661
     * @param mixed $xfIndex The optional XF format
662
     *
663
     * @return int
664
     */
665 29
    private function writeNumber($row, $col, $num, $xfIndex)
666
    {
667 29
        $record = 0x0203; // Record identifier
668 29
        $length = 0x000E; // Number of bytes to follow
669
670 29
        $header = pack('vv', $record, $length);
671 29
        $data = pack('vvv', $row, $col, $xfIndex);
672 29
        $xl_double = pack('d', $num);
673 29
        if (self::getByteOrder()) { // if it's Big Endian
674
            $xl_double = strrev($xl_double);
675
        }
676
677 29
        $this->append($header . $data . $xl_double);
678
679 29
        return 0;
680
    }
681
682
    /**
683
     * Write a LABELSST record or a LABEL record. Which one depends on BIFF version.
684
     *
685
     * @param int $row Row index (0-based)
686
     * @param int $col Column index (0-based)
687
     * @param string $str The string
688
     * @param int $xfIndex Index to XF record
689
     */
690 41
    private function writeString($row, $col, $str, $xfIndex): void
691
    {
692 41
        $this->writeLabelSst($row, $col, $str, $xfIndex);
693 41
    }
694
695
    /**
696
     * Write a LABELSST record or a LABEL record. Which one depends on BIFF version
697
     * It differs from writeString by the writing of rich text strings.
698
     *
699
     * @param int $row Row index (0-based)
700
     * @param int $col Column index (0-based)
701
     * @param string $str The string
702
     * @param int $xfIndex The XF format index for the cell
703
     * @param array $arrcRun Index to Font record and characters beginning
704
     */
705 9
    private function writeRichTextString($row, $col, $str, $xfIndex, $arrcRun): void
706
    {
707 9
        $record = 0x00FD; // Record identifier
708 9
        $length = 0x000A; // Bytes to follow
709 9
        $str = StringHelper::UTF8toBIFF8UnicodeShort($str, $arrcRun);
710
711
        // check if string is already present
712 9
        if (!isset($this->stringTable[$str])) {
713 9
            $this->stringTable[$str] = $this->stringUnique++;
714
        }
715 9
        ++$this->stringTotal;
716
717 9
        $header = pack('vv', $record, $length);
718 9
        $data = pack('vvvV', $row, $col, $xfIndex, $this->stringTable[$str]);
719 9
        $this->append($header . $data);
720 9
    }
721
722
    /**
723
     * Write a string to the specified row and column (zero indexed).
724
     * This is the BIFF8 version (no 255 chars limit).
725
     * $format is optional.
726
     *
727
     * @param int $row Zero indexed row
728
     * @param int $col Zero indexed column
729
     * @param string $str The string to write
730
     * @param mixed $xfIndex The XF format index for the cell
731
     */
732 41
    private function writeLabelSst($row, $col, $str, $xfIndex): void
733
    {
734 41
        $record = 0x00FD; // Record identifier
735 41
        $length = 0x000A; // Bytes to follow
736
737 41
        $str = StringHelper::UTF8toBIFF8UnicodeLong($str);
738
739
        // check if string is already present
740 41
        if (!isset($this->stringTable[$str])) {
741 41
            $this->stringTable[$str] = $this->stringUnique++;
742
        }
743 41
        ++$this->stringTotal;
744
745 41
        $header = pack('vv', $record, $length);
746 41
        $data = pack('vvvV', $row, $col, $xfIndex, $this->stringTable[$str]);
747 41
        $this->append($header . $data);
748 41
    }
749
750
    /**
751
     * Write a blank cell to the specified row and column (zero indexed).
752
     * A blank cell is used to specify formatting without adding a string
753
     * or a number.
754
     *
755
     * A blank cell without a format serves no purpose. Therefore, we don't write
756
     * a BLANK record unless a format is specified.
757
     *
758
     * Returns  0 : normal termination (including no format)
759
     *         -1 : insufficient number of arguments
760
     *         -2 : row or column out of range
761
     *
762
     * @param int $row Zero indexed row
763
     * @param int $col Zero indexed column
764
     * @param mixed $xfIndex The XF format index
765
     *
766
     * @return int
767
     */
768 23
    public function writeBlank($row, $col, $xfIndex)
769
    {
770 23
        $record = 0x0201; // Record identifier
771 23
        $length = 0x0006; // Number of bytes to follow
772
773 23
        $header = pack('vv', $record, $length);
774 23
        $data = pack('vvv', $row, $col, $xfIndex);
775 23
        $this->append($header . $data);
776
777 23
        return 0;
778
    }
779
780
    /**
781
     * Write a boolean or an error type to the specified row and column (zero indexed).
782
     *
783
     * @param int $row Row index (0-based)
784
     * @param int $col Column index (0-based)
785
     * @param int $value
786
     * @param bool $isError Error or Boolean?
787
     * @param int $xfIndex
788
     *
789
     * @return int
790
     */
791 9
    private function writeBoolErr($row, $col, $value, $isError, $xfIndex)
792
    {
793 9
        $record = 0x0205;
794 9
        $length = 8;
795
796 9
        $header = pack('vv', $record, $length);
797 9
        $data = pack('vvvCC', $row, $col, $xfIndex, $value, $isError);
798 9
        $this->append($header . $data);
799
800 9
        return 0;
801
    }
802
803
    const WRITE_FORMULA_NORMAL = 0;
804
    const WRITE_FORMULA_ERRORS = -1;
805
    const WRITE_FORMULA_RANGE = -2;
806
    const WRITE_FORMULA_EXCEPTION = -3;
807
808
    /**
809
     * Write a formula to the specified row and column (zero indexed).
810
     * The textual representation of the formula is passed to the parser in
811
     * Parser.php which returns a packed binary string.
812
     *
813
     * Returns  0 : WRITE_FORMULA_NORMAL  normal termination
814
     *         -1 : WRITE_FORMULA_ERRORS formula errors (bad formula)
815
     *         -2 : WRITE_FORMULA_RANGE  row or column out of range
816
     *         -3 : WRITE_FORMULA_EXCEPTION parse raised exception, probably due to definedname
817
     *
818
     * @param int $row Zero indexed row
819
     * @param int $col Zero indexed column
820
     * @param string $formula The formula text string
821
     * @param mixed $xfIndex The XF format index
822
     * @param mixed $calculatedValue Calculated value
823
     *
824
     * @return int
825
     */
826 21
    private function writeFormula($row, $col, $formula, $xfIndex, $calculatedValue)
827
    {
828 21
        $record = 0x0006; // Record identifier
829
        // Initialize possible additional value for STRING record that should be written after the FORMULA record?
830 21
        $stringValue = null;
831
832
        // calculated value
833 21
        if (isset($calculatedValue)) {
834
            // Since we can't yet get the data type of the calculated value,
835
            // we use best effort to determine data type
836 21
            if (is_bool($calculatedValue)) {
837
                // Boolean value
838 4
                $num = pack('CCCvCv', 0x01, 0x00, (int) $calculatedValue, 0x00, 0x00, 0xFFFF);
839 21
            } elseif (is_int($calculatedValue) || is_float($calculatedValue)) {
840
                // Numeric value
841 20
                $num = pack('d', $calculatedValue);
842 16
            } elseif (is_string($calculatedValue)) {
843 16
                $errorCodes = DataType::getErrorCodes();
844 16
                if (isset($errorCodes[$calculatedValue])) {
845
                    // Error value
846 12
                    $num = pack('CCCvCv', 0x02, 0x00, self::mapErrorCode($calculatedValue), 0x00, 0x00, 0xFFFF);
847 13
                } elseif ($calculatedValue === '') {
848
                    // Empty string (and BIFF8)
849 5
                    $num = pack('CCCvCv', 0x03, 0x00, 0x00, 0x00, 0x00, 0xFFFF);
850
                } else {
851
                    // Non-empty string value (or empty string BIFF5)
852 8
                    $stringValue = $calculatedValue;
853 16
                    $num = pack('CCCvCv', 0x00, 0x00, 0x00, 0x00, 0x00, 0xFFFF);
854
                }
855
            } else {
856
                // We are really not supposed to reach here
857 21
                $num = pack('d', 0x00);
858
            }
859
        } else {
860
            $num = pack('d', 0x00);
861
        }
862
863 21
        $grbit = 0x03; // Option flags
864 21
        $unknown = 0x0000; // Must be zero
865
866
        // Strip the '=' or '@' sign at the beginning of the formula string
867 21
        if ($formula[0] == '=') {
868 21
            $formula = substr($formula, 1);
869
        } else {
870
            // Error handling
871
            $this->writeString($row, $col, 'Unrecognised character for formula', 0);
872
873
            return self::WRITE_FORMULA_ERRORS;
874
        }
875
876
        // Parse the formula using the parser in Parser.php
877
        try {
878 21
            $error = $this->parser->parse($formula);
879 21
            $formula = $this->parser->toReversePolish();
880
881 20
            $formlen = strlen($formula); // Length of the binary string
882 20
            $length = 0x16 + $formlen; // Length of the record data
883
884 20
            $header = pack('vv', $record, $length);
885
886 20
            $data = pack('vvv', $row, $col, $xfIndex)
887 20
                . $num
888 20
                . pack('vVv', $grbit, $unknown, $formlen);
889 20
            $this->append($header . $data . $formula);
890
891
            // Append also a STRING record if necessary
892 20
            if ($stringValue !== null) {
893 7
                $this->writeStringRecord($stringValue);
894
            }
895
896 20
            return self::WRITE_FORMULA_NORMAL;
897 6
        } catch (PhpSpreadsheetException $e) {
898 6
            return self::WRITE_FORMULA_EXCEPTION;
899
        }
900
    }
901
902
    /**
903
     * Write a STRING record. This.
904
     *
905
     * @param string $stringValue
906
     */
907 7
    private function writeStringRecord($stringValue): void
908
    {
909 7
        $record = 0x0207; // Record identifier
910 7
        $data = StringHelper::UTF8toBIFF8UnicodeLong($stringValue);
911
912 7
        $length = strlen($data);
913 7
        $header = pack('vv', $record, $length);
914
915 7
        $this->append($header . $data);
916 7
    }
917
918
    /**
919
     * Write a hyperlink.
920
     * This is comprised of two elements: the visible label and
921
     * the invisible link. The visible label is the same as the link unless an
922
     * alternative string is specified. The label is written using the
923
     * writeString() method. Therefore the 255 characters string limit applies.
924
     * $string and $format are optional.
925
     *
926
     * The hyperlink can be to a http, ftp, mail, internal sheet (not yet), or external
927
     * directory url.
928
     *
929
     * Returns  0 : normal termination
930
     *         -2 : row or column out of range
931
     *         -3 : long string truncated to 255 chars
932
     *
933
     * @param int $row Row
934
     * @param int $col Column
935
     * @param string $url URL string
936
     *
937
     * @return int
938
     */
939 9
    private function writeUrl($row, $col, $url)
940
    {
941
        // Add start row and col to arg list
942 9
        return $this->writeUrlRange($row, $col, $row, $col, $url);
943
    }
944
945
    /**
946
     * This is the more general form of writeUrl(). It allows a hyperlink to be
947
     * written to a range of cells. This function also decides the type of hyperlink
948
     * to be written. These are either, Web (http, ftp, mailto), Internal
949
     * (Sheet1!A1) or external ('c:\temp\foo.xls#Sheet1!A1').
950
     *
951
     * @param int $row1 Start row
952
     * @param int $col1 Start column
953
     * @param int $row2 End row
954
     * @param int $col2 End column
955
     * @param string $url URL string
956
     *
957
     * @return int
958
     *
959
     * @see writeUrl()
960
     */
961 9
    public function writeUrlRange($row1, $col1, $row2, $col2, $url)
962
    {
963
        // Check for internal/external sheet links or default to web link
964 9
        if (preg_match('[^internal:]', $url)) {
965 6
            return $this->writeUrlInternal($row1, $col1, $row2, $col2, $url);
966
        }
967 9
        if (preg_match('[^external:]', $url)) {
968
            return $this->writeUrlExternal($row1, $col1, $row2, $col2, $url);
969
        }
970
971 9
        return $this->writeUrlWeb($row1, $col1, $row2, $col2, $url);
972
    }
973
974
    /**
975
     * Used to write http, ftp and mailto hyperlinks.
976
     * The link type ($options) is 0x03 is the same as absolute dir ref without
977
     * sheet. However it is differentiated by the $unknown2 data stream.
978
     *
979
     * @param int $row1 Start row
980
     * @param int $col1 Start column
981
     * @param int $row2 End row
982
     * @param int $col2 End column
983
     * @param string $url URL string
984
     *
985
     * @return int
986
     *
987
     * @see writeUrl()
988
     */
989 9
    public function writeUrlWeb($row1, $col1, $row2, $col2, $url)
990
    {
991 9
        $record = 0x01B8; // Record identifier
992 9
        $length = 0x00000; // Bytes to follow
993
994
        // Pack the undocumented parts of the hyperlink stream
995 9
        $unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000');
996 9
        $unknown2 = pack('H*', 'E0C9EA79F9BACE118C8200AA004BA90B');
997
998
        // Pack the option flags
999 9
        $options = pack('V', 0x03);
1000
1001
        // Convert URL to a null terminated wchar string
1002 9
        $url = implode("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));
1003 9
        $url = $url . "\0\0\0";
1004
1005
        // Pack the length of the URL
1006 9
        $url_len = pack('V', strlen($url));
1007
1008
        // Calculate the data length
1009 9
        $length = 0x34 + strlen($url);
1010
1011
        // Pack the header data
1012 9
        $header = pack('vv', $record, $length);
1013 9
        $data = pack('vvvv', $row1, $row2, $col1, $col2);
1014
1015
        // Write the packed data
1016 9
        $this->append($header . $data . $unknown1 . $options . $unknown2 . $url_len . $url);
1017
1018 9
        return 0;
1019
    }
1020
1021
    /**
1022
     * Used to write internal reference hyperlinks such as "Sheet1!A1".
1023
     *
1024
     * @param int $row1 Start row
1025
     * @param int $col1 Start column
1026
     * @param int $row2 End row
1027
     * @param int $col2 End column
1028
     * @param string $url URL string
1029
     *
1030
     * @return int
1031
     *
1032
     * @see writeUrl()
1033
     */
1034 6
    public function writeUrlInternal($row1, $col1, $row2, $col2, $url)
1035
    {
1036 6
        $record = 0x01B8; // Record identifier
1037 6
        $length = 0x00000; // Bytes to follow
1038
1039
        // Strip URL type
1040 6
        $url = preg_replace('/^internal:/', '', $url);
1041
1042
        // Pack the undocumented parts of the hyperlink stream
1043 6
        $unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000');
1044
1045
        // Pack the option flags
1046 6
        $options = pack('V', 0x08);
1047
1048
        // Convert the URL type and to a null terminated wchar string
1049 6
        $url .= "\0";
1050
1051
        // character count
1052 6
        $url_len = StringHelper::countCharacters($url);
1053 6
        $url_len = pack('V', $url_len);
1054
1055 6
        $url = StringHelper::convertEncoding($url, 'UTF-16LE', 'UTF-8');
1056
1057
        // Calculate the data length
1058 6
        $length = 0x24 + strlen($url);
1059
1060
        // Pack the header data
1061 6
        $header = pack('vv', $record, $length);
1062 6
        $data = pack('vvvv', $row1, $row2, $col1, $col2);
1063
1064
        // Write the packed data
1065 6
        $this->append($header . $data . $unknown1 . $options . $url_len . $url);
1066
1067 6
        return 0;
1068
    }
1069
1070
    /**
1071
     * Write links to external directory names such as 'c:\foo.xls',
1072
     * c:\foo.xls#Sheet1!A1', '../../foo.xls'. and '../../foo.xls#Sheet1!A1'.
1073
     *
1074
     * Note: Excel writes some relative links with the $dir_long string. We ignore
1075
     * these cases for the sake of simpler code.
1076
     *
1077
     * @param int $row1 Start row
1078
     * @param int $col1 Start column
1079
     * @param int $row2 End row
1080
     * @param int $col2 End column
1081
     * @param string $url URL string
1082
     *
1083
     * @return int
1084
     *
1085
     * @see writeUrl()
1086
     */
1087
    public function writeUrlExternal($row1, $col1, $row2, $col2, $url)
1088
    {
1089
        // Network drives are different. We will handle them separately
1090
        // MS/Novell network drives and shares start with \\
1091
        if (preg_match('[^external:\\\\]', $url)) {
1092
            return; //($this->writeUrlExternal_net($row1, $col1, $row2, $col2, $url, $str, $format));
1093
        }
1094
1095
        $record = 0x01B8; // Record identifier
1096
        $length = 0x00000; // Bytes to follow
1097
1098
        // Strip URL type and change Unix dir separator to Dos style (if needed)
1099
        //
1100
        $url = preg_replace('/^external:/', '', $url);
1101
        $url = preg_replace('/\//', '\\', $url);
1102
1103
        // Determine if the link is relative or absolute:
1104
        //   relative if link contains no dir separator, "somefile.xls"
1105
        //   relative if link starts with up-dir, "..\..\somefile.xls"
1106
        //   otherwise, absolute
1107
1108
        $absolute = 0x00; // relative path
1109
        if (preg_match('/^[A-Z]:/', $url)) {
1110
            $absolute = 0x02; // absolute path on Windows, e.g. C:\...
1111
        }
1112
        $link_type = 0x01 | $absolute;
1113
1114
        // Determine if the link contains a sheet reference and change some of the
1115
        // parameters accordingly.
1116
        // Split the dir name and sheet name (if it exists)
1117
        $dir_long = $url;
1118
        if (preg_match('/\\#/', $url)) {
1119
            $link_type |= 0x08;
1120
        }
1121
1122
        // Pack the link type
1123
        $link_type = pack('V', $link_type);
1124
1125
        // Calculate the up-level dir count e.g.. (..\..\..\ == 3)
1126
        $up_count = preg_match_all('/\\.\\.\\\\/', $dir_long, $useless);
1127
        $up_count = pack('v', $up_count);
1128
1129
        // Store the short dos dir name (null terminated)
1130
        $dir_short = preg_replace('/\\.\\.\\\\/', '', $dir_long) . "\0";
1131
1132
        // Store the long dir name as a wchar string (non-null terminated)
1133
        $dir_long = $dir_long . "\0";
1134
1135
        // Pack the lengths of the dir strings
1136
        $dir_short_len = pack('V', strlen($dir_short));
1137
        $dir_long_len = pack('V', strlen($dir_long));
1138
        $stream_len = pack('V', 0); //strlen($dir_long) + 0x06);
1139
1140
        // Pack the undocumented parts of the hyperlink stream
1141
        $unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000');
1142
        $unknown2 = pack('H*', '0303000000000000C000000000000046');
1143
        $unknown3 = pack('H*', 'FFFFADDE000000000000000000000000000000000000000');
1144
        $unknown4 = pack('v', 0x03);
1145
1146
        // Pack the main data stream
1147
        $data = pack('vvvv', $row1, $row2, $col1, $col2) .
1148
            $unknown1 .
1149
            $link_type .
1150
            $unknown2 .
1151
            $up_count .
1152
            $dir_short_len .
1153
            $dir_short .
1154
            $unknown3 .
1155
            $stream_len; /*.
1156
                          $dir_long_len .
1157
                          $unknown4     .
1158
                          $dir_long     .
1159
                          $sheet_len    .
1160
                          $sheet        ;*/
1161
1162
        // Pack the header data
1163
        $length = strlen($data);
1164
        $header = pack('vv', $record, $length);
1165
1166
        // Write the packed data
1167
        $this->append($header . $data);
1168
1169
        return 0;
1170
    }
1171
1172
    /**
1173
     * This method is used to set the height and format for a row.
1174
     *
1175
     * @param int $row The row to set
1176
     * @param int $height Height we are giving to the row.
1177
     *                        Use null to set XF without setting height
1178
     * @param int $xfIndex The optional cell style Xf index to apply to the columns
1179
     * @param bool $hidden The optional hidden attribute
1180
     * @param int $level The optional outline level for row, in range [0,7]
1181
     */
1182 39
    private function writeRow($row, $height, $xfIndex, $hidden = false, $level = 0): void
1183
    {
1184 39
        $record = 0x0208; // Record identifier
1185 39
        $length = 0x0010; // Number of bytes to follow
1186
1187 39
        $colMic = 0x0000; // First defined column
1188 39
        $colMac = 0x0000; // Last defined column
1189 39
        $irwMac = 0x0000; // Used by Excel to optimise loading
1190 39
        $reserved = 0x0000; // Reserved
1191 39
        $grbit = 0x0000; // Option flags
1192 39
        $ixfe = $xfIndex;
1193
1194 39
        if ($height < 0) {
1195 38
            $height = null;
1196
        }
1197
1198
        // Use writeRow($row, null, $XF) to set XF format without setting height
1199 39
        if ($height != null) {
1200 7
            $miyRw = $height * 20; // row height
1201
        } else {
1202 38
            $miyRw = 0xff; // default row height is 256
1203
        }
1204
1205
        // Set the options flags. fUnsynced is used to show that the font and row
1206
        // heights are not compatible. This is usually the case for WriteExcel.
1207
        // The collapsed flag 0x10 doesn't seem to be used to indicate that a row
1208
        // is collapsed. Instead it is used to indicate that the previous row is
1209
        // collapsed. The zero height flag, 0x20, is used to collapse a row.
1210
1211 39
        $grbit |= $level;
1212 39
        if ($hidden) {
1213 3
            $grbit |= 0x0030;
1214
        }
1215 39
        if ($height !== null) {
1216 7
            $grbit |= 0x0040; // fUnsynced
1217
        }
1218 39
        if ($xfIndex !== 0xF) {
1219
            $grbit |= 0x0080;
1220
        }
1221 39
        $grbit |= 0x0100;
1222
1223 39
        $header = pack('vv', $record, $length);
1224 39
        $data = pack('vvvvvvvv', $row, $colMic, $colMac, $miyRw, $irwMac, $reserved, $grbit, $ixfe);
1225 39
        $this->append($header . $data);
1226 39
    }
1227
1228
    /**
1229
     * Writes Excel DIMENSIONS to define the area in which there is data.
1230
     */
1231 55
    private function writeDimensions(): void
1232
    {
1233 55
        $record = 0x0200; // Record identifier
1234
1235 55
        $length = 0x000E;
1236 55
        $data = pack('VVvvv', $this->firstRowIndex, $this->lastRowIndex + 1, $this->firstColumnIndex, $this->lastColumnIndex + 1, 0x0000); // reserved
1237
1238 55
        $header = pack('vv', $record, $length);
1239 55
        $this->append($header . $data);
1240 55
    }
1241
1242
    /**
1243
     * Write BIFF record Window2.
1244
     */
1245 55
    private function writeWindow2(): void
1246
    {
1247 55
        $record = 0x023E; // Record identifier
1248 55
        $length = 0x0012;
1249
1250 55
        $grbit = 0x00B6; // Option flags
1251 55
        $rwTop = 0x0000; // Top row visible in window
1252 55
        $colLeft = 0x0000; // Leftmost column visible in window
1253
1254
        // The options flags that comprise $grbit
1255 55
        $fDspFmla = 0; // 0 - bit
1256 55
        $fDspGrid = $this->phpSheet->getShowGridlines() ? 1 : 0; // 1
1257 55
        $fDspRwCol = $this->phpSheet->getShowRowColHeaders() ? 1 : 0; // 2
1258 55
        $fFrozen = $this->phpSheet->getFreezePane() ? 1 : 0; // 3
1259 55
        $fDspZeros = 1; // 4
1260 55
        $fDefaultHdr = 1; // 5
1261 55
        $fArabic = $this->phpSheet->getRightToLeft() ? 1 : 0; // 6
1262 55
        $fDspGuts = $this->outlineOn; // 7
1263 55
        $fFrozenNoSplit = 0; // 0 - bit
1264
        // no support in PhpSpreadsheet for selected sheet, therefore sheet is only selected if it is the active sheet
1265 55
        $fSelected = ($this->phpSheet === $this->phpSheet->getParent()->getActiveSheet()) ? 1 : 0;
1266 55
        $fPageBreakPreview = $this->phpSheet->getSheetView()->getView() === SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW;
1267
1268 55
        $grbit = $fDspFmla;
1269 55
        $grbit |= $fDspGrid << 1;
1270 55
        $grbit |= $fDspRwCol << 2;
1271 55
        $grbit |= $fFrozen << 3;
1272 55
        $grbit |= $fDspZeros << 4;
1273 55
        $grbit |= $fDefaultHdr << 5;
1274 55
        $grbit |= $fArabic << 6;
1275 55
        $grbit |= $fDspGuts << 7;
1276 55
        $grbit |= $fFrozenNoSplit << 8;
1277 55
        $grbit |= $fSelected << 9; // Selected sheets.
1278 55
        $grbit |= $fSelected << 10; // Active sheet.
1279 55
        $grbit |= $fPageBreakPreview << 11;
1280
1281 55
        $header = pack('vv', $record, $length);
1282 55
        $data = pack('vvv', $grbit, $rwTop, $colLeft);
1283
1284
        // FIXME !!!
1285 55
        $rgbHdr = 0x0040; // Row/column heading and gridline color index
1286 55
        $zoom_factor_page_break = ($fPageBreakPreview ? $this->phpSheet->getSheetView()->getZoomScale() : 0x0000);
1287 55
        $zoom_factor_normal = $this->phpSheet->getSheetView()->getZoomScaleNormal();
1288
1289 55
        $data .= pack('vvvvV', $rgbHdr, 0x0000, $zoom_factor_page_break, $zoom_factor_normal, 0x00000000);
1290
1291 55
        $this->append($header . $data);
1292 55
    }
1293
1294
    /**
1295
     * Write BIFF record DEFAULTROWHEIGHT.
1296
     */
1297 55
    private function writeDefaultRowHeight(): void
1298
    {
1299 55
        $defaultRowHeight = $this->phpSheet->getDefaultRowDimension()->getRowHeight();
1300
1301 55
        if ($defaultRowHeight < 0) {
1302 51
            return;
1303
        }
1304
1305
        // convert to twips
1306 4
        $defaultRowHeight = (int) 20 * $defaultRowHeight;
1307
1308 4
        $record = 0x0225; // Record identifier
1309 4
        $length = 0x0004; // Number of bytes to follow
1310
1311 4
        $header = pack('vv', $record, $length);
1312 4
        $data = pack('vv', 1, $defaultRowHeight);
1313 4
        $this->append($header . $data);
1314 4
    }
1315
1316
    /**
1317
     * Write BIFF record DEFCOLWIDTH if COLINFO records are in use.
1318
     */
1319 55
    private function writeDefcol(): void
1320
    {
1321 55
        $defaultColWidth = 8;
1322
1323 55
        $record = 0x0055; // Record identifier
1324 55
        $length = 0x0002; // Number of bytes to follow
1325
1326 55
        $header = pack('vv', $record, $length);
1327 55
        $data = pack('v', $defaultColWidth);
1328 55
        $this->append($header . $data);
1329 55
    }
1330
1331
    /**
1332
     * Write BIFF record COLINFO to define column widths.
1333
     *
1334
     * Note: The SDK says the record length is 0x0B but Excel writes a 0x0C
1335
     * length record.
1336
     *
1337
     * @param array $col_array This is the only parameter received and is composed of the following:
1338
     *                0 => First formatted column,
1339
     *                1 => Last formatted column,
1340
     *                2 => Col width (8.43 is Excel default),
1341
     *                3 => The optional XF format of the column,
1342
     *                4 => Option flags.
1343
     *                5 => Optional outline level
1344
     */
1345 55
    private function writeColinfo($col_array): void
1346
    {
1347 55
        $colFirst = $col_array[0] ?? null;
1348 55
        $colLast = $col_array[1] ?? null;
1349 55
        $coldx = $col_array[2] ?? 8.43;
1350 55
        $xfIndex = $col_array[3] ?? 15;
1351 55
        $grbit = $col_array[4] ?? 0;
1352 55
        $level = $col_array[5] ?? 0;
1353
1354 55
        $record = 0x007D; // Record identifier
1355 55
        $length = 0x000C; // Number of bytes to follow
1356
1357 55
        $coldx *= 256; // Convert to units of 1/256 of a char
1358
1359 55
        $ixfe = $xfIndex;
1360 55
        $reserved = 0x0000; // Reserved
1361
1362 55
        $level = max(0, min($level, 7));
1363 55
        $grbit |= $level << 8;
1364
1365 55
        $header = pack('vv', $record, $length);
1366 55
        $data = pack('vvvvvv', $colFirst, $colLast, $coldx, $ixfe, $grbit, $reserved);
1367 55
        $this->append($header . $data);
1368 55
    }
1369
1370
    /**
1371
     * Write BIFF record SELECTION.
1372
     */
1373 55
    private function writeSelection(): void
1374
    {
1375
        // look up the selected cell range
1376 55
        $selectedCells = Coordinate::splitRange($this->phpSheet->getSelectedCells());
1377 55
        $selectedCells = $selectedCells[0];
1378 55
        if (count($selectedCells) == 2) {
1379 14
            [$first, $last] = $selectedCells;
1380
        } else {
1381 49
            $first = $selectedCells[0];
1382 49
            $last = $selectedCells[0];
1383
        }
1384
1385 55
        [$colFirst, $rwFirst] = Coordinate::coordinateFromString($first);
1386 55
        $colFirst = Coordinate::columnIndexFromString($colFirst) - 1; // base 0 column index
1387 55
        --$rwFirst; // base 0 row index
1388
1389 55
        [$colLast, $rwLast] = Coordinate::coordinateFromString($last);
1390 55
        $colLast = Coordinate::columnIndexFromString($colLast) - 1; // base 0 column index
1391 55
        --$rwLast; // base 0 row index
1392
1393
        // make sure we are not out of bounds
1394 55
        $colFirst = min($colFirst, 255);
1395 55
        $colLast = min($colLast, 255);
1396
1397 55
        $rwFirst = min($rwFirst, 65535);
1398 55
        $rwLast = min($rwLast, 65535);
1399
1400 55
        $record = 0x001D; // Record identifier
1401 55
        $length = 0x000F; // Number of bytes to follow
1402
1403 55
        $pnn = $this->activePane; // Pane position
1404 55
        $rwAct = $rwFirst; // Active row
1405 55
        $colAct = $colFirst; // Active column
1406 55
        $irefAct = 0; // Active cell ref
1407 55
        $cref = 1; // Number of refs
1408
1409
        // Swap last row/col for first row/col as necessary
1410 55
        if ($rwFirst > $rwLast) {
1411
            [$rwFirst, $rwLast] = [$rwLast, $rwFirst];
1412
        }
1413
1414 55
        if ($colFirst > $colLast) {
1415
            [$colFirst, $colLast] = [$colLast, $colFirst];
1416
        }
1417
1418 55
        $header = pack('vv', $record, $length);
1419 55
        $data = pack('CvvvvvvCC', $pnn, $rwAct, $colAct, $irefAct, $cref, $rwFirst, $rwLast, $colFirst, $colLast);
1420 55
        $this->append($header . $data);
1421 55
    }
1422
1423
    /**
1424
     * Store the MERGEDCELLS records for all ranges of merged cells.
1425
     */
1426 55
    private function writeMergedCells(): void
1427
    {
1428 55
        $mergeCells = $this->phpSheet->getMergeCells();
1429 55
        $countMergeCells = count($mergeCells);
1430
1431 55
        if ($countMergeCells == 0) {
1432 53
            return;
1433
        }
1434
1435
        // maximum allowed number of merged cells per record
1436 11
        $maxCountMergeCellsPerRecord = 1027;
1437
1438
        // record identifier
1439 11
        $record = 0x00E5;
1440
1441
        // counter for total number of merged cells treated so far by the writer
1442 11
        $i = 0;
1443
1444
        // counter for number of merged cells written in record currently being written
1445 11
        $j = 0;
1446
1447
        // initialize record data
1448 11
        $recordData = '';
1449
1450
        // loop through the merged cells
1451 11
        foreach ($mergeCells as $mergeCell) {
1452 11
            ++$i;
1453 11
            ++$j;
1454
1455
            // extract the row and column indexes
1456 11
            $range = Coordinate::splitRange($mergeCell);
1457 11
            [$first, $last] = $range[0];
1458 11
            [$firstColumn, $firstRow] = Coordinate::coordinateFromString($first);
1459 11
            [$lastColumn, $lastRow] = Coordinate::coordinateFromString($last);
1460
1461 11
            $recordData .= pack('vvvv', $firstRow - 1, $lastRow - 1, Coordinate::columnIndexFromString($firstColumn) - 1, Coordinate::columnIndexFromString($lastColumn) - 1);
1462
1463
            // flush record if we have reached limit for number of merged cells, or reached final merged cell
1464 11
            if ($j == $maxCountMergeCellsPerRecord || $i == $countMergeCells) {
1465 11
                $recordData = pack('v', $j) . $recordData;
1466 11
                $length = strlen($recordData);
1467 11
                $header = pack('vv', $record, $length);
1468 11
                $this->append($header . $recordData);
1469
1470
                // initialize for next record, if any
1471 11
                $recordData = '';
1472 11
                $j = 0;
1473
            }
1474
        }
1475 11
    }
1476
1477
    /**
1478
     * Write SHEETLAYOUT record.
1479
     */
1480 55
    private function writeSheetLayout(): void
1481
    {
1482 55
        if (!$this->phpSheet->isTabColorSet()) {
1483 55
            return;
1484
        }
1485
1486 5
        $recordData = pack(
1487 5
            'vvVVVvv',
1488 5
            0x0862,
1489 5
            0x0000, // unused
1490 5
            0x00000000, // unused
1491 5
            0x00000000, // unused
1492 5
            0x00000014, // size of record data
1493 5
            $this->colors[$this->phpSheet->getTabColor()->getRGB()], // color index
1494 5
            0x0000        // unused
1495
        );
1496
1497 5
        $length = strlen($recordData);
1498
1499 5
        $record = 0x0862; // Record identifier
1500 5
        $header = pack('vv', $record, $length);
1501 5
        $this->append($header . $recordData);
1502 5
    }
1503
1504
    /**
1505
     * Write SHEETPROTECTION.
1506
     */
1507 55
    private function writeSheetProtection(): void
1508
    {
1509
        // record identifier
1510 55
        $record = 0x0867;
1511
1512
        // prepare options
1513 55
        $options = (int) !$this->phpSheet->getProtection()->getObjects()
1514 55
            | (int) !$this->phpSheet->getProtection()->getScenarios() << 1
1515 55
            | (int) !$this->phpSheet->getProtection()->getFormatCells() << 2
1516 55
            | (int) !$this->phpSheet->getProtection()->getFormatColumns() << 3
1517 55
            | (int) !$this->phpSheet->getProtection()->getFormatRows() << 4
1518 55
            | (int) !$this->phpSheet->getProtection()->getInsertColumns() << 5
1519 55
            | (int) !$this->phpSheet->getProtection()->getInsertRows() << 6
1520 55
            | (int) !$this->phpSheet->getProtection()->getInsertHyperlinks() << 7
1521 55
            | (int) !$this->phpSheet->getProtection()->getDeleteColumns() << 8
1522 55
            | (int) !$this->phpSheet->getProtection()->getDeleteRows() << 9
1523 55
            | (int) !$this->phpSheet->getProtection()->getSelectLockedCells() << 10
1524 55
            | (int) !$this->phpSheet->getProtection()->getSort() << 11
1525 55
            | (int) !$this->phpSheet->getProtection()->getAutoFilter() << 12
1526 55
            | (int) !$this->phpSheet->getProtection()->getPivotTables() << 13
1527 55
            | (int) !$this->phpSheet->getProtection()->getSelectUnlockedCells() << 14;
1528
1529
        // record data
1530 55
        $recordData = pack(
1531 55
            'vVVCVVvv',
1532 55
            0x0867, // repeated record identifier
1533 55
            0x0000, // not used
1534 55
            0x0000, // not used
1535 55
            0x00, // not used
1536 55
            0x01000200, // unknown data
1537 55
            0xFFFFFFFF, // unknown data
1538
            $options, // options
1539 55
            0x0000 // not used
1540
        );
1541
1542 55
        $length = strlen($recordData);
1543 55
        $header = pack('vv', $record, $length);
1544
1545 55
        $this->append($header . $recordData);
1546 55
    }
1547
1548
    /**
1549
     * Write BIFF record RANGEPROTECTION.
1550
     *
1551
     * Openoffice.org's Documentaion of the Microsoft Excel File Format uses term RANGEPROTECTION for these records
1552
     * Microsoft Office Excel 97-2007 Binary File Format Specification uses term FEAT for these records
1553
     */
1554 55
    private function writeRangeProtection(): void
1555
    {
1556 55
        foreach ($this->phpSheet->getProtectedCells() as $range => $password) {
1557
            // number of ranges, e.g. 'A1:B3 C20:D25'
1558 5
            $cellRanges = explode(' ', $range);
1559 5
            $cref = count($cellRanges);
1560
1561 5
            $recordData = pack(
1562 5
                'vvVVvCVvVv',
1563 5
                0x0868,
1564 5
                0x00,
1565 5
                0x0000,
1566 5
                0x0000,
1567 5
                0x02,
1568 5
                0x0,
1569 5
                0x0000,
1570
                $cref,
1571 5
                0x0000,
1572 5
                0x00
1573
            );
1574
1575 5
            foreach ($cellRanges as $cellRange) {
1576 5
                $recordData .= $this->writeBIFF8CellRangeAddressFixed($cellRange);
1577
            }
1578
1579
            // the rgbFeat structure
1580 5
            $recordData .= pack(
1581 5
                'VV',
1582 5
                0x0000,
1583 5
                hexdec($password)
1584
            );
1585
1586 5
            $recordData .= StringHelper::UTF8toBIFF8UnicodeLong('p' . md5($recordData));
1587
1588 5
            $length = strlen($recordData);
1589
1590 5
            $record = 0x0868; // Record identifier
1591 5
            $header = pack('vv', $record, $length);
1592 5
            $this->append($header . $recordData);
1593
        }
1594 55
    }
1595
1596
    /**
1597
     * Writes the Excel BIFF PANE record.
1598
     * The panes can either be frozen or thawed (unfrozen).
1599
     * Frozen panes are specified in terms of an integer number of rows and columns.
1600
     * Thawed panes are specified in terms of Excel's units for rows and columns.
1601
     */
1602 6
    private function writePanes(): void
1603
    {
1604 6
        $panes = [];
1605 6
        if ($this->phpSheet->getFreezePane()) {
1606 6
            [$column, $row] = Coordinate::coordinateFromString($this->phpSheet->getFreezePane());
1607 6
            $panes[0] = Coordinate::columnIndexFromString($column) - 1;
1608 6
            $panes[1] = $row - 1;
1609
1610 6
            [$leftMostColumn, $topRow] = Coordinate::coordinateFromString($this->phpSheet->getTopLeftCell());
1611
            //Coordinates are zero-based in xls files
1612 6
            $panes[2] = $topRow - 1;
1613 6
            $panes[3] = Coordinate::columnIndexFromString($leftMostColumn) - 1;
1614
        } else {
1615
            // thaw panes
1616
            return;
1617
        }
1618
1619 6
        $x = $panes[0] ?? null;
1620 6
        $y = $panes[1] ?? null;
1621 6
        $rwTop = $panes[2] ?? null;
1622 6
        $colLeft = $panes[3] ?? null;
1623 6
        if (count($panes) > 4) { // if Active pane was received
1624
            $pnnAct = $panes[4];
1625
        } else {
1626 6
            $pnnAct = null;
1627
        }
1628 6
        $record = 0x0041; // Record identifier
1629 6
        $length = 0x000A; // Number of bytes to follow
1630
1631
        // Code specific to frozen or thawed panes.
1632 6
        if ($this->phpSheet->getFreezePane()) {
1633
            // Set default values for $rwTop and $colLeft
1634 6
            if (!isset($rwTop)) {
1635
                $rwTop = $y;
1636
            }
1637 6
            if (!$colLeft) {
1638 6
                $colLeft = $x;
1639
            }
1640
        } else {
1641
            // Set default values for $rwTop and $colLeft
1642
            if (!isset($rwTop)) {
1643
                $rwTop = 0;
1644
            }
1645
            if (!$colLeft) {
1646
                $colLeft = 0;
1647
            }
1648
1649
            // Convert Excel's row and column units to the internal units.
1650
            // The default row height is 12.75
1651
            // The default column width is 8.43
1652
            // The following slope and intersection values were interpolated.
1653
            //
1654
            $y = 20 * $y + 255;
1655
            $x = 113.879 * $x + 390;
1656
        }
1657
1658
        // Determine which pane should be active. There is also the undocumented
1659
        // option to override this should it be necessary: may be removed later.
1660
        //
1661 6
        if (!$pnnAct) {
1662 6
            if ($x != 0 && $y != 0) {
1663 1
                $pnnAct = 0; // Bottom right
1664
            }
1665 6
            if ($x != 0 && $y == 0) {
1666
                $pnnAct = 1; // Top right
1667
            }
1668 6
            if ($x == 0 && $y != 0) {
1669 5
                $pnnAct = 2; // Bottom left
1670
            }
1671 6
            if ($x == 0 && $y == 0) {
1672
                $pnnAct = 3; // Top left
1673
            }
1674
        }
1675
1676 6
        $this->activePane = $pnnAct; // Used in writeSelection
1677
1678 6
        $header = pack('vv', $record, $length);
1679 6
        $data = pack('vvvvv', $x, $y, $rwTop, $colLeft, $pnnAct);
1680 6
        $this->append($header . $data);
1681 6
    }
1682
1683
    /**
1684
     * Store the page setup SETUP BIFF record.
1685
     */
1686 55
    private function writeSetup(): void
1687
    {
1688 55
        $record = 0x00A1; // Record identifier
1689 55
        $length = 0x0022; // Number of bytes to follow
1690
1691 55
        $iPaperSize = $this->phpSheet->getPageSetup()->getPaperSize(); // Paper size
1692 55
        $iScale = $this->phpSheet->getPageSetup()->getScale() ?: 100; // Print scaling factor
1693
1694 55
        $iPageStart = 0x01; // Starting page number
1695 55
        $iFitWidth = (int) $this->phpSheet->getPageSetup()->getFitToWidth(); // Fit to number of pages wide
1696 55
        $iFitHeight = (int) $this->phpSheet->getPageSetup()->getFitToHeight(); // Fit to number of pages high
1697 55
        $grbit = 0x00; // Option flags
1698 55
        $iRes = 0x0258; // Print resolution
1699 55
        $iVRes = 0x0258; // Vertical print resolution
1700
1701 55
        $numHdr = $this->phpSheet->getPageMargins()->getHeader(); // Header Margin
1702
1703 55
        $numFtr = $this->phpSheet->getPageMargins()->getFooter(); // Footer Margin
1704 55
        $iCopies = 0x01; // Number of copies
1705
1706
        // Order of printing pages
1707 55
        $fLeftToRight = $this->phpSheet->getPageSetup()->getPageOrder() === PageSetup::PAGEORDER_DOWN_THEN_OVER
1708 55
            ? 0x1 : 0x0;
1709
        // Page orientation
1710 55
        $fLandscape = ($this->phpSheet->getPageSetup()->getOrientation() == PageSetup::ORIENTATION_LANDSCAPE)
1711 55
            ? 0x0 : 0x1;
1712
1713 55
        $fNoPls = 0x0; // Setup not read from printer
1714 55
        $fNoColor = 0x0; // Print black and white
1715 55
        $fDraft = 0x0; // Print draft quality
1716 55
        $fNotes = 0x0; // Print notes
1717 55
        $fNoOrient = 0x0; // Orientation not set
1718 55
        $fUsePage = 0x0; // Use custom starting page
1719
1720 55
        $grbit = $fLeftToRight;
1721 55
        $grbit |= $fLandscape << 1;
1722 55
        $grbit |= $fNoPls << 2;
1723 55
        $grbit |= $fNoColor << 3;
1724 55
        $grbit |= $fDraft << 4;
1725 55
        $grbit |= $fNotes << 5;
1726 55
        $grbit |= $fNoOrient << 6;
1727 55
        $grbit |= $fUsePage << 7;
1728
1729 55
        $numHdr = pack('d', $numHdr);
1730 55
        $numFtr = pack('d', $numFtr);
1731 55
        if (self::getByteOrder()) { // if it's Big Endian
1732
            $numHdr = strrev($numHdr);
1733
            $numFtr = strrev($numFtr);
1734
        }
1735
1736 55
        $header = pack('vv', $record, $length);
1737 55
        $data1 = pack('vvvvvvvv', $iPaperSize, $iScale, $iPageStart, $iFitWidth, $iFitHeight, $grbit, $iRes, $iVRes);
1738 55
        $data2 = $numHdr . $numFtr;
1739 55
        $data3 = pack('v', $iCopies);
1740 55
        $this->append($header . $data1 . $data2 . $data3);
1741 55
    }
1742
1743
    /**
1744
     * Store the header caption BIFF record.
1745
     */
1746 55
    private function writeHeader(): void
1747
    {
1748 55
        $record = 0x0014; // Record identifier
1749
1750
        /* removing for now
1751
        // need to fix character count (multibyte!)
1752
        if (strlen($this->phpSheet->getHeaderFooter()->getOddHeader()) <= 255) {
1753
            $str      = $this->phpSheet->getHeaderFooter()->getOddHeader();       // header string
1754
        } else {
1755
            $str = '';
1756
        }
1757
        */
1758
1759 55
        $recordData = StringHelper::UTF8toBIFF8UnicodeLong($this->phpSheet->getHeaderFooter()->getOddHeader());
1760 55
        $length = strlen($recordData);
1761
1762 55
        $header = pack('vv', $record, $length);
1763
1764 55
        $this->append($header . $recordData);
1765 55
    }
1766
1767
    /**
1768
     * Store the footer caption BIFF record.
1769
     */
1770 55
    private function writeFooter(): void
1771
    {
1772 55
        $record = 0x0015; // Record identifier
1773
1774
        /* removing for now
1775
        // need to fix character count (multibyte!)
1776
        if (strlen($this->phpSheet->getHeaderFooter()->getOddFooter()) <= 255) {
1777
            $str = $this->phpSheet->getHeaderFooter()->getOddFooter();
1778
        } else {
1779
            $str = '';
1780
        }
1781
        */
1782
1783 55
        $recordData = StringHelper::UTF8toBIFF8UnicodeLong($this->phpSheet->getHeaderFooter()->getOddFooter());
1784 55
        $length = strlen($recordData);
1785
1786 55
        $header = pack('vv', $record, $length);
1787
1788 55
        $this->append($header . $recordData);
1789 55
    }
1790
1791
    /**
1792
     * Store the horizontal centering HCENTER BIFF record.
1793
     */
1794 55
    private function writeHcenter(): void
1795
    {
1796 55
        $record = 0x0083; // Record identifier
1797 55
        $length = 0x0002; // Bytes to follow
1798
1799 55
        $fHCenter = $this->phpSheet->getPageSetup()->getHorizontalCentered() ? 1 : 0; // Horizontal centering
1800
1801 55
        $header = pack('vv', $record, $length);
1802 55
        $data = pack('v', $fHCenter);
1803
1804 55
        $this->append($header . $data);
1805 55
    }
1806
1807
    /**
1808
     * Store the vertical centering VCENTER BIFF record.
1809
     */
1810 55
    private function writeVcenter(): void
1811
    {
1812 55
        $record = 0x0084; // Record identifier
1813 55
        $length = 0x0002; // Bytes to follow
1814
1815 55
        $fVCenter = $this->phpSheet->getPageSetup()->getVerticalCentered() ? 1 : 0; // Horizontal centering
1816
1817 55
        $header = pack('vv', $record, $length);
1818 55
        $data = pack('v', $fVCenter);
1819 55
        $this->append($header . $data);
1820 55
    }
1821
1822
    /**
1823
     * Store the LEFTMARGIN BIFF record.
1824
     */
1825 55
    private function writeMarginLeft(): void
1826
    {
1827 55
        $record = 0x0026; // Record identifier
1828 55
        $length = 0x0008; // Bytes to follow
1829
1830 55
        $margin = $this->phpSheet->getPageMargins()->getLeft(); // Margin in inches
1831
1832 55
        $header = pack('vv', $record, $length);
1833 55
        $data = pack('d', $margin);
1834 55
        if (self::getByteOrder()) { // if it's Big Endian
1835
            $data = strrev($data);
1836
        }
1837
1838 55
        $this->append($header . $data);
1839 55
    }
1840
1841
    /**
1842
     * Store the RIGHTMARGIN BIFF record.
1843
     */
1844 55
    private function writeMarginRight(): void
1845
    {
1846 55
        $record = 0x0027; // Record identifier
1847 55
        $length = 0x0008; // Bytes to follow
1848
1849 55
        $margin = $this->phpSheet->getPageMargins()->getRight(); // Margin in inches
1850
1851 55
        $header = pack('vv', $record, $length);
1852 55
        $data = pack('d', $margin);
1853 55
        if (self::getByteOrder()) { // if it's Big Endian
1854
            $data = strrev($data);
1855
        }
1856
1857 55
        $this->append($header . $data);
1858 55
    }
1859
1860
    /**
1861
     * Store the TOPMARGIN BIFF record.
1862
     */
1863 55
    private function writeMarginTop(): void
1864
    {
1865 55
        $record = 0x0028; // Record identifier
1866 55
        $length = 0x0008; // Bytes to follow
1867
1868 55
        $margin = $this->phpSheet->getPageMargins()->getTop(); // Margin in inches
1869
1870 55
        $header = pack('vv', $record, $length);
1871 55
        $data = pack('d', $margin);
1872 55
        if (self::getByteOrder()) { // if it's Big Endian
1873
            $data = strrev($data);
1874
        }
1875
1876 55
        $this->append($header . $data);
1877 55
    }
1878
1879
    /**
1880
     * Store the BOTTOMMARGIN BIFF record.
1881
     */
1882 55
    private function writeMarginBottom(): void
1883
    {
1884 55
        $record = 0x0029; // Record identifier
1885 55
        $length = 0x0008; // Bytes to follow
1886
1887 55
        $margin = $this->phpSheet->getPageMargins()->getBottom(); // Margin in inches
1888
1889 55
        $header = pack('vv', $record, $length);
1890 55
        $data = pack('d', $margin);
1891 55
        if (self::getByteOrder()) { // if it's Big Endian
1892
            $data = strrev($data);
1893
        }
1894
1895 55
        $this->append($header . $data);
1896 55
    }
1897
1898
    /**
1899
     * Write the PRINTHEADERS BIFF record.
1900
     */
1901 55
    private function writePrintHeaders(): void
1902
    {
1903 55
        $record = 0x002a; // Record identifier
1904 55
        $length = 0x0002; // Bytes to follow
1905
1906 55
        $fPrintRwCol = $this->printHeaders; // Boolean flag
1907
1908 55
        $header = pack('vv', $record, $length);
1909 55
        $data = pack('v', $fPrintRwCol);
1910 55
        $this->append($header . $data);
1911 55
    }
1912
1913
    /**
1914
     * Write the PRINTGRIDLINES BIFF record. Must be used in conjunction with the
1915
     * GRIDSET record.
1916
     */
1917 55
    private function writePrintGridlines(): void
1918
    {
1919 55
        $record = 0x002b; // Record identifier
1920 55
        $length = 0x0002; // Bytes to follow
1921
1922 55
        $fPrintGrid = $this->phpSheet->getPrintGridlines() ? 1 : 0; // Boolean flag
1923
1924 55
        $header = pack('vv', $record, $length);
1925 55
        $data = pack('v', $fPrintGrid);
1926 55
        $this->append($header . $data);
1927 55
    }
1928
1929
    /**
1930
     * Write the GRIDSET BIFF record. Must be used in conjunction with the
1931
     * PRINTGRIDLINES record.
1932
     */
1933 55
    private function writeGridset(): void
1934
    {
1935 55
        $record = 0x0082; // Record identifier
1936 55
        $length = 0x0002; // Bytes to follow
1937
1938 55
        $fGridSet = !$this->phpSheet->getPrintGridlines(); // Boolean flag
1939
1940 55
        $header = pack('vv', $record, $length);
1941 55
        $data = pack('v', $fGridSet);
1942 55
        $this->append($header . $data);
1943 55
    }
1944
1945
    /**
1946
     * Write the AUTOFILTERINFO BIFF record. This is used to configure the number of autofilter select used in the sheet.
1947
     */
1948 3
    private function writeAutoFilterInfo(): void
1949
    {
1950 3
        $record = 0x009D; // Record identifier
1951 3
        $length = 0x0002; // Bytes to follow
1952
1953 3
        $rangeBounds = Coordinate::rangeBoundaries($this->phpSheet->getAutoFilter()->getRange());
1954 3
        $iNumFilters = 1 + $rangeBounds[1][0] - $rangeBounds[0][0];
1955
1956 3
        $header = pack('vv', $record, $length);
1957 3
        $data = pack('v', $iNumFilters);
1958 3
        $this->append($header . $data);
1959 3
    }
1960
1961
    /**
1962
     * Write the GUTS BIFF record. This is used to configure the gutter margins
1963
     * where Excel outline symbols are displayed. The visibility of the gutters is
1964
     * controlled by a flag in WSBOOL.
1965
     *
1966
     * @see writeWsbool()
1967
     */
1968 55
    private function writeGuts(): void
1969
    {
1970 55
        $record = 0x0080; // Record identifier
1971 55
        $length = 0x0008; // Bytes to follow
1972
1973 55
        $dxRwGut = 0x0000; // Size of row gutter
1974 55
        $dxColGut = 0x0000; // Size of col gutter
1975
1976
        // determine maximum row outline level
1977 55
        $maxRowOutlineLevel = 0;
1978 55
        foreach ($this->phpSheet->getRowDimensions() as $rowDimension) {
1979 39
            $maxRowOutlineLevel = max($maxRowOutlineLevel, $rowDimension->getOutlineLevel());
1980
        }
1981
1982 55
        $col_level = 0;
1983
1984
        // Calculate the maximum column outline level. The equivalent calculation
1985
        // for the row outline level is carried out in writeRow().
1986 55
        $colcount = count($this->columnInfo);
1987 55
        for ($i = 0; $i < $colcount; ++$i) {
1988 55
            $col_level = max($this->columnInfo[$i][5], $col_level);
1989
        }
1990
1991
        // Set the limits for the outline levels (0 <= x <= 7).
1992 55
        $col_level = max(0, min($col_level, 7));
1993
1994
        // The displayed level is one greater than the max outline levels
1995 55
        if ($maxRowOutlineLevel) {
1996
            ++$maxRowOutlineLevel;
1997
        }
1998 55
        if ($col_level) {
1999 1
            ++$col_level;
2000
        }
2001
2002 55
        $header = pack('vv', $record, $length);
2003 55
        $data = pack('vvvv', $dxRwGut, $dxColGut, $maxRowOutlineLevel, $col_level);
2004
2005 55
        $this->append($header . $data);
2006 55
    }
2007
2008
    /**
2009
     * Write the WSBOOL BIFF record, mainly for fit-to-page. Used in conjunction
2010
     * with the SETUP record.
2011
     */
2012 55
    private function writeWsbool(): void
2013
    {
2014 55
        $record = 0x0081; // Record identifier
2015 55
        $length = 0x0002; // Bytes to follow
2016 55
        $grbit = 0x0000;
2017
2018
        // The only option that is of interest is the flag for fit to page. So we
2019
        // set all the options in one go.
2020
        //
2021
        // Set the option flags
2022 55
        $grbit |= 0x0001; // Auto page breaks visible
2023 55
        if ($this->outlineStyle) {
2024
            $grbit |= 0x0020; // Auto outline styles
2025
        }
2026 55
        if ($this->phpSheet->getShowSummaryBelow()) {
2027 55
            $grbit |= 0x0040; // Outline summary below
2028
        }
2029 55
        if ($this->phpSheet->getShowSummaryRight()) {
2030 55
            $grbit |= 0x0080; // Outline summary right
2031
        }
2032 55
        if ($this->phpSheet->getPageSetup()->getFitToPage()) {
2033
            $grbit |= 0x0100; // Page setup fit to page
2034
        }
2035 55
        if ($this->outlineOn) {
2036 55
            $grbit |= 0x0400; // Outline symbols displayed
2037
        }
2038
2039 55
        $header = pack('vv', $record, $length);
2040 55
        $data = pack('v', $grbit);
2041 55
        $this->append($header . $data);
2042 55
    }
2043
2044
    /**
2045
     * Write the HORIZONTALPAGEBREAKS and VERTICALPAGEBREAKS BIFF records.
2046
     */
2047 55
    private function writeBreaks(): void
2048
    {
2049
        // initialize
2050 55
        $vbreaks = [];
2051 55
        $hbreaks = [];
2052
2053 55
        foreach ($this->phpSheet->getBreaks() as $cell => $breakType) {
2054
            // Fetch coordinates
2055 1
            $coordinates = Coordinate::coordinateFromString($cell);
2056
2057
            // Decide what to do by the type of break
2058 1
            switch ($breakType) {
2059
                case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_COLUMN:
2060
                    // Add to list of vertical breaks
2061
                    $vbreaks[] = Coordinate::columnIndexFromString($coordinates[0]) - 1;
2062
2063
                    break;
2064
                case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_ROW:
2065
                    // Add to list of horizontal breaks
2066 1
                    $hbreaks[] = $coordinates[1];
2067
2068 1
                    break;
2069
                case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_NONE:
2070
                default:
2071
                    // Nothing to do
2072
                    break;
2073
            }
2074
        }
2075
2076
        //horizontal page breaks
2077 55
        if (!empty($hbreaks)) {
2078
            // Sort and filter array of page breaks
2079 1
            sort($hbreaks, SORT_NUMERIC);
2080 1
            if ($hbreaks[0] == 0) { // don't use first break if it's 0
2081
                array_shift($hbreaks);
2082
            }
2083
2084 1
            $record = 0x001b; // Record identifier
2085 1
            $cbrk = count($hbreaks); // Number of page breaks
2086 1
            $length = 2 + 6 * $cbrk; // Bytes to follow
2087
2088 1
            $header = pack('vv', $record, $length);
2089 1
            $data = pack('v', $cbrk);
2090
2091
            // Append each page break
2092 1
            foreach ($hbreaks as $hbreak) {
2093 1
                $data .= pack('vvv', $hbreak, 0x0000, 0x00ff);
2094
            }
2095
2096 1
            $this->append($header . $data);
2097
        }
2098
2099
        // vertical page breaks
2100 55
        if (!empty($vbreaks)) {
2101
            // 1000 vertical pagebreaks appears to be an internal Excel 5 limit.
2102
            // It is slightly higher in Excel 97/200, approx. 1026
2103
            $vbreaks = array_slice($vbreaks, 0, 1000);
2104
2105
            // Sort and filter array of page breaks
2106
            sort($vbreaks, SORT_NUMERIC);
2107
            if ($vbreaks[0] == 0) { // don't use first break if it's 0
2108
                array_shift($vbreaks);
2109
            }
2110
2111
            $record = 0x001a; // Record identifier
2112
            $cbrk = count($vbreaks); // Number of page breaks
2113
            $length = 2 + 6 * $cbrk; // Bytes to follow
2114
2115
            $header = pack('vv', $record, $length);
2116
            $data = pack('v', $cbrk);
2117
2118
            // Append each page break
2119
            foreach ($vbreaks as $vbreak) {
2120
                $data .= pack('vvv', $vbreak, 0x0000, 0xffff);
2121
            }
2122
2123
            $this->append($header . $data);
2124
        }
2125 55
    }
2126
2127
    /**
2128
     * Set the Biff PROTECT record to indicate that the worksheet is protected.
2129
     */
2130 55
    private function writeProtect(): void
2131
    {
2132
        // Exit unless sheet protection has been specified
2133 55
        if (!$this->phpSheet->getProtection()->getSheet()) {
2134 53
            return;
2135
        }
2136
2137 7
        $record = 0x0012; // Record identifier
2138 7
        $length = 0x0002; // Bytes to follow
2139
2140 7
        $fLock = 1; // Worksheet is protected
2141
2142 7
        $header = pack('vv', $record, $length);
2143 7
        $data = pack('v', $fLock);
2144
2145 7
        $this->append($header . $data);
2146 7
    }
2147
2148
    /**
2149
     * Write SCENPROTECT.
2150
     */
2151 55
    private function writeScenProtect(): void
2152
    {
2153
        // Exit if sheet protection is not active
2154 55
        if (!$this->phpSheet->getProtection()->getSheet()) {
2155 53
            return;
2156
        }
2157
2158
        // Exit if scenarios are not protected
2159 7
        if (!$this->phpSheet->getProtection()->getScenarios()) {
2160 7
            return;
2161
        }
2162
2163
        $record = 0x00DD; // Record identifier
2164
        $length = 0x0002; // Bytes to follow
2165
2166
        $header = pack('vv', $record, $length);
2167
        $data = pack('v', 1);
2168
2169
        $this->append($header . $data);
2170
    }
2171
2172
    /**
2173
     * Write OBJECTPROTECT.
2174
     */
2175 55
    private function writeObjectProtect(): void
2176
    {
2177
        // Exit if sheet protection is not active
2178 55
        if (!$this->phpSheet->getProtection()->getSheet()) {
2179 53
            return;
2180
        }
2181
2182
        // Exit if objects are not protected
2183 7
        if (!$this->phpSheet->getProtection()->getObjects()) {
2184 7
            return;
2185
        }
2186
2187
        $record = 0x0063; // Record identifier
2188
        $length = 0x0002; // Bytes to follow
2189
2190
        $header = pack('vv', $record, $length);
2191
        $data = pack('v', 1);
2192
2193
        $this->append($header . $data);
2194
    }
2195
2196
    /**
2197
     * Write the worksheet PASSWORD record.
2198
     */
2199 55
    private function writePassword(): void
2200
    {
2201
        // Exit unless sheet protection and password have been specified
2202 55
        if (!$this->phpSheet->getProtection()->getSheet() || !$this->phpSheet->getProtection()->getPassword()) {
2203 54
            return;
2204
        }
2205
2206 1
        $record = 0x0013; // Record identifier
2207 1
        $length = 0x0002; // Bytes to follow
2208
2209 1
        $wPassword = hexdec($this->phpSheet->getProtection()->getPassword()); // Encoded password
2210
2211 1
        $header = pack('vv', $record, $length);
2212 1
        $data = pack('v', $wPassword);
2213
2214 1
        $this->append($header . $data);
2215 1
    }
2216
2217
    /**
2218
     * Insert a 24bit bitmap image in a worksheet.
2219
     *
2220
     * @param int $row The row we are going to insert the bitmap into
2221
     * @param int $col The column we are going to insert the bitmap into
2222
     * @param mixed $bitmap The bitmap filename or GD-image resource
2223
     * @param int $x the horizontal position (offset) of the image inside the cell
2224
     * @param int $y the vertical position (offset) of the image inside the cell
2225
     * @param float $scale_x The horizontal scale
2226
     * @param float $scale_y The vertical scale
2227
     */
2228
    public function insertBitmap($row, $col, $bitmap, $x = 0, $y = 0, $scale_x = 1, $scale_y = 1): void
2229
    {
2230
        $bitmap_array = (is_resource($bitmap) || $bitmap instanceof GdImage ? $this->processBitmapGd($bitmap) : $this->processBitmap($bitmap));
2231
        [$width, $height, $size, $data] = $bitmap_array;
2232
2233
        // Scale the frame of the image.
2234
        $width *= $scale_x;
2235
        $height *= $scale_y;
2236
2237
        // Calculate the vertices of the image and write the OBJ record
2238
        $this->positionImage($col, $row, $x, $y, $width, $height);
2239
2240
        // Write the IMDATA record to store the bitmap data
2241
        $record = 0x007f;
2242
        $length = 8 + $size;
2243
        $cf = 0x09;
2244
        $env = 0x01;
2245
        $lcb = $size;
2246
2247
        $header = pack('vvvvV', $record, $length, $cf, $env, $lcb);
2248
        $this->append($header . $data);
2249
    }
2250
2251
    /**
2252
     * Calculate the vertices that define the position of the image as required by
2253
     * the OBJ record.
2254
     *
2255
     *         +------------+------------+
2256
     *         |     A      |      B     |
2257
     *   +-----+------------+------------+
2258
     *   |     |(x1,y1)     |            |
2259
     *   |  1  |(A1)._______|______      |
2260
     *   |     |    |              |     |
2261
     *   |     |    |              |     |
2262
     *   +-----+----|    BITMAP    |-----+
2263
     *   |     |    |              |     |
2264
     *   |  2  |    |______________.     |
2265
     *   |     |            |        (B2)|
2266
     *   |     |            |     (x2,y2)|
2267
     *   +---- +------------+------------+
2268
     *
2269
     * Example of a bitmap that covers some of the area from cell A1 to cell B2.
2270
     *
2271
     * Based on the width and height of the bitmap we need to calculate 8 vars:
2272
     *     $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
2273
     * The width and height of the cells are also variable and have to be taken into
2274
     * account.
2275
     * The values of $col_start and $row_start are passed in from the calling
2276
     * function. The values of $col_end and $row_end are calculated by subtracting
2277
     * the width and height of the bitmap from the width and height of the
2278
     * underlying cells.
2279
     * The vertices are expressed as a percentage of the underlying cell width as
2280
     * follows (rhs values are in pixels):
2281
     *
2282
     *       x1 = X / W *1024
2283
     *       y1 = Y / H *256
2284
     *       x2 = (X-1) / W *1024
2285
     *       y2 = (Y-1) / H *256
2286
     *
2287
     *       Where:  X is distance from the left side of the underlying cell
2288
     *               Y is distance from the top of the underlying cell
2289
     *               W is the width of the cell
2290
     *               H is the height of the cell
2291
     * The SDK incorrectly states that the height should be expressed as a
2292
     *        percentage of 1024.
2293
     *
2294
     * @param int $col_start Col containing upper left corner of object
2295
     * @param int $row_start Row containing top left corner of object
2296
     * @param int $x1 Distance to left side of object
2297
     * @param int $y1 Distance to top of object
2298
     * @param int $width Width of image frame
2299
     * @param int $height Height of image frame
2300
     */
2301
    public function positionImage($col_start, $row_start, $x1, $y1, $width, $height): void
2302
    {
2303
        // Initialise end cell to the same as the start cell
2304
        $col_end = $col_start; // Col containing lower right corner of object
2305
        $row_end = $row_start; // Row containing bottom right corner of object
2306
2307
        // Zero the specified offset if greater than the cell dimensions
2308
        if ($x1 >= Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_start + 1))) {
2309
            $x1 = 0;
2310
        }
2311
        if ($y1 >= Xls::sizeRow($this->phpSheet, $row_start + 1)) {
2312
            $y1 = 0;
2313
        }
2314
2315
        $width = $width + $x1 - 1;
2316
        $height = $height + $y1 - 1;
2317
2318
        // Subtract the underlying cell widths to find the end cell of the image
2319
        while ($width >= Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1))) {
2320
            $width -= Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1));
2321
            ++$col_end;
2322
        }
2323
2324
        // Subtract the underlying cell heights to find the end cell of the image
2325
        while ($height >= Xls::sizeRow($this->phpSheet, $row_end + 1)) {
2326
            $height -= Xls::sizeRow($this->phpSheet, $row_end + 1);
2327
            ++$row_end;
2328
        }
2329
2330
        // Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
2331
        // with zero eight or width.
2332
        //
2333
        if (Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_start + 1)) == 0) {
2334
            return;
2335
        }
2336
        if (Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1)) == 0) {
2337
            return;
2338
        }
2339
        if (Xls::sizeRow($this->phpSheet, $row_start + 1) == 0) {
2340
            return;
2341
        }
2342
        if (Xls::sizeRow($this->phpSheet, $row_end + 1) == 0) {
2343
            return;
2344
        }
2345
2346
        // Convert the pixel values to the percentage value expected by Excel
2347
        $x1 = $x1 / Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_start + 1)) * 1024;
2348
        $y1 = $y1 / Xls::sizeRow($this->phpSheet, $row_start + 1) * 256;
2349
        $x2 = $width / Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1)) * 1024; // Distance to right side of object
2350
        $y2 = $height / Xls::sizeRow($this->phpSheet, $row_end + 1) * 256; // Distance to bottom of object
2351
2352
        $this->writeObjPicture($col_start, $x1, $row_start, $y1, $col_end, $x2, $row_end, $y2);
2353
    }
2354
2355
    /**
2356
     * Store the OBJ record that precedes an IMDATA record. This could be generalise
2357
     * to support other Excel objects.
2358
     *
2359
     * @param int $colL Column containing upper left corner of object
2360
     * @param int $dxL Distance from left side of cell
2361
     * @param int $rwT Row containing top left corner of object
2362
     * @param int $dyT Distance from top of cell
2363
     * @param int $colR Column containing lower right corner of object
2364
     * @param int $dxR Distance from right of cell
2365
     * @param int $rwB Row containing bottom right corner of object
2366
     * @param int $dyB Distance from bottom of cell
2367
     */
2368
    private function writeObjPicture($colL, $dxL, $rwT, $dyT, $colR, $dxR, $rwB, $dyB): void
2369
    {
2370
        $record = 0x005d; // Record identifier
2371
        $length = 0x003c; // Bytes to follow
2372
2373
        $cObj = 0x0001; // Count of objects in file (set to 1)
2374
        $OT = 0x0008; // Object type. 8 = Picture
2375
        $id = 0x0001; // Object ID
2376
        $grbit = 0x0614; // Option flags
2377
2378
        $cbMacro = 0x0000; // Length of FMLA structure
2379
        $Reserved1 = 0x0000; // Reserved
2380
        $Reserved2 = 0x0000; // Reserved
2381
2382
        $icvBack = 0x09; // Background colour
2383
        $icvFore = 0x09; // Foreground colour
2384
        $fls = 0x00; // Fill pattern
2385
        $fAuto = 0x00; // Automatic fill
2386
        $icv = 0x08; // Line colour
2387
        $lns = 0xff; // Line style
2388
        $lnw = 0x01; // Line weight
2389
        $fAutoB = 0x00; // Automatic border
2390
        $frs = 0x0000; // Frame style
2391
        $cf = 0x0009; // Image format, 9 = bitmap
2392
        $Reserved3 = 0x0000; // Reserved
2393
        $cbPictFmla = 0x0000; // Length of FMLA structure
2394
        $Reserved4 = 0x0000; // Reserved
2395
        $grbit2 = 0x0001; // Option flags
2396
        $Reserved5 = 0x0000; // Reserved
2397
2398
        $header = pack('vv', $record, $length);
2399
        $data = pack('V', $cObj);
2400
        $data .= pack('v', $OT);
2401
        $data .= pack('v', $id);
2402
        $data .= pack('v', $grbit);
2403
        $data .= pack('v', $colL);
2404
        $data .= pack('v', $dxL);
2405
        $data .= pack('v', $rwT);
2406
        $data .= pack('v', $dyT);
2407
        $data .= pack('v', $colR);
2408
        $data .= pack('v', $dxR);
2409
        $data .= pack('v', $rwB);
2410
        $data .= pack('v', $dyB);
2411
        $data .= pack('v', $cbMacro);
2412
        $data .= pack('V', $Reserved1);
2413
        $data .= pack('v', $Reserved2);
2414
        $data .= pack('C', $icvBack);
2415
        $data .= pack('C', $icvFore);
2416
        $data .= pack('C', $fls);
2417
        $data .= pack('C', $fAuto);
2418
        $data .= pack('C', $icv);
2419
        $data .= pack('C', $lns);
2420
        $data .= pack('C', $lnw);
2421
        $data .= pack('C', $fAutoB);
2422
        $data .= pack('v', $frs);
2423
        $data .= pack('V', $cf);
2424
        $data .= pack('v', $Reserved3);
2425
        $data .= pack('v', $cbPictFmla);
2426
        $data .= pack('v', $Reserved4);
2427
        $data .= pack('v', $grbit2);
2428
        $data .= pack('V', $Reserved5);
2429
2430
        $this->append($header . $data);
2431
    }
2432
2433
    /**
2434
     * Convert a GD-image into the internal format.
2435
     *
2436
     * @param GdImage|resource $image The image to process
2437
     *
2438
     * @return array Array with data and properties of the bitmap
2439
     */
2440
    public function processBitmapGd($image)
2441
    {
2442
        $width = imagesx($image);
2443
        $height = imagesy($image);
2444
2445
        $data = pack('Vvvvv', 0x000c, $width, $height, 0x01, 0x18);
2446
        for ($j = $height; --$j;) {
2447
            for ($i = 0; $i < $width; ++$i) {
2448
                $color = imagecolorsforindex($image, imagecolorat($image, $i, $j));
2449
                foreach (['red', 'green', 'blue'] as $key) {
2450
                    $color[$key] = $color[$key] + round((255 - $color[$key]) * $color['alpha'] / 127);
2451
                }
2452
                $data .= chr($color['blue']) . chr($color['green']) . chr($color['red']);
2453
            }
2454
            if (3 * $width % 4) {
2455
                $data .= str_repeat("\x00", 4 - 3 * $width % 4);
2456
            }
2457
        }
2458
2459
        return [$width, $height, strlen($data), $data];
2460
    }
2461
2462
    /**
2463
     * Convert a 24 bit bitmap into the modified internal format used by Windows.
2464
     * This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the
2465
     * MSDN library.
2466
     *
2467
     * @param string $bitmap The bitmap to process
2468
     *
2469
     * @return array Array with data and properties of the bitmap
2470
     */
2471
    public function processBitmap($bitmap)
2472
    {
2473
        // Open file.
2474
        $bmp_fd = @fopen($bitmap, 'rb');
2475
        if (!$bmp_fd) {
2476
            throw new WriterException("Couldn't import $bitmap");
2477
        }
2478
2479
        // Slurp the file into a string.
2480
        $data = fread($bmp_fd, filesize($bitmap));
2481
2482
        // Check that the file is big enough to be a bitmap.
2483
        if (strlen($data) <= 0x36) {
2484
            throw new WriterException("$bitmap doesn't contain enough data.\n");
2485
        }
2486
2487
        // The first 2 bytes are used to identify the bitmap.
2488
        $identity = unpack('A2ident', $data);
2489
        if ($identity['ident'] != 'BM') {
2490
            throw new WriterException("$bitmap doesn't appear to be a valid bitmap image.\n");
2491
        }
2492
2493
        // Remove bitmap data: ID.
2494
        $data = substr($data, 2);
2495
2496
        // Read and remove the bitmap size. This is more reliable than reading
2497
        // the data size at offset 0x22.
2498
        //
2499
        $size_array = unpack('Vsa', substr($data, 0, 4));
2500
        $size = $size_array['sa'];
2501
        $data = substr($data, 4);
2502
        $size -= 0x36; // Subtract size of bitmap header.
2503
        $size += 0x0C; // Add size of BIFF header.
2504
2505
        // Remove bitmap data: reserved, offset, header length.
2506
        $data = substr($data, 12);
2507
2508
        // Read and remove the bitmap width and height. Verify the sizes.
2509
        $width_and_height = unpack('V2', substr($data, 0, 8));
2510
        $width = $width_and_height[1];
2511
        $height = $width_and_height[2];
2512
        $data = substr($data, 8);
2513
        if ($width > 0xFFFF) {
2514
            throw new WriterException("$bitmap: largest image width supported is 65k.\n");
2515
        }
2516
        if ($height > 0xFFFF) {
2517
            throw new WriterException("$bitmap: largest image height supported is 65k.\n");
2518
        }
2519
2520
        // Read and remove the bitmap planes and bpp data. Verify them.
2521
        $planes_and_bitcount = unpack('v2', substr($data, 0, 4));
2522
        $data = substr($data, 4);
2523
        if ($planes_and_bitcount[2] != 24) { // Bitcount
2524
            throw new WriterException("$bitmap isn't a 24bit true color bitmap.\n");
2525
        }
2526
        if ($planes_and_bitcount[1] != 1) {
2527
            throw new WriterException("$bitmap: only 1 plane supported in bitmap image.\n");
2528
        }
2529
2530
        // Read and remove the bitmap compression. Verify compression.
2531
        $compression = unpack('Vcomp', substr($data, 0, 4));
2532
        $data = substr($data, 4);
2533
2534
        if ($compression['comp'] != 0) {
2535
            throw new WriterException("$bitmap: compression not supported in bitmap image.\n");
2536
        }
2537
2538
        // Remove bitmap data: data size, hres, vres, colours, imp. colours.
2539
        $data = substr($data, 20);
2540
2541
        // Add the BITMAPCOREHEADER data
2542
        $header = pack('Vvvvv', 0x000c, $width, $height, 0x01, 0x18);
2543
        $data = $header . $data;
2544
2545
        return [$width, $height, $size, $data];
2546
    }
2547
2548
    /**
2549
     * Store the window zoom factor. This should be a reduced fraction but for
2550
     * simplicity we will store all fractions with a numerator of 100.
2551
     */
2552 55
    private function writeZoom(): void
2553
    {
2554
        // If scale is 100 we don't need to write a record
2555 55
        if ($this->phpSheet->getSheetView()->getZoomScale() == 100) {
2556 55
            return;
2557
        }
2558
2559
        $record = 0x00A0; // Record identifier
2560
        $length = 0x0004; // Bytes to follow
2561
2562
        $header = pack('vv', $record, $length);
2563
        $data = pack('vv', $this->phpSheet->getSheetView()->getZoomScale(), 100);
2564
        $this->append($header . $data);
2565
    }
2566
2567
    /**
2568
     * Get Escher object.
2569
     *
2570
     * @return \PhpOffice\PhpSpreadsheet\Shared\Escher
2571
     */
2572
    public function getEscher()
2573
    {
2574
        return $this->escher;
2575
    }
2576
2577
    /**
2578
     * Set Escher object.
2579
     *
2580
     * @param \PhpOffice\PhpSpreadsheet\Shared\Escher $pValue
2581
     */
2582 12
    public function setEscher(?\PhpOffice\PhpSpreadsheet\Shared\Escher $pValue = null): void
2583
    {
2584 12
        $this->escher = $pValue;
2585 12
    }
2586
2587
    /**
2588
     * Write MSODRAWING record.
2589
     */
2590 55
    private function writeMsoDrawing(): void
2591
    {
2592
        // write the Escher stream if necessary
2593 55
        if (isset($this->escher)) {
2594 12
            $writer = new Escher($this->escher);
2595 12
            $data = $writer->close();
2596 12
            $spOffsets = $writer->getSpOffsets();
2597 12
            $spTypes = $writer->getSpTypes();
2598
            // write the neccesary MSODRAWING, OBJ records
2599
2600
            // split the Escher stream
2601 12
            $spOffsets[0] = 0;
2602 12
            $nm = count($spOffsets) - 1; // number of shapes excluding first shape
2603 12
            for ($i = 1; $i <= $nm; ++$i) {
2604
                // MSODRAWING record
2605 12
                $record = 0x00EC; // Record identifier
2606
2607
                // chunk of Escher stream for one shape
2608 12
                $dataChunk = substr($data, $spOffsets[$i - 1], $spOffsets[$i] - $spOffsets[$i - 1]);
2609
2610 12
                $length = strlen($dataChunk);
2611 12
                $header = pack('vv', $record, $length);
2612
2613 12
                $this->append($header . $dataChunk);
2614
2615
                // OBJ record
2616 12
                $record = 0x005D; // record identifier
2617 12
                $objData = '';
2618
2619
                // ftCmo
2620 12
                if ($spTypes[$i] == 0x00C9) {
2621
                    // Add ftCmo (common object data) subobject
2622
                    $objData .=
2623 3
                        pack(
2624 3
                            'vvvvvVVV',
2625 3
                            0x0015, // 0x0015 = ftCmo
2626 3
                            0x0012, // length of ftCmo data
2627 3
                            0x0014, // object type, 0x0014 = filter
2628
                            $i, // object id number, Excel seems to use 1-based index, local for the sheet
2629 3
                            0x2101, // option flags, 0x2001 is what OpenOffice.org uses
2630 3
                            0, // reserved
2631 3
                            0, // reserved
2632 3
                            0  // reserved
2633
                        );
2634
2635
                    // Add ftSbs Scroll bar subobject
2636 3
                    $objData .= pack('vv', 0x00C, 0x0014);
2637 3
                    $objData .= pack('H*', '0000000000000000640001000A00000010000100');
2638
                    // Add ftLbsData (List box data) subobject
2639 3
                    $objData .= pack('vv', 0x0013, 0x1FEE);
2640 3
                    $objData .= pack('H*', '00000000010001030000020008005700');
2641
                } else {
2642
                    // Add ftCmo (common object data) subobject
2643
                    $objData .=
2644 9
                        pack(
2645 9
                            'vvvvvVVV',
2646 9
                            0x0015, // 0x0015 = ftCmo
2647 9
                            0x0012, // length of ftCmo data
2648 9
                            0x0008, // object type, 0x0008 = picture
2649
                            $i, // object id number, Excel seems to use 1-based index, local for the sheet
2650 9
                            0x6011, // option flags, 0x6011 is what OpenOffice.org uses
2651 9
                            0, // reserved
2652 9
                            0, // reserved
2653 9
                            0  // reserved
2654
                        );
2655
                }
2656
2657
                // ftEnd
2658
                $objData .=
2659 12
                    pack(
2660 12
                        'vv',
2661 12
                        0x0000, // 0x0000 = ftEnd
2662 12
                        0x0000  // length of ftEnd data
2663
                    );
2664
2665 12
                $length = strlen($objData);
2666 12
                $header = pack('vv', $record, $length);
2667 12
                $this->append($header . $objData);
2668
            }
2669
        }
2670 55
    }
2671
2672
    /**
2673
     * Store the DATAVALIDATIONS and DATAVALIDATION records.
2674
     */
2675 55
    private function writeDataValidity(): void
2676
    {
2677
        // Datavalidation collection
2678 55
        $dataValidationCollection = $this->phpSheet->getDataValidationCollection();
2679
2680
        // Write data validations?
2681 55
        if (!empty($dataValidationCollection)) {
2682
            // DATAVALIDATIONS record
2683 2
            $record = 0x01B2; // Record identifier
2684 2
            $length = 0x0012; // Bytes to follow
2685
2686 2
            $grbit = 0x0000; // Prompt box at cell, no cached validity data at DV records
2687 2
            $horPos = 0x00000000; // Horizontal position of prompt box, if fixed position
2688 2
            $verPos = 0x00000000; // Vertical position of prompt box, if fixed position
2689 2
            $objId = 0xFFFFFFFF; // Object identifier of drop down arrow object, or -1 if not visible
2690
2691 2
            $header = pack('vv', $record, $length);
2692 2
            $data = pack('vVVVV', $grbit, $horPos, $verPos, $objId, count($dataValidationCollection));
2693 2
            $this->append($header . $data);
2694
2695
            // DATAVALIDATION records
2696 2
            $record = 0x01BE; // Record identifier
2697
2698 2
            foreach ($dataValidationCollection as $cellCoordinate => $dataValidation) {
2699
                // initialize record data
2700 2
                $data = '';
2701
2702
                // options
2703 2
                $options = 0x00000000;
2704
2705
                // data type
2706 2
                $type = 0x00;
2707 2
                switch ($dataValidation->getType()) {
2708
                    case DataValidation::TYPE_NONE:
2709
                        $type = 0x00;
2710
2711
                        break;
2712
                    case DataValidation::TYPE_WHOLE:
2713 1
                        $type = 0x01;
2714
2715 1
                        break;
2716
                    case DataValidation::TYPE_DECIMAL:
2717
                        $type = 0x02;
2718
2719
                        break;
2720
                    case DataValidation::TYPE_LIST:
2721 2
                        $type = 0x03;
2722
2723 2
                        break;
2724
                    case DataValidation::TYPE_DATE:
2725
                        $type = 0x04;
2726
2727
                        break;
2728
                    case DataValidation::TYPE_TIME:
2729
                        $type = 0x05;
2730
2731
                        break;
2732
                    case DataValidation::TYPE_TEXTLENGTH:
2733
                        $type = 0x06;
2734
2735
                        break;
2736
                    case DataValidation::TYPE_CUSTOM:
2737
                        $type = 0x07;
2738
2739
                        break;
2740
                }
2741
2742 2
                $options |= $type << 0;
2743
2744
                // error style
2745 2
                $errorStyle = 0x00;
2746 2
                switch ($dataValidation->getErrorStyle()) {
2747
                    case DataValidation::STYLE_STOP:
2748 1
                        $errorStyle = 0x00;
2749
2750 1
                        break;
2751
                    case DataValidation::STYLE_WARNING:
2752
                        $errorStyle = 0x01;
2753
2754
                        break;
2755
                    case DataValidation::STYLE_INFORMATION:
2756 2
                        $errorStyle = 0x02;
2757
2758 2
                        break;
2759
                }
2760
2761 2
                $options |= $errorStyle << 4;
2762
2763
                // explicit formula?
2764 2
                if ($type == 0x03 && preg_match('/^\".*\"$/', $dataValidation->getFormula1())) {
2765 1
                    $options |= 0x01 << 7;
2766
                }
2767
2768
                // empty cells allowed
2769 2
                $options |= $dataValidation->getAllowBlank() << 8;
2770
2771
                // show drop down
2772 2
                $options |= (!$dataValidation->getShowDropDown()) << 9;
2773
2774
                // show input message
2775 2
                $options |= $dataValidation->getShowInputMessage() << 18;
2776
2777
                // show error message
2778 2
                $options |= $dataValidation->getShowErrorMessage() << 19;
2779
2780
                // condition operator
2781 2
                $operator = 0x00;
2782 2
                switch ($dataValidation->getOperator()) {
2783
                    case DataValidation::OPERATOR_BETWEEN:
2784 2
                        $operator = 0x00;
2785
2786 2
                        break;
2787
                    case DataValidation::OPERATOR_NOTBETWEEN:
2788
                        $operator = 0x01;
2789
2790
                        break;
2791
                    case DataValidation::OPERATOR_EQUAL:
2792
                        $operator = 0x02;
2793
2794
                        break;
2795
                    case DataValidation::OPERATOR_NOTEQUAL:
2796
                        $operator = 0x03;
2797
2798
                        break;
2799
                    case DataValidation::OPERATOR_GREATERTHAN:
2800
                        $operator = 0x04;
2801
2802
                        break;
2803
                    case DataValidation::OPERATOR_LESSTHAN:
2804
                        $operator = 0x05;
2805
2806
                        break;
2807
                    case DataValidation::OPERATOR_GREATERTHANOREQUAL:
2808
                        $operator = 0x06;
2809
2810
                        break;
2811
                    case DataValidation::OPERATOR_LESSTHANOREQUAL:
2812
                        $operator = 0x07;
2813
2814
                        break;
2815
                }
2816
2817 2
                $options |= $operator << 20;
2818
2819 2
                $data = pack('V', $options);
2820
2821
                // prompt title
2822 2
                $promptTitle = $dataValidation->getPromptTitle() !== '' ?
2823 2
                    $dataValidation->getPromptTitle() : chr(0);
2824 2
                $data .= StringHelper::UTF8toBIFF8UnicodeLong($promptTitle);
2825
2826
                // error title
2827 2
                $errorTitle = $dataValidation->getErrorTitle() !== '' ?
2828 2
                    $dataValidation->getErrorTitle() : chr(0);
2829 2
                $data .= StringHelper::UTF8toBIFF8UnicodeLong($errorTitle);
2830
2831
                // prompt text
2832 2
                $prompt = $dataValidation->getPrompt() !== '' ?
2833 2
                    $dataValidation->getPrompt() : chr(0);
2834 2
                $data .= StringHelper::UTF8toBIFF8UnicodeLong($prompt);
2835
2836
                // error text
2837 2
                $error = $dataValidation->getError() !== '' ?
2838 2
                    $dataValidation->getError() : chr(0);
2839 2
                $data .= StringHelper::UTF8toBIFF8UnicodeLong($error);
2840
2841
                // formula 1
2842
                try {
2843 2
                    $formula1 = $dataValidation->getFormula1();
2844 2
                    if ($type == 0x03) { // list type
2845 2
                        $formula1 = str_replace(',', chr(0), $formula1);
2846
                    }
2847 2
                    $this->parser->parse($formula1);
2848 1
                    $formula1 = $this->parser->toReversePolish();
2849 1
                    $sz1 = strlen($formula1);
2850 1
                } catch (PhpSpreadsheetException $e) {
2851 1
                    $sz1 = 0;
2852 1
                    $formula1 = '';
2853
                }
2854 2
                $data .= pack('vv', $sz1, 0x0000);
2855 2
                $data .= $formula1;
2856
2857
                // formula 2
2858
                try {
2859 2
                    $formula2 = $dataValidation->getFormula2();
2860 2
                    if ($formula2 === '') {
2861 2
                        throw new WriterException('No formula2');
2862
                    }
2863 1
                    $this->parser->parse($formula2);
2864 1
                    $formula2 = $this->parser->toReversePolish();
2865 1
                    $sz2 = strlen($formula2);
2866 2
                } catch (PhpSpreadsheetException $e) {
2867 2
                    $sz2 = 0;
2868 2
                    $formula2 = '';
2869
                }
2870 2
                $data .= pack('vv', $sz2, 0x0000);
2871 2
                $data .= $formula2;
2872
2873
                // cell range address list
2874 2
                $data .= pack('v', 0x0001);
2875 2
                $data .= $this->writeBIFF8CellRangeAddressFixed($cellCoordinate);
2876
2877 2
                $length = strlen($data);
2878 2
                $header = pack('vv', $record, $length);
2879
2880 2
                $this->append($header . $data);
2881
            }
2882
        }
2883 55
    }
2884
2885
    /**
2886
     * Map Error code.
2887
     *
2888
     * @param string $errorCode
2889
     *
2890
     * @return int
2891
     */
2892 12
    private static function mapErrorCode($errorCode)
2893
    {
2894
        switch ($errorCode) {
2895 12
            case '#NULL!':
2896
                return 0x00;
2897 12
            case '#DIV/0!':
2898 3
                return 0x07;
2899 12
            case '#VALUE!':
2900 10
                return 0x0F;
2901 4
            case '#REF!':
2902
                return 0x17;
2903 4
            case '#NAME?':
2904 3
                return 0x1D;
2905 1
            case '#NUM!':
2906
                return 0x24;
2907 1
            case '#N/A':
2908 1
                return 0x2A;
2909
        }
2910
2911
        return 0;
2912
    }
2913
2914
    /**
2915
     * Write PLV Record.
2916
     */
2917 55
    private function writePageLayoutView(): void
2918
    {
2919 55
        $record = 0x088B; // Record identifier
2920 55
        $length = 0x0010; // Bytes to follow
2921
2922 55
        $rt = 0x088B; // 2
2923 55
        $grbitFrt = 0x0000; // 2
2924 55
        $reserved = 0x0000000000000000; // 8
2925 55
        $wScalvePLV = $this->phpSheet->getSheetView()->getZoomScale(); // 2
2926
2927
        // The options flags that comprise $grbit
2928 55
        if ($this->phpSheet->getSheetView()->getView() == SheetView::SHEETVIEW_PAGE_LAYOUT) {
2929 1
            $fPageLayoutView = 1;
2930
        } else {
2931 54
            $fPageLayoutView = 0;
2932
        }
2933 55
        $fRulerVisible = 0;
2934 55
        $fWhitespaceHidden = 0;
2935
2936 55
        $grbit = $fPageLayoutView; // 2
2937 55
        $grbit |= $fRulerVisible << 1;
2938 55
        $grbit |= $fWhitespaceHidden << 3;
2939
2940 55
        $header = pack('vv', $record, $length);
2941 55
        $data = pack('vvVVvv', $rt, $grbitFrt, 0x00000000, 0x00000000, $wScalvePLV, $grbit);
2942 55
        $this->append($header . $data);
2943 55
    }
2944
2945
    /**
2946
     * Write CFRule Record.
2947
     */
2948 2
    private function writeCFRule(Conditional $conditional): void
2949
    {
2950 2
        $record = 0x01B1; // Record identifier
2951 2
        $type = null;  //  Type of the CF
2952 2
        $operatorType = null;   //  Comparison operator
2953
2954 2
        if ($conditional->getConditionType() == Conditional::CONDITION_EXPRESSION) {
2955
            $type = 0x02;
2956
            $operatorType = 0x00;
2957 2
        } elseif ($conditional->getConditionType() == Conditional::CONDITION_CELLIS) {
2958 2
            $type = 0x01;
2959
2960 2
            switch ($conditional->getOperatorType()) {
2961
                case Conditional::OPERATOR_NONE:
2962
                    $operatorType = 0x00;
2963
2964
                    break;
2965
                case Conditional::OPERATOR_EQUAL:
2966
                    $operatorType = 0x03;
2967
2968
                    break;
2969
                case Conditional::OPERATOR_GREATERTHAN:
2970
                    $operatorType = 0x05;
2971
2972
                    break;
2973
                case Conditional::OPERATOR_GREATERTHANOREQUAL:
2974 2
                    $operatorType = 0x07;
2975
2976 2
                    break;
2977
                case Conditional::OPERATOR_LESSTHAN:
2978 2
                    $operatorType = 0x06;
2979
2980 2
                    break;
2981
                case Conditional::OPERATOR_LESSTHANOREQUAL:
2982
                    $operatorType = 0x08;
2983
2984
                    break;
2985
                case Conditional::OPERATOR_NOTEQUAL:
2986
                    $operatorType = 0x04;
2987
2988
                    break;
2989
                case Conditional::OPERATOR_BETWEEN:
2990 1
                    $operatorType = 0x01;
2991
2992 1
                    break;
2993
                // not OPERATOR_NOTBETWEEN 0x02
2994
            }
2995
        }
2996
2997
        // $szValue1 : size of the formula data for first value or formula
2998
        // $szValue2 : size of the formula data for second value or formula
2999 2
        $arrConditions = $conditional->getConditions();
3000 2
        $numConditions = count($arrConditions);
3001 2
        if ($numConditions == 1) {
3002 2
            $szValue1 = ($arrConditions[0] <= 65535 ? 3 : 0x0000);
3003 2
            $szValue2 = 0x0000;
3004 2
            $operand1 = pack('Cv', 0x1E, $arrConditions[0]);
3005 2
            $operand2 = null;
3006 1
        } elseif ($numConditions == 2 && ($conditional->getOperatorType() == Conditional::OPERATOR_BETWEEN)) {
3007 1
            $szValue1 = ($arrConditions[0] <= 65535 ? 3 : 0x0000);
3008 1
            $szValue2 = ($arrConditions[1] <= 65535 ? 3 : 0x0000);
3009 1
            $operand1 = pack('Cv', 0x1E, $arrConditions[0]);
3010 1
            $operand2 = pack('Cv', 0x1E, $arrConditions[1]);
3011
        } else {
3012
            $szValue1 = 0x0000;
3013
            $szValue2 = 0x0000;
3014
            $operand1 = null;
3015
            $operand2 = null;
3016
        }
3017
3018
        // $flags : Option flags
3019
        // Alignment
3020 2
        $bAlignHz = ($conditional->getStyle()->getAlignment()->getHorizontal() == null ? 1 : 0);
3021 2
        $bAlignVt = ($conditional->getStyle()->getAlignment()->getVertical() == null ? 1 : 0);
3022 2
        $bAlignWrapTx = ($conditional->getStyle()->getAlignment()->getWrapText() == false ? 1 : 0);
3023 2
        $bTxRotation = ($conditional->getStyle()->getAlignment()->getTextRotation() == null ? 1 : 0);
3024 2
        $bIndent = ($conditional->getStyle()->getAlignment()->getIndent() == 0 ? 1 : 0);
3025 2
        $bShrinkToFit = ($conditional->getStyle()->getAlignment()->getShrinkToFit() == false ? 1 : 0);
3026 2
        if ($bAlignHz == 0 || $bAlignVt == 0 || $bAlignWrapTx == 0 || $bTxRotation == 0 || $bIndent == 0 || $bShrinkToFit == 0) {
3027
            $bFormatAlign = 1;
3028
        } else {
3029 2
            $bFormatAlign = 0;
3030
        }
3031
        // Protection
3032 2
        $bProtLocked = ($conditional->getStyle()->getProtection()->getLocked() == null ? 1 : 0);
3033 2
        $bProtHidden = ($conditional->getStyle()->getProtection()->getHidden() == null ? 1 : 0);
3034 2
        if ($bProtLocked == 0 || $bProtHidden == 0) {
3035
            $bFormatProt = 1;
3036
        } else {
3037 2
            $bFormatProt = 0;
3038
        }
3039
        // Border
3040 2
        $bBorderLeft = ($conditional->getStyle()->getBorders()->getLeft()->getColor()->getARGB() == Color::COLOR_BLACK
3041 2
        && $conditional->getStyle()->getBorders()->getLeft()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0);
3042 2
        $bBorderRight = ($conditional->getStyle()->getBorders()->getRight()->getColor()->getARGB() == Color::COLOR_BLACK
3043 2
        && $conditional->getStyle()->getBorders()->getRight()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0);
3044 2
        $bBorderTop = ($conditional->getStyle()->getBorders()->getTop()->getColor()->getARGB() == Color::COLOR_BLACK
3045 2
        && $conditional->getStyle()->getBorders()->getTop()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0);
3046 2
        $bBorderBottom = ($conditional->getStyle()->getBorders()->getBottom()->getColor()->getARGB() == Color::COLOR_BLACK
3047 2
        && $conditional->getStyle()->getBorders()->getBottom()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0);
3048 2
        if ($bBorderLeft == 0 || $bBorderRight == 0 || $bBorderTop == 0 || $bBorderBottom == 0) {
3049
            $bFormatBorder = 1;
3050
        } else {
3051 2
            $bFormatBorder = 0;
3052
        }
3053
        // Pattern
3054 2
        $bFillStyle = ($conditional->getStyle()->getFill()->getFillType() == null ? 0 : 1);
3055 2
        $bFillColor = ($conditional->getStyle()->getFill()->getStartColor()->getARGB() == null ? 0 : 1);
3056 2
        $bFillColorBg = ($conditional->getStyle()->getFill()->getEndColor()->getARGB() == null ? 0 : 1);
3057 2
        if ($bFillStyle == 0 || $bFillColor == 0 || $bFillColorBg == 0) {
3058 2
            $bFormatFill = 1;
3059
        } else {
3060
            $bFormatFill = 0;
3061
        }
3062
        // Font
3063
        if (
3064 2
            $conditional->getStyle()->getFont()->getName() != null
3065 2
            || $conditional->getStyle()->getFont()->getSize() != null
3066 2
            || $conditional->getStyle()->getFont()->getBold() != null
3067 2
            || $conditional->getStyle()->getFont()->getItalic() != null
3068 2
            || $conditional->getStyle()->getFont()->getSuperscript() != null
3069 2
            || $conditional->getStyle()->getFont()->getSubscript() != null
3070 2
            || $conditional->getStyle()->getFont()->getUnderline() != null
3071 2
            || $conditional->getStyle()->getFont()->getStrikethrough() != null
3072 2
            || $conditional->getStyle()->getFont()->getColor()->getARGB() != null
3073
        ) {
3074 2
            $bFormatFont = 1;
3075
        } else {
3076
            $bFormatFont = 0;
3077
        }
3078
        // Alignment
3079 2
        $flags = 0;
3080 2
        $flags |= (1 == $bAlignHz ? 0x00000001 : 0);
3081 2
        $flags |= (1 == $bAlignVt ? 0x00000002 : 0);
3082 2
        $flags |= (1 == $bAlignWrapTx ? 0x00000004 : 0);
3083 2
        $flags |= (1 == $bTxRotation ? 0x00000008 : 0);
3084
        // Justify last line flag
3085 2
        $flags |= (1 == 1 ? 0x00000010 : 0);
3086 2
        $flags |= (1 == $bIndent ? 0x00000020 : 0);
3087 2
        $flags |= (1 == $bShrinkToFit ? 0x00000040 : 0);
3088
        // Default
3089 2
        $flags |= (1 == 1 ? 0x00000080 : 0);
3090
        // Protection
3091 2
        $flags |= (1 == $bProtLocked ? 0x00000100 : 0);
3092 2
        $flags |= (1 == $bProtHidden ? 0x00000200 : 0);
3093
        // Border
3094 2
        $flags |= (1 == $bBorderLeft ? 0x00000400 : 0);
3095 2
        $flags |= (1 == $bBorderRight ? 0x00000800 : 0);
3096 2
        $flags |= (1 == $bBorderTop ? 0x00001000 : 0);
3097 2
        $flags |= (1 == $bBorderBottom ? 0x00002000 : 0);
3098 2
        $flags |= (1 == 1 ? 0x00004000 : 0); // Top left to Bottom right border
3099 2
        $flags |= (1 == 1 ? 0x00008000 : 0); // Bottom left to Top right border
3100
        // Pattern
3101 2
        $flags |= (1 == $bFillStyle ? 0x00010000 : 0);
3102 2
        $flags |= (1 == $bFillColor ? 0x00020000 : 0);
3103 2
        $flags |= (1 == $bFillColorBg ? 0x00040000 : 0);
3104 2
        $flags |= (1 == 1 ? 0x00380000 : 0);
3105
        // Font
3106 2
        $flags |= (1 == $bFormatFont ? 0x04000000 : 0);
3107
        // Alignment:
3108 2
        $flags |= (1 == $bFormatAlign ? 0x08000000 : 0);
3109
        // Border
3110 2
        $flags |= (1 == $bFormatBorder ? 0x10000000 : 0);
3111
        // Pattern
3112 2
        $flags |= (1 == $bFormatFill ? 0x20000000 : 0);
3113
        // Protection
3114 2
        $flags |= (1 == $bFormatProt ? 0x40000000 : 0);
3115
        // Text direction
3116 2
        $flags |= (1 == 0 ? 0x80000000 : 0);
3117
3118 2
        $dataBlockFont = null;
3119 2
        $dataBlockAlign = null;
3120 2
        $dataBlockBorder = null;
3121 2
        $dataBlockFill = null;
3122
3123
        // Data Blocks
3124 2
        if ($bFormatFont == 1) {
3125
            // Font Name
3126 2
            if ($conditional->getStyle()->getFont()->getName() == null) {
3127 2
                $dataBlockFont = pack('VVVVVVVV', 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000);
3128 2
                $dataBlockFont .= pack('VVVVVVVV', 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000);
3129
            } else {
3130
                $dataBlockFont = StringHelper::UTF8toBIFF8UnicodeLong($conditional->getStyle()->getFont()->getName());
3131
            }
3132
            // Font Size
3133 2
            if ($conditional->getStyle()->getFont()->getSize() == null) {
3134 2
                $dataBlockFont .= pack('V', 20 * 11);
3135
            } else {
3136
                $dataBlockFont .= pack('V', 20 * $conditional->getStyle()->getFont()->getSize());
3137
            }
3138
            // Font Options
3139 2
            $dataBlockFont .= pack('V', 0);
3140
            // Font weight
3141 2
            if ($conditional->getStyle()->getFont()->getBold() == true) {
3142 1
                $dataBlockFont .= pack('v', 0x02BC);
3143
            } else {
3144 2
                $dataBlockFont .= pack('v', 0x0190);
3145
            }
3146
            // Escapement type
3147 2
            if ($conditional->getStyle()->getFont()->getSubscript() == true) {
3148
                $dataBlockFont .= pack('v', 0x02);
3149
                $fontEscapement = 0;
3150 2
            } elseif ($conditional->getStyle()->getFont()->getSuperscript() == true) {
3151
                $dataBlockFont .= pack('v', 0x01);
3152
                $fontEscapement = 0;
3153
            } else {
3154 2
                $dataBlockFont .= pack('v', 0x00);
3155 2
                $fontEscapement = 1;
3156
            }
3157
            // Underline type
3158 2
            switch ($conditional->getStyle()->getFont()->getUnderline()) {
3159
                case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_NONE:
3160
                    $dataBlockFont .= pack('C', 0x00);
3161
                    $fontUnderline = 0;
3162
3163
                    break;
3164
                case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLE:
3165
                    $dataBlockFont .= pack('C', 0x02);
3166
                    $fontUnderline = 0;
3167
3168
                    break;
3169
                case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLEACCOUNTING:
3170
                    $dataBlockFont .= pack('C', 0x22);
3171
                    $fontUnderline = 0;
3172
3173
                    break;
3174
                case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE:
3175
                    $dataBlockFont .= pack('C', 0x01);
3176
                    $fontUnderline = 0;
3177
3178
                    break;
3179
                case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLEACCOUNTING:
3180
                    $dataBlockFont .= pack('C', 0x21);
3181
                    $fontUnderline = 0;
3182
3183
                    break;
3184
                default:
3185 2
                    $dataBlockFont .= pack('C', 0x00);
3186 2
                    $fontUnderline = 1;
3187
3188 2
                    break;
3189
            }
3190
            // Not used (3)
3191 2
            $dataBlockFont .= pack('vC', 0x0000, 0x00);
3192
            // Font color index
3193 2
            switch ($conditional->getStyle()->getFont()->getColor()->getRGB()) {
3194 2
                case '000000':
3195
                    $colorIdx = 0x08;
3196
3197
                    break;
3198 2
                case 'FFFFFF':
3199
                    $colorIdx = 0x09;
3200
3201
                    break;
3202 2
                case 'FF0000':
3203 2
                    $colorIdx = 0x0A;
3204
3205 2
                    break;
3206 2
                case '00FF00':
3207 2
                    $colorIdx = 0x0B;
3208
3209 2
                    break;
3210 1
                case '0000FF':
3211
                    $colorIdx = 0x0C;
3212
3213
                    break;
3214 1
                case 'FFFF00':
3215 1
                    $colorIdx = 0x0D;
3216
3217 1
                    break;
3218
                case 'FF00FF':
3219
                    $colorIdx = 0x0E;
3220
3221
                    break;
3222
                case '00FFFF':
3223
                    $colorIdx = 0x0F;
3224
3225
                    break;
3226
                case '800000':
3227
                    $colorIdx = 0x10;
3228
3229
                    break;
3230
                case '008000':
3231
                    $colorIdx = 0x11;
3232
3233
                    break;
3234
                case '000080':
3235
                    $colorIdx = 0x12;
3236
3237
                    break;
3238
                case '808000':
3239
                    $colorIdx = 0x13;
3240
3241
                    break;
3242
                case '800080':
3243
                    $colorIdx = 0x14;
3244
3245
                    break;
3246
                case '008080':
3247
                    $colorIdx = 0x15;
3248
3249
                    break;
3250
                case 'C0C0C0':
3251
                    $colorIdx = 0x16;
3252
3253
                    break;
3254
                case '808080':
3255
                    $colorIdx = 0x17;
3256
3257
                    break;
3258
                case '9999FF':
3259
                    $colorIdx = 0x18;
3260
3261
                    break;
3262
                case '993366':
3263
                    $colorIdx = 0x19;
3264
3265
                    break;
3266
                case 'FFFFCC':
3267
                    $colorIdx = 0x1A;
3268
3269
                    break;
3270
                case 'CCFFFF':
3271
                    $colorIdx = 0x1B;
3272
3273
                    break;
3274
                case '660066':
3275
                    $colorIdx = 0x1C;
3276
3277
                    break;
3278
                case 'FF8080':
3279
                    $colorIdx = 0x1D;
3280
3281
                    break;
3282
                case '0066CC':
3283
                    $colorIdx = 0x1E;
3284
3285
                    break;
3286
                case 'CCCCFF':
3287
                    $colorIdx = 0x1F;
3288
3289
                    break;
3290
                case '000080':
3291
                    $colorIdx = 0x20;
3292
3293
                    break;
3294
                case 'FF00FF':
3295
                    $colorIdx = 0x21;
3296
3297
                    break;
3298
                case 'FFFF00':
3299
                    $colorIdx = 0x22;
3300
3301
                    break;
3302
                case '00FFFF':
3303
                    $colorIdx = 0x23;
3304
3305
                    break;
3306
                case '800080':
3307
                    $colorIdx = 0x24;
3308
3309
                    break;
3310
                case '800000':
3311
                    $colorIdx = 0x25;
3312
3313
                    break;
3314
                case '008080':
3315
                    $colorIdx = 0x26;
3316
3317
                    break;
3318
                case '0000FF':
3319
                    $colorIdx = 0x27;
3320
3321
                    break;
3322
                case '00CCFF':
3323
                    $colorIdx = 0x28;
3324
3325
                    break;
3326
                case 'CCFFFF':
3327
                    $colorIdx = 0x29;
3328
3329
                    break;
3330
                case 'CCFFCC':
3331
                    $colorIdx = 0x2A;
3332
3333
                    break;
3334
                case 'FFFF99':
3335
                    $colorIdx = 0x2B;
3336
3337
                    break;
3338
                case '99CCFF':
3339
                    $colorIdx = 0x2C;
3340
3341
                    break;
3342
                case 'FF99CC':
3343
                    $colorIdx = 0x2D;
3344
3345
                    break;
3346
                case 'CC99FF':
3347
                    $colorIdx = 0x2E;
3348
3349
                    break;
3350
                case 'FFCC99':
3351
                    $colorIdx = 0x2F;
3352
3353
                    break;
3354
                case '3366FF':
3355
                    $colorIdx = 0x30;
3356
3357
                    break;
3358
                case '33CCCC':
3359
                    $colorIdx = 0x31;
3360
3361
                    break;
3362
                case '99CC00':
3363
                    $colorIdx = 0x32;
3364
3365
                    break;
3366
                case 'FFCC00':
3367
                    $colorIdx = 0x33;
3368
3369
                    break;
3370
                case 'FF9900':
3371
                    $colorIdx = 0x34;
3372
3373
                    break;
3374
                case 'FF6600':
3375
                    $colorIdx = 0x35;
3376
3377
                    break;
3378
                case '666699':
3379
                    $colorIdx = 0x36;
3380
3381
                    break;
3382
                case '969696':
3383
                    $colorIdx = 0x37;
3384
3385
                    break;
3386
                case '003366':
3387
                    $colorIdx = 0x38;
3388
3389
                    break;
3390
                case '339966':
3391
                    $colorIdx = 0x39;
3392
3393
                    break;
3394
                case '003300':
3395
                    $colorIdx = 0x3A;
3396
3397
                    break;
3398
                case '333300':
3399
                    $colorIdx = 0x3B;
3400
3401
                    break;
3402
                case '993300':
3403
                    $colorIdx = 0x3C;
3404
3405
                    break;
3406
                case '993366':
3407
                    $colorIdx = 0x3D;
3408
3409
                    break;
3410
                case '333399':
3411
                    $colorIdx = 0x3E;
3412
3413
                    break;
3414
                case '333333':
3415
                    $colorIdx = 0x3F;
3416
3417
                    break;
3418
                default:
3419
                    $colorIdx = 0x00;
3420
3421
                    break;
3422
            }
3423 2
            $dataBlockFont .= pack('V', $colorIdx);
3424
            // Not used (4)
3425 2
            $dataBlockFont .= pack('V', 0x00000000);
3426
            // Options flags for modified font attributes
3427 2
            $optionsFlags = 0;
3428 2
            $optionsFlagsBold = ($conditional->getStyle()->getFont()->getBold() == null ? 1 : 0);
3429 2
            $optionsFlags |= (1 == $optionsFlagsBold ? 0x00000002 : 0);
3430 2
            $optionsFlags |= (1 == 1 ? 0x00000008 : 0);
3431 2
            $optionsFlags |= (1 == 1 ? 0x00000010 : 0);
3432 2
            $optionsFlags |= (1 == 0 ? 0x00000020 : 0);
3433 2
            $optionsFlags |= (1 == 1 ? 0x00000080 : 0);
3434 2
            $dataBlockFont .= pack('V', $optionsFlags);
3435
            // Escapement type
3436 2
            $dataBlockFont .= pack('V', $fontEscapement);
3437
            // Underline type
3438 2
            $dataBlockFont .= pack('V', $fontUnderline);
3439
            // Always
3440 2
            $dataBlockFont .= pack('V', 0x00000000);
3441
            // Always
3442 2
            $dataBlockFont .= pack('V', 0x00000000);
3443
            // Not used (8)
3444 2
            $dataBlockFont .= pack('VV', 0x00000000, 0x00000000);
3445
            // Always
3446 2
            $dataBlockFont .= pack('v', 0x0001);
3447
        }
3448 2
        if ($bFormatAlign == 1) {
3449
            $blockAlign = 0;
3450
            // Alignment and text break
3451
            switch ($conditional->getStyle()->getAlignment()->getHorizontal()) {
3452
                case Alignment::HORIZONTAL_GENERAL:
3453
                    $blockAlign = 0;
3454
3455
                    break;
3456
                case Alignment::HORIZONTAL_LEFT:
3457
                    $blockAlign = 1;
3458
3459
                    break;
3460
                case Alignment::HORIZONTAL_RIGHT:
3461
                    $blockAlign = 3;
3462
3463
                    break;
3464
                case Alignment::HORIZONTAL_CENTER:
3465
                    $blockAlign = 2;
3466
3467
                    break;
3468
                case Alignment::HORIZONTAL_CENTER_CONTINUOUS:
3469
                    $blockAlign = 6;
3470
3471
                    break;
3472
                case Alignment::HORIZONTAL_JUSTIFY:
3473
                    $blockAlign = 5;
3474
3475
                    break;
3476
            }
3477
            if ($conditional->getStyle()->getAlignment()->getWrapText() == true) {
3478
                $blockAlign |= 1 << 3;
3479
            } else {
3480
                $blockAlign |= 0 << 3;
3481
            }
3482
            switch ($conditional->getStyle()->getAlignment()->getVertical()) {
3483
                case Alignment::VERTICAL_BOTTOM:
3484
                    $blockAlign = 2 << 4;
3485
3486
                    break;
3487
                case Alignment::VERTICAL_TOP:
3488
                    $blockAlign = 0 << 4;
3489
3490
                    break;
3491
                case Alignment::VERTICAL_CENTER:
3492
                    $blockAlign = 1 << 4;
3493
3494
                    break;
3495
                case Alignment::VERTICAL_JUSTIFY:
3496
                    $blockAlign = 3 << 4;
3497
3498
                    break;
3499
            }
3500
            $blockAlign |= 0 << 7;
3501
3502
            // Text rotation angle
3503
            $blockRotation = $conditional->getStyle()->getAlignment()->getTextRotation();
3504
3505
            // Indentation
3506
            $blockIndent = $conditional->getStyle()->getAlignment()->getIndent();
3507
            if ($conditional->getStyle()->getAlignment()->getShrinkToFit() == true) {
3508
                $blockIndent |= 1 << 4;
3509
            } else {
3510
                $blockIndent |= 0 << 4;
3511
            }
3512
            $blockIndent |= 0 << 6;
3513
3514
            // Relative indentation
3515
            $blockIndentRelative = 255;
3516
3517
            $dataBlockAlign = pack('CCvvv', $blockAlign, $blockRotation, $blockIndent, $blockIndentRelative, 0x0000);
3518
        }
3519 2
        if ($bFormatBorder == 1) {
3520
            $blockLineStyle = 0;
3521
            switch ($conditional->getStyle()->getBorders()->getLeft()->getBorderStyle()) {
3522
                case Border::BORDER_NONE:
3523
                    $blockLineStyle |= 0x00;
3524
3525
                    break;
3526
                case Border::BORDER_THIN:
3527
                    $blockLineStyle |= 0x01;
3528
3529
                    break;
3530
                case Border::BORDER_MEDIUM:
3531
                    $blockLineStyle |= 0x02;
3532
3533
                    break;
3534
                case Border::BORDER_DASHED:
3535
                    $blockLineStyle |= 0x03;
3536
3537
                    break;
3538
                case Border::BORDER_DOTTED:
3539
                    $blockLineStyle |= 0x04;
3540
3541
                    break;
3542
                case Border::BORDER_THICK:
3543
                    $blockLineStyle |= 0x05;
3544
3545
                    break;
3546
                case Border::BORDER_DOUBLE:
3547
                    $blockLineStyle |= 0x06;
3548
3549
                    break;
3550
                case Border::BORDER_HAIR:
3551
                    $blockLineStyle |= 0x07;
3552
3553
                    break;
3554
                case Border::BORDER_MEDIUMDASHED:
3555
                    $blockLineStyle |= 0x08;
3556
3557
                    break;
3558
                case Border::BORDER_DASHDOT:
3559
                    $blockLineStyle |= 0x09;
3560
3561
                    break;
3562
                case Border::BORDER_MEDIUMDASHDOT:
3563
                    $blockLineStyle |= 0x0A;
3564
3565
                    break;
3566
                case Border::BORDER_DASHDOTDOT:
3567
                    $blockLineStyle |= 0x0B;
3568
3569
                    break;
3570
                case Border::BORDER_MEDIUMDASHDOTDOT:
3571
                    $blockLineStyle |= 0x0C;
3572
3573
                    break;
3574
                case Border::BORDER_SLANTDASHDOT:
3575
                    $blockLineStyle |= 0x0D;
3576
3577
                    break;
3578
            }
3579
            switch ($conditional->getStyle()->getBorders()->getRight()->getBorderStyle()) {
3580
                case Border::BORDER_NONE:
3581
                    $blockLineStyle |= 0x00 << 4;
3582
3583
                    break;
3584
                case Border::BORDER_THIN:
3585
                    $blockLineStyle |= 0x01 << 4;
3586
3587
                    break;
3588
                case Border::BORDER_MEDIUM:
3589
                    $blockLineStyle |= 0x02 << 4;
3590
3591
                    break;
3592
                case Border::BORDER_DASHED:
3593
                    $blockLineStyle |= 0x03 << 4;
3594
3595
                    break;
3596
                case Border::BORDER_DOTTED:
3597
                    $blockLineStyle |= 0x04 << 4;
3598
3599
                    break;
3600
                case Border::BORDER_THICK:
3601
                    $blockLineStyle |= 0x05 << 4;
3602
3603
                    break;
3604
                case Border::BORDER_DOUBLE:
3605
                    $blockLineStyle |= 0x06 << 4;
3606
3607
                    break;
3608
                case Border::BORDER_HAIR:
3609
                    $blockLineStyle |= 0x07 << 4;
3610
3611
                    break;
3612
                case Border::BORDER_MEDIUMDASHED:
3613
                    $blockLineStyle |= 0x08 << 4;
3614
3615
                    break;
3616
                case Border::BORDER_DASHDOT:
3617
                    $blockLineStyle |= 0x09 << 4;
3618
3619
                    break;
3620
                case Border::BORDER_MEDIUMDASHDOT:
3621
                    $blockLineStyle |= 0x0A << 4;
3622
3623
                    break;
3624
                case Border::BORDER_DASHDOTDOT:
3625
                    $blockLineStyle |= 0x0B << 4;
3626
3627
                    break;
3628
                case Border::BORDER_MEDIUMDASHDOTDOT:
3629
                    $blockLineStyle |= 0x0C << 4;
3630
3631
                    break;
3632
                case Border::BORDER_SLANTDASHDOT:
3633
                    $blockLineStyle |= 0x0D << 4;
3634
3635
                    break;
3636
            }
3637
            switch ($conditional->getStyle()->getBorders()->getTop()->getBorderStyle()) {
3638
                case Border::BORDER_NONE:
3639
                    $blockLineStyle |= 0x00 << 8;
3640
3641
                    break;
3642
                case Border::BORDER_THIN:
3643
                    $blockLineStyle |= 0x01 << 8;
3644
3645
                    break;
3646
                case Border::BORDER_MEDIUM:
3647
                    $blockLineStyle |= 0x02 << 8;
3648
3649
                    break;
3650
                case Border::BORDER_DASHED:
3651
                    $blockLineStyle |= 0x03 << 8;
3652
3653
                    break;
3654
                case Border::BORDER_DOTTED:
3655
                    $blockLineStyle |= 0x04 << 8;
3656
3657
                    break;
3658
                case Border::BORDER_THICK:
3659
                    $blockLineStyle |= 0x05 << 8;
3660
3661
                    break;
3662
                case Border::BORDER_DOUBLE:
3663
                    $blockLineStyle |= 0x06 << 8;
3664
3665
                    break;
3666
                case Border::BORDER_HAIR:
3667
                    $blockLineStyle |= 0x07 << 8;
3668
3669
                    break;
3670
                case Border::BORDER_MEDIUMDASHED:
3671
                    $blockLineStyle |= 0x08 << 8;
3672
3673
                    break;
3674
                case Border::BORDER_DASHDOT:
3675
                    $blockLineStyle |= 0x09 << 8;
3676
3677
                    break;
3678
                case Border::BORDER_MEDIUMDASHDOT:
3679
                    $blockLineStyle |= 0x0A << 8;
3680
3681
                    break;
3682
                case Border::BORDER_DASHDOTDOT:
3683
                    $blockLineStyle |= 0x0B << 8;
3684
3685
                    break;
3686
                case Border::BORDER_MEDIUMDASHDOTDOT:
3687
                    $blockLineStyle |= 0x0C << 8;
3688
3689
                    break;
3690
                case Border::BORDER_SLANTDASHDOT:
3691
                    $blockLineStyle |= 0x0D << 8;
3692
3693
                    break;
3694
            }
3695
            switch ($conditional->getStyle()->getBorders()->getBottom()->getBorderStyle()) {
3696
                case Border::BORDER_NONE:
3697
                    $blockLineStyle |= 0x00 << 12;
3698
3699
                    break;
3700
                case Border::BORDER_THIN:
3701
                    $blockLineStyle |= 0x01 << 12;
3702
3703
                    break;
3704
                case Border::BORDER_MEDIUM:
3705
                    $blockLineStyle |= 0x02 << 12;
3706
3707
                    break;
3708
                case Border::BORDER_DASHED:
3709
                    $blockLineStyle |= 0x03 << 12;
3710
3711
                    break;
3712
                case Border::BORDER_DOTTED:
3713
                    $blockLineStyle |= 0x04 << 12;
3714
3715
                    break;
3716
                case Border::BORDER_THICK:
3717
                    $blockLineStyle |= 0x05 << 12;
3718
3719
                    break;
3720
                case Border::BORDER_DOUBLE:
3721
                    $blockLineStyle |= 0x06 << 12;
3722
3723
                    break;
3724
                case Border::BORDER_HAIR:
3725
                    $blockLineStyle |= 0x07 << 12;
3726
3727
                    break;
3728
                case Border::BORDER_MEDIUMDASHED:
3729
                    $blockLineStyle |= 0x08 << 12;
3730
3731
                    break;
3732
                case Border::BORDER_DASHDOT:
3733
                    $blockLineStyle |= 0x09 << 12;
3734
3735
                    break;
3736
                case Border::BORDER_MEDIUMDASHDOT:
3737
                    $blockLineStyle |= 0x0A << 12;
3738
3739
                    break;
3740
                case Border::BORDER_DASHDOTDOT:
3741
                    $blockLineStyle |= 0x0B << 12;
3742
3743
                    break;
3744
                case Border::BORDER_MEDIUMDASHDOTDOT:
3745
                    $blockLineStyle |= 0x0C << 12;
3746
3747
                    break;
3748
                case Border::BORDER_SLANTDASHDOT:
3749
                    $blockLineStyle |= 0x0D << 12;
3750
3751
                    break;
3752
            }
3753
3754
            // TODO writeCFRule() => $blockLineStyle => Index Color for left line
3755
            // TODO writeCFRule() => $blockLineStyle => Index Color for right line
3756
            // TODO writeCFRule() => $blockLineStyle => Top-left to bottom-right on/off
3757
            // TODO writeCFRule() => $blockLineStyle => Bottom-left to top-right on/off
3758
            $blockColor = 0;
3759
            // TODO writeCFRule() => $blockColor => Index Color for top line
3760
            // TODO writeCFRule() => $blockColor => Index Color for bottom line
3761
            // TODO writeCFRule() => $blockColor => Index Color for diagonal line
3762
            switch ($conditional->getStyle()->getBorders()->getDiagonal()->getBorderStyle()) {
3763
                case Border::BORDER_NONE:
3764
                    $blockColor |= 0x00 << 21;
3765
3766
                    break;
3767
                case Border::BORDER_THIN:
3768
                    $blockColor |= 0x01 << 21;
3769
3770
                    break;
3771
                case Border::BORDER_MEDIUM:
3772
                    $blockColor |= 0x02 << 21;
3773
3774
                    break;
3775
                case Border::BORDER_DASHED:
3776
                    $blockColor |= 0x03 << 21;
3777
3778
                    break;
3779
                case Border::BORDER_DOTTED:
3780
                    $blockColor |= 0x04 << 21;
3781
3782
                    break;
3783
                case Border::BORDER_THICK:
3784
                    $blockColor |= 0x05 << 21;
3785
3786
                    break;
3787
                case Border::BORDER_DOUBLE:
3788
                    $blockColor |= 0x06 << 21;
3789
3790
                    break;
3791
                case Border::BORDER_HAIR:
3792
                    $blockColor |= 0x07 << 21;
3793
3794
                    break;
3795
                case Border::BORDER_MEDIUMDASHED:
3796
                    $blockColor |= 0x08 << 21;
3797
3798
                    break;
3799
                case Border::BORDER_DASHDOT:
3800
                    $blockColor |= 0x09 << 21;
3801
3802
                    break;
3803
                case Border::BORDER_MEDIUMDASHDOT:
3804
                    $blockColor |= 0x0A << 21;
3805
3806
                    break;
3807
                case Border::BORDER_DASHDOTDOT:
3808
                    $blockColor |= 0x0B << 21;
3809
3810
                    break;
3811
                case Border::BORDER_MEDIUMDASHDOTDOT:
3812
                    $blockColor |= 0x0C << 21;
3813
3814
                    break;
3815
                case Border::BORDER_SLANTDASHDOT:
3816
                    $blockColor |= 0x0D << 21;
3817
3818
                    break;
3819
            }
3820
            $dataBlockBorder = pack('vv', $blockLineStyle, $blockColor);
3821
        }
3822 2
        if ($bFormatFill == 1) {
3823
            // Fill Patern Style
3824 2
            $blockFillPatternStyle = 0;
3825 2
            switch ($conditional->getStyle()->getFill()->getFillType()) {
3826
                case Fill::FILL_NONE:
3827
                    $blockFillPatternStyle = 0x00;
3828
3829
                    break;
3830
                case Fill::FILL_SOLID:
3831
                    $blockFillPatternStyle = 0x01;
3832
3833
                    break;
3834
                case Fill::FILL_PATTERN_MEDIUMGRAY:
3835
                    $blockFillPatternStyle = 0x02;
3836
3837
                    break;
3838
                case Fill::FILL_PATTERN_DARKGRAY:
3839
                    $blockFillPatternStyle = 0x03;
3840
3841
                    break;
3842
                case Fill::FILL_PATTERN_LIGHTGRAY:
3843
                    $blockFillPatternStyle = 0x04;
3844
3845
                    break;
3846
                case Fill::FILL_PATTERN_DARKHORIZONTAL:
3847
                    $blockFillPatternStyle = 0x05;
3848
3849
                    break;
3850
                case Fill::FILL_PATTERN_DARKVERTICAL:
3851
                    $blockFillPatternStyle = 0x06;
3852
3853
                    break;
3854
                case Fill::FILL_PATTERN_DARKDOWN:
3855
                    $blockFillPatternStyle = 0x07;
3856
3857
                    break;
3858
                case Fill::FILL_PATTERN_DARKUP:
3859
                    $blockFillPatternStyle = 0x08;
3860
3861
                    break;
3862
                case Fill::FILL_PATTERN_DARKGRID:
3863
                    $blockFillPatternStyle = 0x09;
3864
3865
                    break;
3866
                case Fill::FILL_PATTERN_DARKTRELLIS:
3867
                    $blockFillPatternStyle = 0x0A;
3868
3869
                    break;
3870
                case Fill::FILL_PATTERN_LIGHTHORIZONTAL:
3871
                    $blockFillPatternStyle = 0x0B;
3872
3873
                    break;
3874
                case Fill::FILL_PATTERN_LIGHTVERTICAL:
3875
                    $blockFillPatternStyle = 0x0C;
3876
3877
                    break;
3878
                case Fill::FILL_PATTERN_LIGHTDOWN:
3879
                    $blockFillPatternStyle = 0x0D;
3880
3881
                    break;
3882
                case Fill::FILL_PATTERN_LIGHTUP:
3883
                    $blockFillPatternStyle = 0x0E;
3884
3885
                    break;
3886
                case Fill::FILL_PATTERN_LIGHTGRID:
3887
                    $blockFillPatternStyle = 0x0F;
3888
3889
                    break;
3890
                case Fill::FILL_PATTERN_LIGHTTRELLIS:
3891
                    $blockFillPatternStyle = 0x10;
3892
3893
                    break;
3894
                case Fill::FILL_PATTERN_GRAY125:
3895
                    $blockFillPatternStyle = 0x11;
3896
3897
                    break;
3898
                case Fill::FILL_PATTERN_GRAY0625:
3899
                    $blockFillPatternStyle = 0x12;
3900
3901
                    break;
3902
                case Fill::FILL_GRADIENT_LINEAR:
3903
                    $blockFillPatternStyle = 0x00;
3904
3905
                    break; // does not exist in BIFF8
3906
                case Fill::FILL_GRADIENT_PATH:
3907
                    $blockFillPatternStyle = 0x00;
3908
3909
                    break; // does not exist in BIFF8
3910
                default:
3911 2
                    $blockFillPatternStyle = 0x00;
3912
3913 2
                    break;
3914
            }
3915
            // Color
3916 2
            switch ($conditional->getStyle()->getFill()->getStartColor()->getRGB()) {
3917 2
                case '000000':
3918
                    $colorIdxBg = 0x08;
3919
3920
                    break;
3921 2
                case 'FFFFFF':
3922
                    $colorIdxBg = 0x09;
3923
3924
                    break;
3925 2
                case 'FF0000':
3926
                    $colorIdxBg = 0x0A;
3927
3928
                    break;
3929 2
                case '00FF00':
3930
                    $colorIdxBg = 0x0B;
3931
3932
                    break;
3933 2
                case '0000FF':
3934
                    $colorIdxBg = 0x0C;
3935
3936
                    break;
3937 2
                case 'FFFF00':
3938
                    $colorIdxBg = 0x0D;
3939
3940
                    break;
3941 2
                case 'FF00FF':
3942
                    $colorIdxBg = 0x0E;
3943
3944
                    break;
3945 2
                case '00FFFF':
3946
                    $colorIdxBg = 0x0F;
3947
3948
                    break;
3949 2
                case '800000':
3950
                    $colorIdxBg = 0x10;
3951
3952
                    break;
3953 2
                case '008000':
3954
                    $colorIdxBg = 0x11;
3955
3956
                    break;
3957 2
                case '000080':
3958
                    $colorIdxBg = 0x12;
3959
3960
                    break;
3961 2
                case '808000':
3962
                    $colorIdxBg = 0x13;
3963
3964
                    break;
3965 2
                case '800080':
3966
                    $colorIdxBg = 0x14;
3967
3968
                    break;
3969 2
                case '008080':
3970
                    $colorIdxBg = 0x15;
3971
3972
                    break;
3973 2
                case 'C0C0C0':
3974
                    $colorIdxBg = 0x16;
3975
3976
                    break;
3977 2
                case '808080':
3978
                    $colorIdxBg = 0x17;
3979
3980
                    break;
3981 2
                case '9999FF':
3982
                    $colorIdxBg = 0x18;
3983
3984
                    break;
3985 2
                case '993366':
3986
                    $colorIdxBg = 0x19;
3987
3988
                    break;
3989 2
                case 'FFFFCC':
3990
                    $colorIdxBg = 0x1A;
3991
3992
                    break;
3993 2
                case 'CCFFFF':
3994
                    $colorIdxBg = 0x1B;
3995
3996
                    break;
3997 2
                case '660066':
3998
                    $colorIdxBg = 0x1C;
3999
4000
                    break;
4001 2
                case 'FF8080':
4002
                    $colorIdxBg = 0x1D;
4003
4004
                    break;
4005 2
                case '0066CC':
4006
                    $colorIdxBg = 0x1E;
4007
4008
                    break;
4009 2
                case 'CCCCFF':
4010
                    $colorIdxBg = 0x1F;
4011
4012
                    break;
4013 2
                case '000080':
4014
                    $colorIdxBg = 0x20;
4015
4016
                    break;
4017 2
                case 'FF00FF':
4018
                    $colorIdxBg = 0x21;
4019
4020
                    break;
4021 2
                case 'FFFF00':
4022
                    $colorIdxBg = 0x22;
4023
4024
                    break;
4025 2
                case '00FFFF':
4026
                    $colorIdxBg = 0x23;
4027
4028
                    break;
4029 2
                case '800080':
4030
                    $colorIdxBg = 0x24;
4031
4032
                    break;
4033 2
                case '800000':
4034
                    $colorIdxBg = 0x25;
4035
4036
                    break;
4037 2
                case '008080':
4038
                    $colorIdxBg = 0x26;
4039
4040
                    break;
4041 2
                case '0000FF':
4042
                    $colorIdxBg = 0x27;
4043
4044
                    break;
4045 2
                case '00CCFF':
4046
                    $colorIdxBg = 0x28;
4047
4048
                    break;
4049 2
                case 'CCFFFF':
4050
                    $colorIdxBg = 0x29;
4051
4052
                    break;
4053 2
                case 'CCFFCC':
4054
                    $colorIdxBg = 0x2A;
4055
4056
                    break;
4057 2
                case 'FFFF99':
4058
                    $colorIdxBg = 0x2B;
4059
4060
                    break;
4061 2
                case '99CCFF':
4062
                    $colorIdxBg = 0x2C;
4063
4064
                    break;
4065 2
                case 'FF99CC':
4066
                    $colorIdxBg = 0x2D;
4067
4068
                    break;
4069 2
                case 'CC99FF':
4070
                    $colorIdxBg = 0x2E;
4071
4072
                    break;
4073 2
                case 'FFCC99':
4074
                    $colorIdxBg = 0x2F;
4075
4076
                    break;
4077 2
                case '3366FF':
4078
                    $colorIdxBg = 0x30;
4079
4080
                    break;
4081 2
                case '33CCCC':
4082
                    $colorIdxBg = 0x31;
4083
4084
                    break;
4085 2
                case '99CC00':
4086
                    $colorIdxBg = 0x32;
4087
4088
                    break;
4089 2
                case 'FFCC00':
4090
                    $colorIdxBg = 0x33;
4091
4092
                    break;
4093 2
                case 'FF9900':
4094
                    $colorIdxBg = 0x34;
4095
4096
                    break;
4097 2
                case 'FF6600':
4098
                    $colorIdxBg = 0x35;
4099
4100
                    break;
4101 2
                case '666699':
4102
                    $colorIdxBg = 0x36;
4103
4104
                    break;
4105 2
                case '969696':
4106
                    $colorIdxBg = 0x37;
4107
4108
                    break;
4109 2
                case '003366':
4110
                    $colorIdxBg = 0x38;
4111
4112
                    break;
4113 2
                case '339966':
4114
                    $colorIdxBg = 0x39;
4115
4116
                    break;
4117 2
                case '003300':
4118
                    $colorIdxBg = 0x3A;
4119
4120
                    break;
4121 2
                case '333300':
4122
                    $colorIdxBg = 0x3B;
4123
4124
                    break;
4125 2
                case '993300':
4126
                    $colorIdxBg = 0x3C;
4127
4128
                    break;
4129 2
                case '993366':
4130
                    $colorIdxBg = 0x3D;
4131
4132
                    break;
4133 2
                case '333399':
4134
                    $colorIdxBg = 0x3E;
4135
4136
                    break;
4137 2
                case '333333':
4138
                    $colorIdxBg = 0x3F;
4139
4140
                    break;
4141
                default:
4142 2
                    $colorIdxBg = 0x41;
4143
4144 2
                    break;
4145
            }
4146
            // Fg Color
4147 2
            switch ($conditional->getStyle()->getFill()->getEndColor()->getRGB()) {
4148 2
                case '000000':
4149
                    $colorIdxFg = 0x08;
4150
4151
                    break;
4152 2
                case 'FFFFFF':
4153
                    $colorIdxFg = 0x09;
4154
4155
                    break;
4156 2
                case 'FF0000':
4157
                    $colorIdxFg = 0x0A;
4158
4159
                    break;
4160 2
                case '00FF00':
4161
                    $colorIdxFg = 0x0B;
4162
4163
                    break;
4164 2
                case '0000FF':
4165
                    $colorIdxFg = 0x0C;
4166
4167
                    break;
4168 2
                case 'FFFF00':
4169
                    $colorIdxFg = 0x0D;
4170
4171
                    break;
4172 2
                case 'FF00FF':
4173
                    $colorIdxFg = 0x0E;
4174
4175
                    break;
4176 2
                case '00FFFF':
4177
                    $colorIdxFg = 0x0F;
4178
4179
                    break;
4180 2
                case '800000':
4181
                    $colorIdxFg = 0x10;
4182
4183
                    break;
4184 2
                case '008000':
4185
                    $colorIdxFg = 0x11;
4186
4187
                    break;
4188 2
                case '000080':
4189
                    $colorIdxFg = 0x12;
4190
4191
                    break;
4192 2
                case '808000':
4193
                    $colorIdxFg = 0x13;
4194
4195
                    break;
4196 2
                case '800080':
4197
                    $colorIdxFg = 0x14;
4198
4199
                    break;
4200 2
                case '008080':
4201
                    $colorIdxFg = 0x15;
4202
4203
                    break;
4204 2
                case 'C0C0C0':
4205
                    $colorIdxFg = 0x16;
4206
4207
                    break;
4208 2
                case '808080':
4209
                    $colorIdxFg = 0x17;
4210
4211
                    break;
4212 2
                case '9999FF':
4213
                    $colorIdxFg = 0x18;
4214
4215
                    break;
4216 2
                case '993366':
4217
                    $colorIdxFg = 0x19;
4218
4219
                    break;
4220 2
                case 'FFFFCC':
4221
                    $colorIdxFg = 0x1A;
4222
4223
                    break;
4224 2
                case 'CCFFFF':
4225
                    $colorIdxFg = 0x1B;
4226
4227
                    break;
4228 2
                case '660066':
4229
                    $colorIdxFg = 0x1C;
4230
4231
                    break;
4232 2
                case 'FF8080':
4233
                    $colorIdxFg = 0x1D;
4234
4235
                    break;
4236 2
                case '0066CC':
4237
                    $colorIdxFg = 0x1E;
4238
4239
                    break;
4240 2
                case 'CCCCFF':
4241
                    $colorIdxFg = 0x1F;
4242
4243
                    break;
4244 2
                case '000080':
4245
                    $colorIdxFg = 0x20;
4246
4247
                    break;
4248 2
                case 'FF00FF':
4249
                    $colorIdxFg = 0x21;
4250
4251
                    break;
4252 2
                case 'FFFF00':
4253
                    $colorIdxFg = 0x22;
4254
4255
                    break;
4256 2
                case '00FFFF':
4257
                    $colorIdxFg = 0x23;
4258
4259
                    break;
4260 2
                case '800080':
4261
                    $colorIdxFg = 0x24;
4262
4263
                    break;
4264 2
                case '800000':
4265
                    $colorIdxFg = 0x25;
4266
4267
                    break;
4268 2
                case '008080':
4269
                    $colorIdxFg = 0x26;
4270
4271
                    break;
4272 2
                case '0000FF':
4273
                    $colorIdxFg = 0x27;
4274
4275
                    break;
4276 2
                case '00CCFF':
4277
                    $colorIdxFg = 0x28;
4278
4279
                    break;
4280 2
                case 'CCFFFF':
4281
                    $colorIdxFg = 0x29;
4282
4283
                    break;
4284 2
                case 'CCFFCC':
4285
                    $colorIdxFg = 0x2A;
4286
4287
                    break;
4288 2
                case 'FFFF99':
4289
                    $colorIdxFg = 0x2B;
4290
4291
                    break;
4292 2
                case '99CCFF':
4293
                    $colorIdxFg = 0x2C;
4294
4295
                    break;
4296 2
                case 'FF99CC':
4297
                    $colorIdxFg = 0x2D;
4298
4299
                    break;
4300 2
                case 'CC99FF':
4301
                    $colorIdxFg = 0x2E;
4302
4303
                    break;
4304 2
                case 'FFCC99':
4305
                    $colorIdxFg = 0x2F;
4306
4307
                    break;
4308 2
                case '3366FF':
4309
                    $colorIdxFg = 0x30;
4310
4311
                    break;
4312 2
                case '33CCCC':
4313
                    $colorIdxFg = 0x31;
4314
4315
                    break;
4316 2
                case '99CC00':
4317
                    $colorIdxFg = 0x32;
4318
4319
                    break;
4320 2
                case 'FFCC00':
4321
                    $colorIdxFg = 0x33;
4322
4323
                    break;
4324 2
                case 'FF9900':
4325
                    $colorIdxFg = 0x34;
4326
4327
                    break;
4328 2
                case 'FF6600':
4329
                    $colorIdxFg = 0x35;
4330
4331
                    break;
4332 2
                case '666699':
4333
                    $colorIdxFg = 0x36;
4334
4335
                    break;
4336 2
                case '969696':
4337
                    $colorIdxFg = 0x37;
4338
4339
                    break;
4340 2
                case '003366':
4341
                    $colorIdxFg = 0x38;
4342
4343
                    break;
4344 2
                case '339966':
4345
                    $colorIdxFg = 0x39;
4346
4347
                    break;
4348 2
                case '003300':
4349
                    $colorIdxFg = 0x3A;
4350
4351
                    break;
4352 2
                case '333300':
4353
                    $colorIdxFg = 0x3B;
4354
4355
                    break;
4356 2
                case '993300':
4357
                    $colorIdxFg = 0x3C;
4358
4359
                    break;
4360 2
                case '993366':
4361
                    $colorIdxFg = 0x3D;
4362
4363
                    break;
4364 2
                case '333399':
4365
                    $colorIdxFg = 0x3E;
4366
4367
                    break;
4368 2
                case '333333':
4369
                    $colorIdxFg = 0x3F;
4370
4371
                    break;
4372
                default:
4373 2
                    $colorIdxFg = 0x40;
4374
4375 2
                    break;
4376
            }
4377 2
            $dataBlockFill = pack('v', $blockFillPatternStyle);
4378 2
            $dataBlockFill .= pack('v', $colorIdxFg | ($colorIdxBg << 7));
4379
        }
4380
4381 2
        $data = pack('CCvvVv', $type, $operatorType, $szValue1, $szValue2, $flags, 0x0000);
4382 2
        if ($bFormatFont == 1) { // Block Formatting : OK
4383 2
            $data .= $dataBlockFont;
4384
        }
4385 2
        if ($bFormatAlign == 1) {
4386
            $data .= $dataBlockAlign;
4387
        }
4388 2
        if ($bFormatBorder == 1) {
4389
            $data .= $dataBlockBorder;
4390
        }
4391 2
        if ($bFormatFill == 1) { // Block Formatting : OK
4392 2
            $data .= $dataBlockFill;
4393
        }
4394 2
        if ($bFormatProt == 1) {
4395
            $data .= $this->getDataBlockProtection($conditional);
4396
        }
4397 2
        if ($operand1 !== null) {
4398 2
            $data .= $operand1;
4399
        }
4400 2
        if ($operand2 !== null) {
4401 1
            $data .= $operand2;
4402
        }
4403 2
        $header = pack('vv', $record, strlen($data));
4404 2
        $this->append($header . $data);
4405 2
    }
4406
4407
    /**
4408
     * Write CFHeader record.
4409
     */
4410 2
    private function writeCFHeader(): void
4411
    {
4412 2
        $record = 0x01B0; // Record identifier
4413 2
        $length = 0x0016; // Bytes to follow
4414
4415 2
        $numColumnMin = null;
4416 2
        $numColumnMax = null;
4417 2
        $numRowMin = null;
4418 2
        $numRowMax = null;
4419 2
        $arrConditional = [];
4420 2
        foreach ($this->phpSheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) {
4421 2
            foreach ($conditionalStyles as $conditional) {
4422
                if (
4423 2
                    $conditional->getConditionType() == Conditional::CONDITION_EXPRESSION
4424 2
                    || $conditional->getConditionType() == Conditional::CONDITION_CELLIS
4425
                ) {
4426 2
                    if (!in_array($conditional->getHashCode(), $arrConditional)) {
4427 2
                        $arrConditional[] = $conditional->getHashCode();
4428
                    }
4429
                    // Cells
4430 2
                    $arrCoord = Coordinate::coordinateFromString($cellCoordinate);
4431 2
                    if (!is_numeric($arrCoord[0])) {
4432 2
                        $arrCoord[0] = Coordinate::columnIndexFromString($arrCoord[0]);
4433
                    }
4434 2
                    if ($numColumnMin === null || ($numColumnMin > $arrCoord[0])) {
4435 2
                        $numColumnMin = $arrCoord[0];
4436
                    }
4437 2
                    if ($numColumnMax === null || ($numColumnMax < $arrCoord[0])) {
4438 2
                        $numColumnMax = $arrCoord[0];
4439
                    }
4440 2
                    if ($numRowMin === null || ($numRowMin > $arrCoord[1])) {
4441 2
                        $numRowMin = $arrCoord[1];
4442
                    }
4443 2
                    if ($numRowMax === null || ($numRowMax < $arrCoord[1])) {
4444 2
                        $numRowMax = $arrCoord[1];
4445
                    }
4446
                }
4447
            }
4448
        }
4449 2
        $needRedraw = 1;
4450 2
        $cellRange = pack('vvvv', $numRowMin - 1, $numRowMax - 1, $numColumnMin - 1, $numColumnMax - 1);
4451
4452 2
        $header = pack('vv', $record, $length);
4453 2
        $data = pack('vv', count($arrConditional), $needRedraw);
4454 2
        $data .= $cellRange;
4455 2
        $data .= pack('v', 0x0001);
4456 2
        $data .= $cellRange;
4457 2
        $this->append($header . $data);
4458 2
    }
4459
4460
    private function getDataBlockProtection(Conditional $conditional): int
4461
    {
4462
        $dataBlockProtection = 0;
4463
        if ($conditional->getStyle()->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED) {
4464
            $dataBlockProtection = 1;
4465
        }
4466
        if ($conditional->getStyle()->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED) {
4467
            $dataBlockProtection = 1 << 1;
4468
        }
4469
4470
        return $dataBlockProtection;
4471
    }
4472
}
4473