Passed
Push — master ( c380b2...9239b3 )
by Adrien
10:06
created

Worksheet::writeCFRule()   F

Complexity

Conditions 387

Size

Total Lines 1461
Code Lines 1066

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 278
CRAP Score 52911.1267

Importance

Changes 0
Metric Value
cc 387
eloc 1066
c 0
b 0
f 0
nop 1
dl 0
loc 1461
ccs 278
cts 943
cp 0.2948
crap 52911.1267
rs 3.3333

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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