Completed
Pull Request — master (#1559)
by Adrien
09:36
created

Worksheet::writeSetup()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 57
Code Lines 42

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 42
c 2
b 0
f 0
nc 16
nop 0
dl 0
loc 57
ccs 40
cts 42
cp 0.9524
crap 5.0026
rs 8.9368

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