Failed Conditions
Push — master ( 42761f...d02352 )
by Adrien
16:26 queued 08:32
created

Worksheet::writeSetup()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 55
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 5.0026

Importance

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

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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