Failed Conditions
Push — master ( 02f37d...f95322 )
by Adrien
09:26
created

Worksheet::writeObjPicture()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 63
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 56
nc 1
nop 8
dl 0
loc 63
ccs 0
cts 57
cp 0
crap 2
rs 8.9599
c 0
b 0
f 0

How to fix   Long Method    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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