For God so loved the world, that He gave His only begotten Son, that all who believe in Him should not perish but have everlasting life
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4240 lines
157 KiB

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