PageRenderTime 1082ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/branches/v1.6.4/Classes/PHPExcel/Writer/Excel5/Worksheet.php

#
PHP | 3650 lines | 1802 code | 444 blank | 1404 comment | 263 complexity | a4e415e3687cea9552899ea3dbf07b64 MD5 | raw file
Possible License(s): AGPL-1.0, LGPL-2.0, LGPL-2.1, GPL-3.0, LGPL-3.0
  1. <?php
  2. /*
  3. * Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
  4. *
  5. * The majority of this is _NOT_ my code. I simply ported it from the
  6. * PERL Spreadsheet::WriteExcel module.
  7. *
  8. * The author of the Spreadsheet::WriteExcel module is John McNamara
  9. * <jmcnamara@cpan.org>
  10. *
  11. * I _DO_ maintain this code, and John McNamara has nothing to do with the
  12. * porting of this code to PHP. Any questions directly related to this
  13. * class library should be directed to me.
  14. *
  15. * License Information:
  16. *
  17. * PHPExcel_Writer_Excel5_Writer: A library for generating Excel Spreadsheets
  18. * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
  19. *
  20. * This library is free software; you can redistribute it and/or
  21. * modify it under the terms of the GNU Lesser General Public
  22. * License as published by the Free Software Foundation; either
  23. * version 2.1 of the License, or (at your option) any later version.
  24. *
  25. * This library is distributed in the hope that it will be useful,
  26. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  27. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  28. * Lesser General Public License for more details.
  29. *
  30. * You should have received a copy of the GNU Lesser General Public
  31. * License along with this library; if not, write to the Free Software
  32. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  33. */
  34. require_once 'PHPExcel/Writer/Excel5/Parser.php';
  35. require_once 'PHPExcel/Writer/Excel5/BIFFwriter.php';
  36. /**
  37. * Class for generating Excel Spreadsheets
  38. *
  39. * @author Xavier Noguer <xnoguer@rezebra.com>
  40. * @category FileFormats
  41. * @package PHPExcel_Writer_Excel5_Writer
  42. */
  43. class PHPExcel_Writer_Excel5_Worksheet extends PHPExcel_Writer_Excel5_BIFFwriter
  44. {
  45. /**
  46. * Name of the Worksheet
  47. * @var string
  48. */
  49. var $name;
  50. /**
  51. * Index for the Worksheet
  52. * @var integer
  53. */
  54. var $index;
  55. /**
  56. * Reference to the (default) Format object for URLs
  57. * @var object Format
  58. */
  59. var $_url_format;
  60. /**
  61. * Reference to the parser used for parsing formulas
  62. * @var object Format
  63. */
  64. var $_parser;
  65. /**
  66. * Filehandle to the temporary file for storing data
  67. * @var resource
  68. */
  69. var $_filehandle;
  70. /**
  71. * Boolean indicating if we are using a temporary file for storing data
  72. * @var bool
  73. */
  74. var $_using_tmpfile;
  75. /**
  76. * Maximum number of rows for an Excel spreadsheet (BIFF5)
  77. * @var integer
  78. */
  79. var $_xls_rowmax;
  80. /**
  81. * Maximum number of columns for an Excel spreadsheet (BIFF5)
  82. * @var integer
  83. */
  84. var $_xls_colmax;
  85. /**
  86. * Maximum number of characters for a string (LABEL record in BIFF5)
  87. * @var integer
  88. */
  89. var $_xls_strmax;
  90. /**
  91. * First row for the DIMENSIONS record
  92. * @var integer
  93. * @see _storeDimensions()
  94. */
  95. var $_dim_rowmin;
  96. /**
  97. * Last row for the DIMENSIONS record
  98. * @var integer
  99. * @see _storeDimensions()
  100. */
  101. var $_dim_rowmax;
  102. /**
  103. * First column for the DIMENSIONS record
  104. * @var integer
  105. * @see _storeDimensions()
  106. */
  107. var $_dim_colmin;
  108. /**
  109. * Last column for the DIMENSIONS record
  110. * @var integer
  111. * @see _storeDimensions()
  112. */
  113. var $_dim_colmax;
  114. /**
  115. * Default column character width
  116. */
  117. private $_defColWidth = 8;
  118. /**
  119. * Default row height in twips = 1/20 point
  120. */
  121. private $_defaultRowHeight = null;
  122. /**
  123. * Array containing format information for columns
  124. * @var array
  125. */
  126. var $_colinfo;
  127. /**
  128. * Array containing the selected area for the worksheet
  129. * @var array
  130. */
  131. var $_selection;
  132. /**
  133. * Array containing the panes for the worksheet
  134. * @var array
  135. */
  136. var $_panes;
  137. /**
  138. * The active pane for the worksheet
  139. * @var integer
  140. */
  141. var $_active_pane;
  142. /**
  143. * Bit specifying if panes are frozen
  144. * @var integer
  145. */
  146. var $_frozen;
  147. /**
  148. * Bit specifying if the worksheet is selected
  149. * @var integer
  150. */
  151. var $selected;
  152. /**
  153. * The paper size (for printing) (DOCUMENT!!!)
  154. * @var integer
  155. */
  156. var $_paper_size;
  157. /**
  158. * Bit specifying paper orientation (for printing). 0 => landscape, 1 => portrait
  159. * @var integer
  160. */
  161. var $_orientation;
  162. /**
  163. * The page header caption
  164. * @var string
  165. */
  166. var $_header;
  167. /**
  168. * The page footer caption
  169. * @var string
  170. */
  171. var $_footer;
  172. /**
  173. * The horizontal centering value for the page
  174. * @var integer
  175. */
  176. var $_hcenter;
  177. /**
  178. * The vertical centering value for the page
  179. * @var integer
  180. */
  181. var $_vcenter;
  182. /**
  183. * The margin for the header
  184. * @var float
  185. */
  186. var $_margin_head;
  187. /**
  188. * The margin for the footer
  189. * @var float
  190. */
  191. var $_margin_foot;
  192. /**
  193. * The left margin for the worksheet in inches
  194. * @var float
  195. */
  196. var $_margin_left;
  197. /**
  198. * The right margin for the worksheet in inches
  199. * @var float
  200. */
  201. var $_margin_right;
  202. /**
  203. * The top margin for the worksheet in inches
  204. * @var float
  205. */
  206. var $_margin_top;
  207. /**
  208. * The bottom margin for the worksheet in inches
  209. * @var float
  210. */
  211. var $_margin_bottom;
  212. /**
  213. * First row to reapeat on each printed page
  214. * @var integer
  215. */
  216. var $title_rowmin;
  217. /**
  218. * Last row to reapeat on each printed page
  219. * @var integer
  220. */
  221. var $title_rowmax;
  222. /**
  223. * First column to reapeat on each printed page
  224. * @var integer
  225. */
  226. var $title_colmin;
  227. /**
  228. * First row of the area to print
  229. * @var integer
  230. */
  231. var $print_rowmin;
  232. /**
  233. * Last row to of the area to print
  234. * @var integer
  235. */
  236. var $print_rowmax;
  237. /**
  238. * First column of the area to print
  239. * @var integer
  240. */
  241. var $print_colmin;
  242. /**
  243. * Last column of the area to print
  244. * @var integer
  245. */
  246. var $print_colmax;
  247. /**
  248. * Whether to use outline.
  249. * @var integer
  250. */
  251. var $_outline_on;
  252. /**
  253. * Auto outline styles.
  254. * @var bool
  255. */
  256. var $_outline_style;
  257. /**
  258. * Whether to have outline summary below.
  259. * @var bool
  260. */
  261. var $_outline_below;
  262. /**
  263. * Whether to have outline summary at the right.
  264. * @var bool
  265. */
  266. var $_outline_right;
  267. /**
  268. * Outline row level.
  269. * @var integer
  270. */
  271. var $_outline_row_level;
  272. /**
  273. * Whether to fit to page when printing or not.
  274. * @var bool
  275. */
  276. var $_fit_page;
  277. /**
  278. * Number of pages to fit wide
  279. * @var integer
  280. */
  281. var $_fit_width;
  282. /**
  283. * Number of pages to fit high
  284. * @var integer
  285. */
  286. var $_fit_height;
  287. /**
  288. * Reference to the total number of strings in the workbook
  289. * @var integer
  290. */
  291. var $_str_total;
  292. /**
  293. * Reference to the number of unique strings in the workbook
  294. * @var integer
  295. */
  296. var $_str_unique;
  297. /**
  298. * Reference to the array containing all the unique strings in the workbook
  299. * @var array
  300. */
  301. var $_str_table;
  302. /**
  303. * Merged cell ranges
  304. * @var array
  305. */
  306. var $_merged_ranges;
  307. /**
  308. * Charset encoding currently used when calling writeString()
  309. * @var string
  310. */
  311. var $_input_encoding;
  312. /**
  313. * The temporary dir for storing files
  314. * @var string
  315. */
  316. var $_tmp_dir;
  317. /**
  318. * List of temporary files created
  319. * @var array
  320. */
  321. var $_tempFilesCreated = array();
  322. /**
  323. * Index of first used row (at least 0)
  324. * @var int
  325. */
  326. private $_firstRowIndex;
  327. /**
  328. * Index of last used row. (no used rows means -1)
  329. * @var int
  330. */
  331. private $_lastRowIndex;
  332. /**
  333. * Index of first used column (at least 0)
  334. * @var int
  335. */
  336. private $_firstColumnIndex;
  337. /**
  338. * Index of last used column (no used columns means -1)
  339. * @var int
  340. */
  341. private $_lastColumnIndex;
  342. /**
  343. * Constructor
  344. *
  345. * @param string $name The name of the new worksheet
  346. * @param integer $index The index of the new worksheet
  347. * @param mixed &$activesheet The current activesheet of the workbook we belong to
  348. * @param mixed &$firstsheet The first worksheet in the workbook we belong to
  349. * @param mixed &$url_format The default format for hyperlinks
  350. * @param mixed &$parser The formula parser created for the Workbook
  351. * @param string $tempDir The temporary directory to be used
  352. * @access private
  353. */
  354. function PHPExcel_Writer_Excel5_Worksheet($BIFF_version, $name,
  355. $index, &$activesheet,
  356. &$firstsheet, &$str_total,
  357. &$str_unique, &$str_table,
  358. &$url_format, &$parser, $tempDir = '')
  359. {
  360. // It needs to call its parent's constructor explicitly
  361. $this->PHPExcel_Writer_Excel5_BIFFwriter();
  362. $this->_BIFF_version = $BIFF_version;
  363. $rowmax = 65536; // 16384 in Excel 5
  364. $colmax = 256;
  365. $this->name = $name;
  366. $this->index = $index;
  367. $this->activesheet = &$activesheet;
  368. $this->firstsheet = &$firstsheet;
  369. $this->_str_total = &$str_total;
  370. $this->_str_unique = &$str_unique;
  371. $this->_str_table = &$str_table;
  372. $this->_url_format = &$url_format;
  373. $this->_parser = &$parser;
  374. //$this->ext_sheets = array();
  375. $this->_filehandle = '';
  376. $this->_using_tmpfile = true;
  377. //$this->fileclosed = 0;
  378. //$this->offset = 0;
  379. $this->_xls_rowmax = $rowmax;
  380. $this->_xls_colmax = $colmax;
  381. $this->_xls_strmax = 255;
  382. $this->_dim_rowmin = $rowmax + 1;
  383. $this->_dim_rowmax = 0;
  384. $this->_dim_colmin = $colmax + 1;
  385. $this->_dim_colmax = 0;
  386. $this->_colinfo = array();
  387. $this->_selection = array(0,0,0,0);
  388. $this->_panes = array();
  389. $this->_active_pane = 3;
  390. $this->_frozen = 0;
  391. $this->selected = 0;
  392. $this->_paper_size = 0x0;
  393. $this->_orientation = 0x1;
  394. $this->_header = '';
  395. $this->_footer = '';
  396. $this->_hcenter = 0;
  397. $this->_vcenter = 0;
  398. $this->_margin_head = 0.50;
  399. $this->_margin_foot = 0.50;
  400. $this->_margin_left = 0.75;
  401. $this->_margin_right = 0.75;
  402. $this->_margin_top = 1.00;
  403. $this->_margin_bottom = 1.00;
  404. $this->title_rowmin = null;
  405. $this->title_rowmax = null;
  406. $this->title_colmin = null;
  407. $this->title_colmax = null;
  408. $this->print_rowmin = null;
  409. $this->print_rowmax = null;
  410. $this->print_colmin = null;
  411. $this->print_colmax = null;
  412. $this->_print_gridlines = 1;
  413. $this->_screen_gridlines = 1;
  414. $this->_print_headers = 0;
  415. $this->_fit_page = 0;
  416. $this->_fit_width = 0;
  417. $this->_fit_height = 0;
  418. $this->_hbreaks = array();
  419. $this->_vbreaks = array();
  420. $this->_protect = 0;
  421. $this->_password = null;
  422. $this->col_sizes = array();
  423. $this->_row_sizes = array();
  424. $this->_zoom = 100;
  425. $this->_print_scale = 100;
  426. $this->_outline_row_level = 0;
  427. $this->_outline_style = 0;
  428. $this->_outline_below = 1;
  429. $this->_outline_right = 1;
  430. $this->_outline_on = 1;
  431. $this->_merged_ranges = array();
  432. $this->_input_encoding = '';
  433. $this->_dv = array();
  434. $this->_tmp_dir = $tempDir;
  435. $this->_initialize();
  436. }
  437. /**
  438. * Cleanup
  439. */
  440. public function cleanup() {
  441. @fclose($this->_filehandle);
  442. foreach ($this->_tempFilesCreated as $file) {
  443. @unlink($file);
  444. }
  445. }
  446. /**
  447. * Open a tmp file to store the majority of the Worksheet data. If this fails,
  448. * for example due to write permissions, store the data in memory. This can be
  449. * slow for large files.
  450. *
  451. * @access private
  452. */
  453. function _initialize()
  454. {
  455. // Open tmp file for storing Worksheet data
  456. $fileName = tempnam($this->_tmp_dir, 'XLSHEET');
  457. $fh = fopen($fileName, 'w+');
  458. if ($fh) {
  459. // Store filehandle
  460. $this->_filehandle = $fh;
  461. $this->_tempFilesCreated[] = $fileName;
  462. } else {
  463. // If tmpfile() fails store data in memory
  464. $this->_using_tmpfile = false;
  465. }
  466. }
  467. /**
  468. * Sets the temp dir used for storing files
  469. *
  470. * @access public
  471. * @param string $dir The dir to be used as temp dir
  472. * @return true if given dir is valid, false otherwise
  473. */
  474. function setTempDir($dir)
  475. {
  476. if (is_dir($dir)) {
  477. $this->_tmp_dir = $dir;
  478. return true;
  479. }
  480. return false;
  481. }
  482. /**
  483. * Add data to the beginning of the workbook (note the reverse order)
  484. * and to the end of the workbook.
  485. *
  486. * @access public
  487. * @see PHPExcel_Writer_Excel5_Workbook::storeWorkbook()
  488. * @param array $sheetnames The array of sheetnames from the Workbook this
  489. * worksheet belongs to
  490. */
  491. function close($sheetnames)
  492. {
  493. $num_sheets = count($sheetnames);
  494. /***********************************************
  495. * Prepend in reverse order!!
  496. */
  497. // Prepend the sheet dimensions
  498. $this->_storeDimensions();
  499. // Prepend the sheet password
  500. $this->_storePassword();
  501. // Prepend the sheet protection
  502. $this->_storeProtect();
  503. // Prepend the page setup
  504. $this->_storeSetup();
  505. /* FIXME: margins are actually appended */
  506. // Prepend the bottom margin
  507. $this->_storeMarginBottom();
  508. // Prepend the top margin
  509. $this->_storeMarginTop();
  510. // Prepend the right margin
  511. $this->_storeMarginRight();
  512. // Prepend the left margin
  513. $this->_storeMarginLeft();
  514. // Prepend the page vertical centering
  515. $this->_storeVcenter();
  516. // Prepend the page horizontal centering
  517. $this->_storeHcenter();
  518. // Prepend the page footer
  519. $this->_storeFooter();
  520. // Prepend the page header
  521. $this->_storeHeader();
  522. // Prepend the vertical page breaks
  523. $this->_storeVbreak();
  524. // Prepend the horizontal page breaks
  525. $this->_storeHbreak();
  526. // Prepend WSBOOL
  527. $this->_storeWsbool();
  528. // Prepend DEFAULTROWHEIGHT
  529. if ($this->_BIFF_version == 0x0600) {
  530. $this->_storeDefaultRowHeight();
  531. }
  532. // Prepend GRIDSET
  533. $this->_storeGridset();
  534. // Prepend GUTS
  535. $this->_storeGuts();
  536. // Prepend PRINTGRIDLINES
  537. $this->_storePrintGridlines();
  538. // Prepend PRINTHEADERS
  539. $this->_storePrintHeaders();
  540. // Prepend EXTERNSHEET references
  541. if ($this->_BIFF_version == 0x0500) {
  542. for ($i = $num_sheets; $i > 0; --$i) {
  543. $sheetname = $sheetnames[$i-1];
  544. $this->_storeExternsheet($sheetname);
  545. }
  546. }
  547. // Prepend the EXTERNCOUNT of external references.
  548. if ($this->_BIFF_version == 0x0500) {
  549. $this->_storeExterncount($num_sheets);
  550. }
  551. // Prepend the COLINFO records if they exist
  552. if (!empty($this->_colinfo)) {
  553. $colcount = count($this->_colinfo);
  554. for ($i = 0; $i < $colcount; ++$i) {
  555. $this->_storeColinfo($this->_colinfo[$i]);
  556. }
  557. }
  558. // Prepend the DEFCOLWIDTH record
  559. $this->_storeDefcol();
  560. // Prepend the BOF record
  561. $this->_storeBof(0x0010);
  562. /*
  563. * End of prepend. Read upwards from here.
  564. ***********************************************/
  565. // Append
  566. $this->_storeWindow2();
  567. $this->_storeZoom();
  568. if (!empty($this->_panes)) {
  569. $this->_storePanes($this->_panes);
  570. }
  571. $this->_storeSelection($this->_selection);
  572. $this->_storeMergedCells();
  573. /* TODO: add data validity */
  574. /*if ($this->_BIFF_version == 0x0600) {
  575. $this->_storeDataValidity();
  576. }*/
  577. $this->_storeEof();
  578. }
  579. /**
  580. * Retrieve the worksheet name.
  581. * This is usefull when creating worksheets without a name.
  582. *
  583. * @access public
  584. * @return string The worksheet's name
  585. */
  586. function getName()
  587. {
  588. return $this->name;
  589. }
  590. /**
  591. * Retrieves data from memory in one chunk, or from disk in $buffer
  592. * sized chunks.
  593. *
  594. * @return string The data
  595. */
  596. function getData()
  597. {
  598. $buffer = 4096;
  599. // Return data stored in memory
  600. if (isset($this->_data)) {
  601. $tmp = $this->_data;
  602. unset($this->_data);
  603. $fh = $this->_filehandle;
  604. if ($this->_using_tmpfile) {
  605. fseek($fh, 0);
  606. }
  607. return $tmp;
  608. }
  609. // Return data stored on disk
  610. if ($this->_using_tmpfile) {
  611. if ($tmp = fread($this->_filehandle, $buffer)) {
  612. return $tmp;
  613. }
  614. }
  615. // No data to return
  616. return '';
  617. }
  618. /**
  619. * Sets a merged cell range
  620. *
  621. * @access public
  622. * @param integer $first_row First row of the area to merge
  623. * @param integer $first_col First column of the area to merge
  624. * @param integer $last_row Last row of the area to merge
  625. * @param integer $last_col Last column of the area to merge
  626. */
  627. function setMerge($first_row, $first_col, $last_row, $last_col)
  628. {
  629. if (($last_row < $first_row) || ($last_col < $first_col)) {
  630. return;
  631. }
  632. // don't check rowmin, rowmax, etc... because we don't know when this
  633. // is going to be called
  634. $this->_merged_ranges[] = array($first_row, $first_col, $last_row, $last_col);
  635. }
  636. /**
  637. * Set this worksheet as a selected worksheet,
  638. * i.e. the worksheet has its tab highlighted.
  639. *
  640. * @access public
  641. */
  642. function select()
  643. {
  644. $this->selected = 1;
  645. }
  646. /**
  647. * Set this worksheet as the active worksheet,
  648. * i.e. the worksheet that is displayed when the workbook is opened.
  649. * Also set it as selected.
  650. *
  651. * @access public
  652. */
  653. function activate()
  654. {
  655. $this->selected = 1;
  656. $this->activesheet = $this->index;
  657. }
  658. /**
  659. * Set this worksheet as the first visible sheet.
  660. * This is necessary when there are a large number of worksheets and the
  661. * activated worksheet is not visible on the screen.
  662. *
  663. * @access public
  664. */
  665. function setFirstSheet()
  666. {
  667. $this->firstsheet = $this->index;
  668. }
  669. /**
  670. * Set the worksheet protection flag
  671. * to prevent accidental modification and to
  672. * hide formulas if the locked and hidden format properties have been set.
  673. *
  674. * @access public
  675. * @param string $password The password to use for protecting the sheet.
  676. */
  677. function protect($password, $encoded = false)
  678. {
  679. $this->_protect = 1;
  680. $this->_password = ($encoded ? $password : $this->_encodePassword($password));
  681. }
  682. /**
  683. * Set the width of a single column or a range of columns.
  684. *
  685. * @access public
  686. * @param integer $firstcol first column on the range
  687. * @param integer $lastcol last column on the range
  688. * @param integer $width width to set
  689. * @param mixed $format The optional XF format to apply to the columns
  690. * @param integer $hidden The optional hidden atribute
  691. * @param integer $level The optional outline level
  692. */
  693. function setColumn($firstcol, $lastcol, $width, $format = null, $hidden = 0, $level = 0)
  694. {
  695. $this->_colinfo[] = array($firstcol, $lastcol, $width, &$format, $hidden, $level);
  696. // Set width to zero if column is hidden
  697. $width = ($hidden) ? 0 : $width;
  698. for ($col = $firstcol; $col <= $lastcol; ++$col) {
  699. $this->col_sizes[$col] = $width;
  700. }
  701. }
  702. /**
  703. * Set which cell or cells are selected in a worksheet
  704. *
  705. * @access public
  706. * @param integer $first_row first row in the selected quadrant
  707. * @param integer $first_column first column in the selected quadrant
  708. * @param integer $last_row last row in the selected quadrant
  709. * @param integer $last_column last column in the selected quadrant
  710. */
  711. function setSelection($first_row,$first_column,$last_row,$last_column)
  712. {
  713. $this->_selection = array($first_row,$first_column,$last_row,$last_column);
  714. }
  715. /**
  716. * Set panes and mark them as frozen.
  717. *
  718. * @access public
  719. * @param array $panes This is the only parameter received and is composed of the following:
  720. * 0 => Vertical split position,
  721. * 1 => Horizontal split position
  722. * 2 => Top row visible
  723. * 3 => Leftmost column visible
  724. * 4 => Active pane
  725. */
  726. function freezePanes($panes)
  727. {
  728. $this->_frozen = 1;
  729. $this->_panes = $panes;
  730. }
  731. /**
  732. * Set panes and mark them as unfrozen.
  733. *
  734. * @access public
  735. * @param array $panes This is the only parameter received and is composed of the following:
  736. * 0 => Vertical split position,
  737. * 1 => Horizontal split position
  738. * 2 => Top row visible
  739. * 3 => Leftmost column visible
  740. * 4 => Active pane
  741. */
  742. function thawPanes($panes)
  743. {
  744. $this->_frozen = 0;
  745. $this->_panes = $panes;
  746. }
  747. /**
  748. * Set the page orientation as portrait.
  749. *
  750. * @access public
  751. */
  752. function setPortrait()
  753. {
  754. $this->_orientation = 1;
  755. }
  756. /**
  757. * Set the page orientation as landscape.
  758. *
  759. * @access public
  760. */
  761. function setLandscape()
  762. {
  763. $this->_orientation = 0;
  764. }
  765. /**
  766. * Set the paper type. Ex. 1 = US Letter, 9 = A4
  767. *
  768. * @access public
  769. * @param integer $size The type of paper size to use
  770. */
  771. function setPaper($size = 0)
  772. {
  773. $this->_paper_size = $size;
  774. }
  775. /**
  776. * Set the page header caption and optional margin.
  777. *
  778. * @access public
  779. * @param string $string The header text
  780. * @param float $margin optional head margin in inches.
  781. */
  782. function setHeader($string,$margin = 0.50)
  783. {
  784. if (strlen($string) >= 255) {
  785. //carp 'Header string must be less than 255 characters';
  786. return;
  787. }
  788. $this->_header = $string;
  789. $this->_margin_head = $margin;
  790. }
  791. /**
  792. * Set the page footer caption and optional margin.
  793. *
  794. * @access public
  795. * @param string $string The footer text
  796. * @param float $margin optional foot margin in inches.
  797. */
  798. function setFooter($string,$margin = 0.50)
  799. {
  800. if (strlen($string) >= 255) {
  801. //carp 'Footer string must be less than 255 characters';
  802. return;
  803. }
  804. $this->_footer = $string;
  805. $this->_margin_foot = $margin;
  806. }
  807. /**
  808. * Center the page horinzontally.
  809. *
  810. * @access public
  811. * @param integer $center the optional value for centering. Defaults to 1 (center).
  812. */
  813. function centerHorizontally($center = 1)
  814. {
  815. $this->_hcenter = $center;
  816. }
  817. /**
  818. * Center the page vertically.
  819. *
  820. * @access public
  821. * @param integer $center the optional value for centering. Defaults to 1 (center).
  822. */
  823. function centerVertically($center = 1)
  824. {
  825. $this->_vcenter = $center;
  826. }
  827. /**
  828. * Set all the page margins to the same value in inches.
  829. *
  830. * @access public
  831. * @param float $margin The margin to set in inches
  832. */
  833. function setMargins($margin)
  834. {
  835. $this->setMarginLeft($margin);
  836. $this->setMarginRight($margin);
  837. $this->setMarginTop($margin);
  838. $this->setMarginBottom($margin);
  839. }
  840. /**
  841. * Set the left and right margins to the same value in inches.
  842. *
  843. * @access public
  844. * @param float $margin The margin to set in inches
  845. */
  846. function setMargins_LR($margin)
  847. {
  848. $this->setMarginLeft($margin);
  849. $this->setMarginRight($margin);
  850. }
  851. /**
  852. * Set the top and bottom margins to the same value in inches.
  853. *
  854. * @access public
  855. * @param float $margin The margin to set in inches
  856. */
  857. function setMargins_TB($margin)
  858. {
  859. $this->setMarginTop($margin);
  860. $this->setMarginBottom($margin);
  861. }
  862. /**
  863. * Set the left margin in inches.
  864. *
  865. * @access public
  866. * @param float $margin The margin to set in inches
  867. */
  868. function setMarginLeft($margin = 0.75)
  869. {
  870. $this->_margin_left = $margin;
  871. }
  872. /**
  873. * Set the right margin in inches.
  874. *
  875. * @access public
  876. * @param float $margin The margin to set in inches
  877. */
  878. function setMarginRight($margin = 0.75)
  879. {
  880. $this->_margin_right = $margin;
  881. }
  882. /**
  883. * Set the top margin in inches.
  884. *
  885. * @access public
  886. * @param float $margin The margin to set in inches
  887. */
  888. function setMarginTop($margin = 1.00)
  889. {
  890. $this->_margin_top = $margin;
  891. }
  892. /**
  893. * Set the bottom margin in inches.
  894. *
  895. * @access public
  896. * @param float $margin The margin to set in inches
  897. */
  898. function setMarginBottom($margin = 1.00)
  899. {
  900. $this->_margin_bottom = $margin;
  901. }
  902. /**
  903. * Set the rows to repeat at the top of each printed page.
  904. *
  905. * @access public
  906. * @param integer $first_row First row to repeat
  907. * @param integer $last_row Last row to repeat. Optional.
  908. */
  909. function repeatRows($first_row, $last_row = null)
  910. {
  911. $this->title_rowmin = $first_row;
  912. if (isset($last_row)) { //Second row is optional
  913. $this->title_rowmax = $last_row;
  914. } else {
  915. $this->title_rowmax = $first_row;
  916. }
  917. }
  918. /**
  919. * Set the columns to repeat at the left hand side of each printed page.
  920. *
  921. * @access public
  922. * @param integer $first_col First column to repeat
  923. * @param integer $last_col Last column to repeat. Optional.
  924. */
  925. function repeatColumns($first_col, $last_col = null)
  926. {
  927. $this->title_colmin = $first_col;
  928. if (isset($last_col)) { // Second col is optional
  929. $this->title_colmax = $last_col;
  930. } else {
  931. $this->title_colmax = $first_col;
  932. }
  933. }
  934. /**
  935. * Set the area of each worksheet that will be printed.
  936. *
  937. * @access public
  938. * @param integer $first_row First row of the area to print
  939. * @param integer $first_col First column of the area to print
  940. * @param integer $last_row Last row of the area to print
  941. * @param integer $last_col Last column of the area to print
  942. */
  943. function printArea($first_row, $first_col, $last_row, $last_col)
  944. {
  945. $this->print_rowmin = $first_row;
  946. $this->print_colmin = $first_col;
  947. $this->print_rowmax = $last_row;
  948. $this->print_colmax = $last_col;
  949. }
  950. /**
  951. * Set the option to hide gridlines on the printed page.
  952. *
  953. * @access public
  954. */
  955. function hideGridlines()
  956. {
  957. $this->_print_gridlines = 0;
  958. }
  959. /**
  960. * Set the option to hide gridlines on the worksheet (as seen on the screen).
  961. *
  962. * @access public
  963. */
  964. function hideScreenGridlines()
  965. {
  966. $this->_screen_gridlines = 0;
  967. }
  968. /**
  969. * Set the option to print the row and column headers on the printed page.
  970. *
  971. * @access public
  972. * @param integer $print Whether to print the headers or not. Defaults to 1 (print).
  973. */
  974. function printRowColHeaders($print = 1)
  975. {
  976. $this->_print_headers = $print;
  977. }
  978. /**
  979. * Set the vertical and horizontal number of pages that will define the maximum area printed.
  980. * It doesn't seem to work with OpenOffice.
  981. *
  982. * @access public
  983. * @param integer $width Maximun width of printed area in pages
  984. * @param integer $height Maximun heigth of printed area in pages
  985. * @see setPrintScale()
  986. */
  987. function fitToPages($width, $height)
  988. {
  989. $this->_fit_page = 1;
  990. $this->_fit_width = $width;
  991. $this->_fit_height = $height;
  992. }
  993. /**
  994. * Store the horizontal page breaks on a worksheet (for printing).
  995. * The breaks represent the row after which the break is inserted.
  996. *
  997. * @access public
  998. * @param array $breaks Array containing the horizontal page breaks
  999. */
  1000. function setHPagebreaks($breaks)
  1001. {
  1002. foreach ($breaks as $break) {
  1003. array_push($this->_hbreaks, $break);
  1004. }
  1005. }
  1006. /**
  1007. * Store the vertical page breaks on a worksheet (for printing).
  1008. * The breaks represent the column after which the break is inserted.
  1009. *
  1010. * @access public
  1011. * @param array $breaks Array containing the vertical page breaks
  1012. */
  1013. function setVPagebreaks($breaks)
  1014. {
  1015. foreach ($breaks as $break) {
  1016. array_push($this->_vbreaks, $break);
  1017. }
  1018. }
  1019. /**
  1020. * Set the worksheet zoom factor.
  1021. *
  1022. * @access public
  1023. * @param integer $scale The zoom factor
  1024. */
  1025. function setZoom($scale = 100)
  1026. {
  1027. // Confine the scale to Excel's range
  1028. if ($scale < 10 || $scale > 400) {
  1029. throw new Exception("Zoom factor $scale outside range: 10 <= zoom <= 400");
  1030. $scale = 100;
  1031. }
  1032. $this->_zoom = floor($scale);
  1033. }
  1034. /**
  1035. * Set the scale factor for the printed page.
  1036. * It turns off the "fit to page" option
  1037. *
  1038. * @access public
  1039. * @param integer $scale The optional scale factor. Defaults to 100
  1040. */
  1041. function setPrintScale($scale = 100)
  1042. {
  1043. // Confine the scale to Excel's range
  1044. if ($scale < 10 || $scale > 400) {
  1045. throw new Exception("Print scale $scale outside range: 10 <= zoom <= 400");
  1046. $scale = 100;
  1047. }
  1048. // Turn off "fit to page" option
  1049. $this->_fit_page = 0;
  1050. $this->_print_scale = floor($scale);
  1051. }
  1052. /**
  1053. * Map to the appropriate write method acording to the token recieved.
  1054. *
  1055. * @access public
  1056. * @param integer $row The row of the cell we are writing to
  1057. * @param integer $col The column of the cell we are writing to
  1058. * @param mixed $token What we are writing
  1059. * @param mixed $format The optional format to apply to the cell
  1060. */
  1061. function write($row, $col, $token, $format = null, $numberFormat = null)
  1062. {
  1063. // Check for a cell reference in A1 notation and substitute row and column
  1064. /*if ($_[0] =~ /^\D/) {
  1065. @_ = $this->_substituteCellref(@_);
  1066. }*/
  1067. if (($numberFormat != 'General') && (PHPExcel_Shared_Date::isDateTimeFormatCode($numberFormat))) {
  1068. if (is_string($token)) {
  1069. // Error string
  1070. return $this->writeString($row, $col, $token, $format);
  1071. } elseif (!is_float($token)) {
  1072. // PHP serialized date/time or date/time object
  1073. return $this->writeNumber($row, $col, PHPExcel_Shared_Date::PHPToExcel($token), $format);
  1074. } else {
  1075. // Excel serialized date/time
  1076. return $this->writeNumber($row, $col, $token, $format);
  1077. }
  1078. } elseif (preg_match("/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/", $token)) {
  1079. // Match number
  1080. return $this->writeNumber($row, $col, $token, $format);
  1081. } elseif (preg_match("/^[fh]tt?p:\/\//", $token)) {
  1082. // Match http or ftp URL
  1083. return $this->writeUrl($row, $col, $token, '', $format);
  1084. } elseif (preg_match("/^mailto:/", $token)) {
  1085. // Match mailto:
  1086. return $this->writeUrl($row, $col, $token, '', $format);
  1087. } elseif (preg_match("/^(?:in|ex)ternal:/", $token)) {
  1088. // Match internal or external sheet link
  1089. return $this->writeUrl($row, $col, $token, '', $format);
  1090. } elseif (preg_match("/^=/", $token)) {
  1091. // Match formula
  1092. return $this->writeFormula($row, $col, $token, $format);
  1093. } elseif (preg_match("/^@/", $token)) {
  1094. // Match formula
  1095. return $this->writeFormula($row, $col, $token, $format);
  1096. } elseif ($token == '') {
  1097. // Match blank
  1098. return $this->writeBlank($row, $col, $format);
  1099. } else {
  1100. // Default: match string
  1101. return $this->writeString($row, $col, $token, $format);
  1102. }
  1103. }
  1104. /**
  1105. * Write an array of values as a row
  1106. *
  1107. * @access public
  1108. * @param integer $row The row we are writing to
  1109. * @param integer $col The first col (leftmost col) we are writing to
  1110. * @param array $val The array of values to write
  1111. * @param mixed $format The optional format to apply to the cell
  1112. */
  1113. function writeRow($row, $col, $val, $format = null, $numberFormat = null)
  1114. {
  1115. $retval = '';
  1116. if (is_array($val)) {
  1117. foreach ($val as $v) {
  1118. if (is_array($v)) {
  1119. $this->writeCol($row, $col, $v, $format, $numberFormat = null);
  1120. } else {
  1121. $this->write($row, $col, $v, $format, $numberFormat);
  1122. }
  1123. ++$col;
  1124. }
  1125. } else {
  1126. throw new Exception('$val needs to be an array');
  1127. }
  1128. return($retval);
  1129. }
  1130. /**
  1131. * Write an array of values as a column
  1132. *
  1133. * @access public
  1134. * @param integer $row The first row (uppermost row) we are writing to
  1135. * @param integer $col The col we are writing to
  1136. * @param array $val The array of values to write
  1137. * @param mixed $format The optional format to apply to the cell
  1138. */
  1139. function writeCol($row, $col, $val, $format = null, $numberFormat = null)
  1140. {
  1141. $retval = '';
  1142. if (is_array($val)) {
  1143. foreach ($val as $v) {
  1144. $this->write($row, $col, $v, $format, $numberFormat);
  1145. ++$row;
  1146. }
  1147. } else {
  1148. throw new Exception('$val needs to be an array');
  1149. }
  1150. return($retval);
  1151. }
  1152. /**
  1153. * Returns an index to the XF record in the workbook
  1154. *
  1155. * @access private
  1156. * @param mixed &$format The optional XF format
  1157. * @return integer The XF record index
  1158. */
  1159. function _XF(&$format)
  1160. {
  1161. if ($format) {
  1162. return($format->getXfIndex());
  1163. } else {
  1164. return(0x0F);
  1165. }
  1166. }
  1167. /******************************************************************************
  1168. *******************************************************************************
  1169. *
  1170. * Internal methods
  1171. */
  1172. /**
  1173. * Store Worksheet data in memory using the parent's class append() or to a
  1174. * temporary file, the default.
  1175. *
  1176. * @access private
  1177. * @param string $data The binary data to append
  1178. */
  1179. function _append($data)
  1180. {
  1181. if ($this->_using_tmpfile) {
  1182. // Add CONTINUE records if necessary
  1183. if (strlen($data) > $this->_limit) {
  1184. $data = $this->_addContinue($data);
  1185. }
  1186. fwrite($this->_filehandle, $data);
  1187. $this->_datasize += strlen($data);
  1188. } else {
  1189. parent::_append($data);
  1190. }
  1191. }
  1192. /**
  1193. * Substitute an Excel cell reference in A1 notation for zero based row and
  1194. * column values in an argument list.
  1195. *
  1196. * Ex: ("A4", "Hello") is converted to (3, 0, "Hello").
  1197. *
  1198. * @access private
  1199. * @param string $cell The cell reference. Or range of cells.
  1200. * @return array
  1201. */
  1202. function _substituteCellref($cell)
  1203. {
  1204. $cell = strtoupper($cell);
  1205. // Convert a column range: 'A:A' or 'B:G'
  1206. if (preg_match("/([A-I]?[A-Z]):([A-I]?[A-Z])/", $cell, $match)) {
  1207. list($no_use, $col1) = $this->_cellToRowcol($match[1] .'1'); // Add a dummy row
  1208. list($no_use, $col2) = $this->_cellToRowcol($match[2] .'1'); // Add a dummy row
  1209. return(array($col1, $col2));
  1210. }
  1211. // Convert a cell range: 'A1:B7'
  1212. if (preg_match("/\$?([A-I]?[A-Z]\$?\d+):\$?([A-I]?[A-Z]\$?\d+)/", $cell, $match)) {
  1213. list($row1, $col1) = $this->_cellToRowcol($match[1]);
  1214. list($row2, $col2) = $this->_cellToRowcol($match[2]);
  1215. return(array($row1, $col1, $row2, $col2));
  1216. }
  1217. // Convert a cell reference: 'A1' or 'AD2000'
  1218. if (preg_match("/\$?([A-I]?[A-Z]\$?\d+)/", $cell)) {
  1219. list($row1, $col1) = $this->_cellToRowcol($match[1]);
  1220. return(array($row1, $col1));
  1221. }
  1222. // TODO use real error codes
  1223. throw new Exception("Unknown cell reference $cell");
  1224. }
  1225. /**
  1226. * Convert an Excel cell reference in A1 notation to a zero based row and column
  1227. * reference; converts C1 to (0, 2).
  1228. *
  1229. * @access private
  1230. * @param string $cell The cell reference.
  1231. * @return array containing (row, column)
  1232. */
  1233. function _cellToRowcol($cell)
  1234. {
  1235. preg_match("/\$?([A-I]?[A-Z])\$?(\d+)/",$cell,$match);
  1236. $col = $match[1];
  1237. $row = $match[2];
  1238. // Convert base26 column string to number
  1239. $chars = split('', $col);
  1240. $expn = 0;
  1241. $col = 0;
  1242. while ($chars) {
  1243. $char = array_pop($chars); // LS char first
  1244. $col += (ord($char) -ord('A') +1) * pow(26,$expn);
  1245. ++$expn;
  1246. }
  1247. // Convert 1-index to zero-index
  1248. --$row;
  1249. --$col;
  1250. return(array($row, $col));
  1251. }
  1252. /**
  1253. * Based on the algorithm provided by Daniel Rentz of OpenOffice.
  1254. *
  1255. * @access private
  1256. * @param string $plaintext The password to be encoded in plaintext.
  1257. * @return string The encoded password
  1258. */
  1259. function _encodePassword($plaintext)
  1260. {
  1261. $password = 0x0000;
  1262. $i = 1; // char position
  1263. // split the plain text password in its component characters
  1264. $chars = preg_split('//', $plaintext, -1, PREG_SPLIT_NO_EMPTY);
  1265. foreach ($chars as $char) {
  1266. $value = ord($char) << $i; // shifted ASCII value
  1267. $rotated_bits = $value >> 15; // rotated bits beyond bit 15
  1268. $value &= 0x7fff; // first 15 bits
  1269. $password ^= ($value | $rotated_bits);
  1270. ++$i;
  1271. }
  1272. $password ^= strlen($plaintext);
  1273. $password ^= 0xCE4B;
  1274. return($password);
  1275. }
  1276. /**
  1277. * This method sets the properties for outlining and grouping. The defaults
  1278. * correspond to Excel's defaults.
  1279. *
  1280. * @param bool $visible
  1281. * @param bool $symbols_below
  1282. * @param bool $symbols_right
  1283. * @param bool $auto_style
  1284. */
  1285. function setOutline($visible = true, $symbols_below = true, $symbols_right = true, $auto_style = false)
  1286. {
  1287. $this->_outline_on = $visible;
  1288. $this->_outline_below = $symbols_below;
  1289. $this->_outline_right = $symbols_right;
  1290. $this->_outline_style = $auto_style;
  1291. // Ensure this is a boolean vale for Window2
  1292. if ($this->_outline_on) {
  1293. $this->_outline_on = 1;
  1294. }
  1295. }
  1296. /******************************************************************************
  1297. *******************************************************************************
  1298. *
  1299. * BIFF RECORDS
  1300. */
  1301. /**
  1302. * Write a double to the specified row and column (zero indexed).
  1303. * An integer can be written as a double. Excel will display an
  1304. * integer. $format is optional.
  1305. *
  1306. * Returns 0 : normal termination
  1307. * -2 : row or column out of range
  1308. *
  1309. * @access public
  1310. * @param integer $row Zero indexed row
  1311. * @param integer $col Zero indexed column
  1312. * @param float $num The number to write
  1313. * @param mixed $format The optional XF format
  1314. * @return integer
  1315. */
  1316. function writeNumber($row, $col, $num, $format = null)
  1317. {
  1318. $record = 0x0203; // Record identifier
  1319. $length = 0x000E; // Number of bytes to follow
  1320. $xf = $this->_XF($format); // The cell format
  1321. // Check that row and col are valid and store max and min values
  1322. if ($row >= $this->_xls_rowmax) {
  1323. return(-2);
  1324. }
  1325. if ($col >= $this->_xls_colmax) {
  1326. return(-2);
  1327. }
  1328. if ($row < $this->_dim_rowmin) {
  1329. $this->_dim_rowmin = $row;
  1330. }
  1331. if ($row > $this->_dim_rowmax) {
  1332. $this->_dim_rowmax = $row;
  1333. }
  1334. if ($col < $this->_dim_colmin) {
  1335. $this->_dim_colmin = $col;
  1336. }
  1337. if ($col > $this->_dim_colmax) {
  1338. $this->_dim_colmax = $col;
  1339. }
  1340. $header = pack("vv", $record, $length);
  1341. $data = pack("vvv", $row, $col, $xf);
  1342. $xl_double = pack("d", $num);
  1343. if ($this->_byte_order) { // if it's Big Endian
  1344. $xl_double = strrev($xl_double);
  1345. }
  1346. $this->_append($header.$data.$xl_double);
  1347. return(0);
  1348. }
  1349. /**
  1350. * Write a string to the specified row and column (zero indexed).
  1351. * NOTE: there is an Excel 5 defined limit of 255 characters.
  1352. * $format is optional.
  1353. * Returns 0 : normal termination
  1354. * -2 : row or column out of range
  1355. * -3 : long string truncated to 255 chars
  1356. *
  1357. * @access public
  1358. * @param integer $row Zero indexed row
  1359. * @param integer $col Zero indexed column
  1360. * @param string $str The string to write
  1361. * @param mixed $format The XF format for the cell
  1362. * @return integer
  1363. */
  1364. function writeString($row, $col, $str, $format = null)
  1365. {
  1366. if ($this->_BIFF_version == 0x0600) {
  1367. return $this->writeStringBIFF8($row, $col, $str, $format);
  1368. }
  1369. $strlen = strlen($str);
  1370. $record = 0x0204; // Record identifier
  1371. $length = 0x0008 + $strlen; // Bytes to follow
  1372. $xf = $this->_XF($format); // The cell format
  1373. $str_error = 0;
  1374. // Check that row and col are valid and store max and min values
  1375. if ($row >= $this->_xls_rowmax) {
  1376. return(-2);
  1377. }
  1378. if ($col >= $this->_xls_colmax) {
  1379. return(-2);
  1380. }
  1381. if ($row < $this->_dim_rowmin) {
  1382. $this->_dim_rowmin = $row;
  1383. }
  1384. if ($row > $this->_dim_rowmax) {
  1385. $this->_dim_rowmax = $row;
  1386. }
  1387. if ($col < $this->_dim_colmin) {
  1388. $this->_dim_colmin = $col;
  1389. }
  1390. if ($col > $this->_dim_colmax) {
  1391. $this->_dim_colmax = $col;
  1392. }
  1393. if ($strlen > $this->_xls_strmax) { // LABEL must be < 255 chars
  1394. $str = substr($str, 0, $this->_xls_strmax);
  1395. $length = 0x0008 + $this->_xls_strmax;
  1396. $strlen = $this->_xls_strmax;
  1397. $str_error = -3;
  1398. }
  1399. $header = pack("vv", $record, $length);
  1400. $data = pack("vvvv", $row, $col, $xf, $strlen);
  1401. $this->_append($header . $data . $str);
  1402. return($str_error);
  1403. }
  1404. var $_biff8_input_encoding = 'UTF-16LE';
  1405. function setBIFF8InputEncoding($encoding) {
  1406. if ($encoding != 'UTF-16LE' && !function_exists('iconv')) {
  1407. $this->raiseError("Using an input encoding other than UTF-16LE requires PHP support for iconv");
  1408. }
  1409. $this->_biff8_input_encoding = $encoding;
  1410. }
  1411. /**
  1412. * Sets Input Encoding for writing strings
  1413. *
  1414. * @access public
  1415. * @param string $encoding The encoding. Ex: 'UTF-16LE', 'utf-8', 'ISO-859-7'
  1416. */
  1417. function setInputEncoding($encoding)
  1418. {
  1419. if ($encoding != 'UTF-16LE' && !function_exists('iconv')) {
  1420. throw new Exception("Using an input encoding other than UTF-16LE requires PHP support for iconv");
  1421. }
  1422. $this->_input_encoding = $encoding;
  1423. }
  1424. /**
  1425. * Write a string to the specified row and column (zero indexed).
  1426. * This is the BIFF8 version (no 255 chars limit).
  1427. * $format is optional.
  1428. * Returns 0 : normal termination
  1429. * -2 : row or column out of range
  1430. * -3 : long string truncated to 255 chars
  1431. *
  1432. * @access public
  1433. * @param integer $row Zero indexed row
  1434. * @param integer $col Zero indexed column
  1435. * @param string $str The string to write
  1436. * @param mixed $format The XF format for the cell
  1437. * @return integer
  1438. */
  1439. function writeStringBIFF8($row, $col, $str, $format = null)
  1440. {
  1441. if ($this->_input_encoding == 'UTF-16LE')
  1442. {
  1443. $strlen = function_exists('mb_strlen') ? mb_strlen($str, 'UTF-16LE') : (strlen($str) / 2);
  1444. $encoding = 0x1;
  1445. }
  1446. elseif ($this->_input_encoding != '')
  1447. {
  1448. $str = iconv($this->_input_encoding, 'UTF-16LE', $str);
  1449. $strlen = function_exists('mb_strlen') ? mb_strlen($str, 'UTF-16LE') : (strlen($str) / 2);
  1450. $encoding = 0x1;
  1451. }
  1452. else
  1453. {
  1454. $strlen = strlen($str);
  1455. $encoding = 0x0;
  1456. }
  1457. $record = 0x00FD; // Record identifier
  1458. $length = 0x000A; // Bytes to follow
  1459. $xf = $this->_XF($format); // The cell format
  1460. $str_error = 0;
  1461. // Check that row and col are valid and store max and min values
  1462. if ($this->_checkRowCol($row, $col) == false) {
  1463. return -2;
  1464. }
  1465. $str = pack('vC', $strlen, $encoding).$str;
  1466. /* check if string is already present */
  1467. if (!isset($this->_str_table[$str])) {
  1468. $this->_str_table[$str] = $this->_str_unique++;
  1469. }
  1470. $this->_str_total++;
  1471. $header = pack('vv', $record, $length);
  1472. $data = pack('vvvV', $row, $col, $xf, $this->_str_table[$str]);
  1473. $this->_append($header.$data);
  1474. return $str_error;
  1475. }
  1476. /**
  1477. * Check row and col before writing to a cell, and update the sheet's
  1478. * dimensions accordingly
  1479. *
  1480. * @access private
  1481. * @param integer $row Zero indexed row
  1482. * @param integer $col Zero indexed column
  1483. * @return boolean true for success, false if row and/or col are grester
  1484. * then maximums allowed.
  1485. */
  1486. function _checkRowCol($row, $col)
  1487. {
  1488. if ($row >= $this->_xls_rowmax) {
  1489. return false;
  1490. }
  1491. if ($col >= $this->_xls_colmax) {
  1492. return false;
  1493. }
  1494. if ($row < $this->_dim_rowmin) {
  1495. $this->_dim_rowmin = $row;
  1496. }
  1497. if ($row > $this->_dim_rowmax) {
  1498. $this->_dim_rowmax = $row;
  1499. }
  1500. if ($col < $this->_dim_colmin) {
  1501. $this->_dim_colmin = $col;
  1502. }
  1503. if ($col > $this->_dim_colmax) {
  1504. $this->_dim_colmax = $col;
  1505. }
  1506. return true;
  1507. }
  1508. /**
  1509. * Writes a note associated with the cell given by the row and column.
  1510. * NOTE records don't have a length limit.
  1511. *
  1512. * @access public
  1513. * @param integer $row Zero indexed row
  1514. * @param integer $col Zero indexed column
  1515. * @param string $note The note to write
  1516. */
  1517. function writeNote($row, $col, $note)
  1518. {
  1519. $note_length = strlen($note);
  1520. $record = 0x001C; // Record identifier
  1521. $max_length = 2048; // Maximun length for a NOTE record
  1522. //$length = 0x0006 + $note_length; // Bytes to follow
  1523. // Check that row and col are valid and store max and min values
  1524. if ($row >= $this->_xls_rowmax) {
  1525. return(-2);
  1526. }
  1527. if ($col >= $this->_xls_colmax) {
  1528. return(-2);
  1529. }
  1530. if ($row < $this->_dim_rowmin) {
  1531. $this->_dim_rowmin = $row;
  1532. }
  1533. if ($row > $this->_dim_rowmax) {
  1534. $this->_dim_rowmax = $row;
  1535. }
  1536. if ($col < $this->_dim_colmin) {
  1537. $this->_dim_colmin = $col;
  1538. }
  1539. if ($col > $this->_dim_colmax) {
  1540. $this->_dim_colmax = $col;
  1541. }
  1542. // Length for this record is no more than 2048 + 6
  1543. $length = 0x0006 + min($note_length, 2048);
  1544. $header = pack("vv", $record, $length);
  1545. $data = pack("vvv", $row, $col, $note_length);
  1546. $this->_append($header . $data . substr($note, 0, 2048));
  1547. for ($i = $max_length; $i < $note_length; $i += $max_length) {
  1548. $chunk = substr($note, $i, $max_length);
  1549. $length = 0x0006 + strlen($chunk);
  1550. $header = pack("vv", $record, $length);
  1551. $data = pack("vvv", -1, 0, strlen($chunk));
  1552. $this->_append($header.$data.$chunk);
  1553. }
  1554. return(0);
  1555. }
  1556. /**
  1557. * Write a blank cell to the specified row and column (zero indexed).
  1558. * A blank cell is used to specify formatting without adding a string
  1559. * or a number.
  1560. *
  1561. * A blank cell without a format serves no purpose. Therefore, we don't write
  1562. * a BLANK record unless a format is specified.
  1563. *
  1564. * Returns 0 : normal termination (including no format)
  1565. * -1 : insufficient number of arguments
  1566. * -2 : row or column out of range
  1567. *
  1568. * @access public
  1569. * @param integer $row Zero indexed row
  1570. * @param integer $col Zero indexed column
  1571. * @param mixed $format The XF format
  1572. */
  1573. function writeBlank($row, $col, $format)
  1574. {
  1575. // Don't write a blank cell unless it has a format
  1576. if (!$format) {
  1577. return(0);
  1578. }
  1579. $record = 0x0201; // Record identifier
  1580. $length = 0x0006; // Number of bytes to follow
  1581. $xf = $this->_XF($format); // The cell format
  1582. // Check that row and col are valid and store max and min values
  1583. if ($row >= $this->_xls_rowmax) {
  1584. return(-2);
  1585. }
  1586. if ($col >= $this->_xls_colmax) {
  1587. return(-2);
  1588. }
  1589. if ($row < $this->_dim_rowmin) {
  1590. $this->_dim_rowmin = $row;
  1591. }
  1592. if ($row > $this->_dim_rowmax) {
  1593. $this->_dim_rowmax = $row;
  1594. }
  1595. if ($col < $this->_dim_colmin) {
  1596. $this->_dim_colmin = $col;
  1597. }
  1598. if ($col > $this->_dim_colmax) {
  1599. $this->_dim_colmax = $col;
  1600. }
  1601. $header = pack("vv", $record, $length);
  1602. $data = pack("vvv", $row, $col, $xf);
  1603. $this->_append($header . $data);
  1604. return 0;
  1605. }
  1606. /**
  1607. * Write a boolean or an error type to the specified row and column (zero indexed)
  1608. */
  1609. public function writeBoolErr($row, $col, $value, $isError, $format)
  1610. {
  1611. $record = 0x0205;
  1612. $length = 8;
  1613. $xf = $this->_XF($format);
  1614. // Check that row and col are valid and store max and min values
  1615. if ($row >= $this->_xls_rowmax) {
  1616. return(-2);
  1617. }
  1618. if ($col >= $this->_xls_colmax) {
  1619. return(-2);
  1620. }
  1621. if ($row < $this->_dim_rowmin) {
  1622. $this->_dim_rowmin = $row;
  1623. }
  1624. if ($row > $this->_dim_rowmax) {
  1625. $this->_dim_rowmax = $row;
  1626. }
  1627. if ($col < $this->_dim_colmin) {
  1628. $this->_dim_colmin = $col;
  1629. }
  1630. if ($col > $this->_dim_colmax) {
  1631. $this->_dim_colmax = $col;
  1632. }
  1633. $header = pack("vv", $record, $length);
  1634. $data = pack("vvvCC", $row, $col, $xf, $value, $isError);
  1635. $this->_append($header . $data);
  1636. return 0;
  1637. }
  1638. /**
  1639. * Write a formula to the specified row and column (zero indexed).
  1640. * The textual representation of the formula is passed to the parser in
  1641. * Parser.php which returns a packed binary string.
  1642. *
  1643. * Returns 0 : normal termination
  1644. * -1 : formula errors (bad formula)
  1645. * -2 : row or column out of range
  1646. *
  1647. * @access public
  1648. * @param integer $row Zero indexed row
  1649. * @param integer $col Zero indexed column
  1650. * @param string $formula The formula text string
  1651. * @param mixed $format The optional XF format
  1652. * @return integer
  1653. */
  1654. function writeFormula($row, $col, $formula, $format = null)
  1655. {
  1656. $record = 0x0006; // Record identifier
  1657. // Excel normally stores the last calculated value of the formula in $num.
  1658. // Clearly we are not in a position to calculate this a priori. Instead
  1659. // we set $num to zero and set the option flags in $grbit to ensure
  1660. // automatic calculation of the formula when the file is opened.
  1661. //
  1662. $xf = $this->_XF($format); // The cell format
  1663. $num = 0x00; // Current value of formula
  1664. $grbit = 0x03; // Option flags
  1665. $unknown = 0x0000; // Must be zero
  1666. // Check that row and col are valid and store max and min values
  1667. if ($this->_checkRowCol($row, $col) == false) {
  1668. return -2;
  1669. }
  1670. // Strip the '=' or '@' sign at the beginning of the formula string
  1671. if (preg_match("/^=/", $formula)) {
  1672. $formula = preg_replace("/(^=)/", "", $formula);
  1673. } elseif (preg_match("/^@/", $formula)) {
  1674. $formula = preg_replace("/(^@)/", "", $formula);
  1675. } else {
  1676. // Error handling
  1677. $this->writeString($row, $col, 'Unrecognised character for formula');
  1678. return -1;
  1679. }
  1680. // Parse the formula using the parser in Parser.php
  1681. $error = $this->_parser->parse($formula);
  1682. $formula = $this->_parser->toReversePolish();
  1683. $formlen = strlen($formula); // Length of the binary string
  1684. $length = 0x16 + $formlen; // Length of the record data
  1685. $header = pack("vv", $record, $length);
  1686. $data = pack("vvvdvVv", $row, $col, $xf, $num,
  1687. $grbit, $unknown, $formlen);
  1688. $this->_append($header . $data . $formula);
  1689. return 0;
  1690. }
  1691. /**
  1692. * Write a hyperlink.
  1693. * This is comprised of two elements: the visible label and
  1694. * the invisible link. The visible label is the same as the link unless an
  1695. * alternative string is specified. The label is written using the
  1696. * writeString() method. Therefore the 255 characters string limit applies.
  1697. * $string and $format are optional.
  1698. *
  1699. * The hyperlink can be to a http, ftp, mail, internal sheet (not yet), or external
  1700. * directory url.
  1701. *
  1702. * Returns 0 : normal termination
  1703. * -2 : row or column out of range
  1704. * -3 : long string truncated to 255 chars
  1705. *
  1706. * @access public
  1707. * @param integer $row Row
  1708. * @param integer $col Column
  1709. * @param string $url URL string
  1710. * @return integer
  1711. */
  1712. function writeUrl($row, $col, $url)
  1713. {
  1714. // Add start row and col to arg list
  1715. return($this->_writeUrlRange($row, $col, $row, $col, $url));
  1716. }
  1717. /**
  1718. * This is the more general form of writeUrl(). It allows a hyperlink to be
  1719. * written to a range of cells. This function also decides the type of hyperlink
  1720. * to be written. These are either, Web (http, ftp, mailto), Internal
  1721. * (Sheet1!A1) or external ('c:\temp\foo.xls#Sheet1!A1').
  1722. *
  1723. * @access private
  1724. * @see writeUrl()
  1725. * @param integer $row1 Start row
  1726. * @param integer $col1 Start column
  1727. * @param integer $row2 End row
  1728. * @param integer $col2 End column
  1729. * @param string $url URL string
  1730. * @return integer
  1731. */
  1732. function _writeUrlRange($row1, $col1, $row2, $col2, $url)
  1733. {
  1734. // Check for internal/external sheet links or default to web link
  1735. if (preg_match('[^internal:]', $url)) {
  1736. return($this->_writeUrlInternal($row1, $col1, $row2, $col2, $url));
  1737. }
  1738. if (preg_match('[^external:]', $url)) {
  1739. return($this->_writeUrlExternal($row1, $col1, $row2, $col2, $url));
  1740. }
  1741. return($this->_writeUrlWeb($row1, $col1, $row2, $col2, $url));
  1742. }
  1743. /**
  1744. * Used to write http, ftp and mailto hyperlinks.
  1745. * The link type ($options) is 0x03 is the same as absolute dir ref without
  1746. * sheet. However it is differentiated by the $unknown2 data stream.
  1747. *
  1748. * @access private
  1749. * @see writeUrl()
  1750. * @param integer $row1 Start row
  1751. * @param integer $col1 Start column
  1752. * @param integer $row2 End row
  1753. * @param integer $col2 End column
  1754. * @param string $url URL string
  1755. * @return integer
  1756. */
  1757. function _writeUrlWeb($row1, $col1, $row2, $col2, $url)
  1758. {
  1759. $record = 0x01B8; // Record identifier
  1760. $length = 0x00000; // Bytes to follow
  1761. // Pack the undocumented parts of the hyperlink stream
  1762. $unknown1 = pack("H*", "D0C9EA79F9BACE118C8200AA004BA90B02000000");
  1763. $unknown2 = pack("H*", "E0C9EA79F9BACE118C8200AA004BA90B");
  1764. // Pack the option flags
  1765. $options = pack("V", 0x03);
  1766. // Convert URL to a null terminated wchar string
  1767. $url = join("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));
  1768. $url = $url . "\0\0\0";
  1769. // Pack the length of the URL
  1770. $url_len = pack("V", strlen($url));
  1771. // Calculate the data length
  1772. $length = 0x34 + strlen($url);
  1773. // Pack the header data
  1774. $header = pack("vv", $record, $length);
  1775. $data = pack("vvvv", $row1, $row2, $col1, $col2);
  1776. // Write the packed data
  1777. $this->_append($header . $data .
  1778. $unknown1 . $options .
  1779. $unknown2 . $url_len . $url);
  1780. return 0;
  1781. }
  1782. /**
  1783. * Used to write internal reference hyperlinks such as "Sheet1!A1".
  1784. *
  1785. * @access private
  1786. * @see writeUrl()
  1787. * @param integer $row1 Start row
  1788. * @param integer $col1 Start column
  1789. * @param integer $row2 End row
  1790. * @param integer $col2 End column
  1791. * @param string $url URL string
  1792. * @return integer
  1793. */
  1794. function _writeUrlInternal($row1, $col1, $row2, $col2, $url)
  1795. {
  1796. $record = 0x01B8; // Record identifier
  1797. $length = 0x00000; // Bytes to follow
  1798. // Strip URL type
  1799. $url = preg_replace('/^internal:/', '', $url);
  1800. // Pack the undocumented parts of the hyperlink stream
  1801. $unknown1 = pack("H*", "D0C9EA79F9BACE118C8200AA004BA90B02000000");
  1802. // Pack the option flags
  1803. $options = pack("V", 0x08);
  1804. // Convert the URL type and to a null terminated wchar string
  1805. $url = join("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));
  1806. $url = $url . "\0\0\0";
  1807. // Pack the length of the URL as chars (not wchars)
  1808. $url_len = pack("V", floor(strlen($url)/2));
  1809. // Calculate the data length
  1810. $length = 0x24 + strlen($url);
  1811. // Pack the header data
  1812. $header = pack("vv", $record, $length);
  1813. $data = pack("vvvv", $row1, $row2, $col1, $col2);
  1814. // Write the packed data
  1815. $this->_append($header . $data .
  1816. $unknown1 . $options .
  1817. $url_len . $url);
  1818. return 0;
  1819. }
  1820. /**
  1821. * Write links to external directory names such as 'c:\foo.xls',
  1822. * c:\foo.xls#Sheet1!A1', '../../foo.xls'. and '../../foo.xls#Sheet1!A1'.
  1823. *
  1824. * Note: Excel writes some relative links with the $dir_long string. We ignore
  1825. * these cases for the sake of simpler code.
  1826. *
  1827. * @access private
  1828. * @see writeUrl()
  1829. * @param integer $row1 Start row
  1830. * @param integer $col1 Start column
  1831. * @param integer $row2 End row
  1832. * @param integer $col2 End column
  1833. * @param string $url URL string
  1834. * @return integer
  1835. */
  1836. function _writeUrlExternal($row1, $col1, $row2, $col2, $url)
  1837. {
  1838. // Network drives are different. We will handle them separately
  1839. // MS/Novell network drives and shares start with \\
  1840. if (preg_match('[^external:\\\\]', $url)) {
  1841. return; //($this->_writeUrlExternal_net($row1, $col1, $row2, $col2, $url, $str, $format));
  1842. }
  1843. $record = 0x01B8; // Record identifier
  1844. $length = 0x00000; // Bytes to follow
  1845. // Strip URL type and change Unix dir separator to Dos style (if needed)
  1846. //
  1847. $url = preg_replace('/^external:/', '', $url);
  1848. $url = preg_replace('/\//', "\\", $url);
  1849. // Determine if the link is relative or absolute:
  1850. // relative if link contains no dir separator, "somefile.xls"
  1851. // relative if link starts with up-dir, "..\..\somefile.xls"
  1852. // otherwise, absolute
  1853. $absolute = 0x02; // Bit mask
  1854. if (!preg_match("/\\\/", $url)) {
  1855. $absolute = 0x00;
  1856. }
  1857. if (preg_match("/^\.\.\\\/", $url)) {
  1858. $absolute = 0x00;
  1859. }
  1860. $link_type = 0x01 | $absolute;
  1861. // Determine if the link contains a sheet reference and change some of the
  1862. // parameters accordingly.
  1863. // Split the dir name and sheet name (if it exists)
  1864. /*if (preg_match("/\#/", $url)) {
  1865. list($dir_long, $sheet) = split("\#", $url);
  1866. } else {
  1867. $dir_long = $url;
  1868. }
  1869. if (isset($sheet)) {
  1870. $link_type |= 0x08;
  1871. $sheet_len = pack("V", strlen($sheet) + 0x01);
  1872. $sheet = join("\0", split('', $sheet));
  1873. $sheet .= "\0\0\0";
  1874. } else {
  1875. $sheet_len = '';
  1876. $sheet = '';
  1877. }*/
  1878. $dir_long = $url;
  1879. if (preg_match("/\#/", $url)) {
  1880. $link_type |= 0x08;
  1881. }
  1882. // Pack the link type
  1883. $link_type = pack("V", $link_type);
  1884. // Calculate the up-level dir count e.g.. (..\..\..\ == 3)
  1885. $up_count = preg_match_all("/\.\.\\\/", $dir_long, $useless);
  1886. $up_count = pack("v", $up_count);
  1887. // Store the short dos dir name (null terminated)
  1888. $dir_short = preg_replace("/\.\.\\\/", '', $dir_long) . "\0";
  1889. // Store the long dir name as a wchar string (non-null terminated)
  1890. //$dir_long = join("\0", split('', $dir_long));
  1891. $dir_long = $dir_long . "\0";
  1892. // Pack the lengths of the dir strings
  1893. $dir_short_len = pack("V", strlen($dir_short) );
  1894. $dir_long_len = pack("V", strlen($dir_long) );
  1895. $stream_len = pack("V", 0);//strlen($dir_long) + 0x06);
  1896. // Pack the undocumented parts of the hyperlink stream
  1897. $unknown1 = pack("H*",'D0C9EA79F9BACE118C8200AA004BA90B02000000' );
  1898. $unknown2 = pack("H*",'0303000000000000C000000000000046' );
  1899. $unknown3 = pack("H*",'FFFFADDE000000000000000000000000000000000000000');
  1900. $unknown4 = pack("v", 0x03 );
  1901. // Pack the main data stream
  1902. $data = pack("vvvv", $row1, $row2, $col1, $col2) .
  1903. $unknown1 .
  1904. $link_type .
  1905. $unknown2 .
  1906. $up_count .
  1907. $dir_short_len.
  1908. $dir_short .
  1909. $unknown3 .
  1910. $stream_len ;/*.
  1911. $dir_long_len .
  1912. $unknown4 .
  1913. $dir_long .
  1914. $sheet_len .
  1915. $sheet ;*/
  1916. // Pack the header data
  1917. $length = strlen($data);
  1918. $header = pack("vv", $record, $length);
  1919. // Write the packed data
  1920. $this->_append($header. $data);
  1921. return 0;
  1922. }
  1923. /**
  1924. * Set the default column (character) width
  1925. *
  1926. * @param integer $width
  1927. */
  1928. public function setDefColWidth($width)
  1929. {
  1930. $this->_defColWidth = $width;
  1931. }
  1932. /**
  1933. * Set the default row height in twips = 1/20 of a point
  1934. */
  1935. public function setDefaultRowHeight($height)
  1936. {
  1937. $this->_defaultRowHeight = $height;
  1938. }
  1939. /**
  1940. * This method is used to set the height and format for a row.
  1941. *
  1942. * @access public
  1943. * @param integer $row The row to set
  1944. * @param integer $height Height we are giving to the row.
  1945. * Use null to set XF without setting height
  1946. * @param mixed $format XF format we are giving to the row
  1947. * @param bool $hidden The optional hidden attribute
  1948. * @param integer $level The optional outline level for row, in range [0,7]
  1949. */
  1950. function setRow($row, $height, $format = null, $hidden = false, $level = 0)
  1951. {
  1952. $record = 0x0208; // Record identifier
  1953. $length = 0x0010; // Number of bytes to follow
  1954. $colMic = 0x0000; // First defined column
  1955. $colMac = 0x0000; // Last defined column
  1956. $irwMac = 0x0000; // Used by Excel to optimise loading
  1957. $reserved = 0x0000; // Reserved
  1958. $grbit = 0x0000; // Option flags
  1959. $ixfe = $this->_XF($format); // XF index
  1960. if ( $height < 0 ){
  1961. $height = null;
  1962. }
  1963. // set _row_sizes so _sizeRow() can use it
  1964. $this->_row_sizes[$row] = $height;
  1965. // Use setRow($row, null, $XF) to set XF format without setting height
  1966. if ($height != null) {
  1967. $miyRw = $height * 20; // row height
  1968. } else {
  1969. $miyRw = 0xff; // default row height is 256
  1970. }
  1971. $level = max(0, min($level, 7)); // level should be between 0 and 7
  1972. $this->_outline_row_level = max($level, $this->_outline_row_level);
  1973. // Set the options flags. fUnsynced is used to show that the font and row
  1974. // heights are not compatible. This is usually the case for WriteExcel.
  1975. // The collapsed flag 0x10 doesn't seem to be used to indicate that a row
  1976. // is collapsed. Instead it is used to indicate that the previous row is
  1977. // collapsed. The zero height flag, 0x20, is used to collapse a row.
  1978. $grbit |= $level;
  1979. if ($hidden) {
  1980. $grbit |= 0x0020;
  1981. }
  1982. $grbit |= 0x0040; // fUnsynced
  1983. if ($format) {
  1984. $grbit |= 0x0080;
  1985. }
  1986. $grbit |= 0x0100;
  1987. $header = pack("vv", $record, $length);
  1988. $data = pack("vvvvvvvv", $row, $colMic, $colMac, $miyRw,
  1989. $irwMac,$reserved, $grbit, $ixfe);
  1990. $this->_append($header.$data);
  1991. }
  1992. /**
  1993. * Writes Excel DIMENSIONS to define the area in which there is data.
  1994. *
  1995. * @access private
  1996. */
  1997. function _storeDimensions()
  1998. {
  1999. $record = 0x0200; // Record identifier
  2000. $row_min = $this->_dim_rowmin; // First row
  2001. $row_max = $this->_dim_rowmax + 1; // Last row plus 1
  2002. $col_min = $this->_dim_colmin; // First column
  2003. $col_max = $this->_dim_colmax + 1; // Last column plus 1
  2004. $reserved = 0x0000; // Reserved by Excel
  2005. if ($this->_BIFF_version == 0x0500) {
  2006. $length = 0x000A; // Number of bytes to follow
  2007. $data = pack("vvvvv", $row_min, $row_max,
  2008. $col_min, $col_max, $reserved);
  2009. } elseif ($this->_BIFF_version == 0x0600) {
  2010. $length = 0x000E;
  2011. //$data = pack("VVvvv", $row_min, $row_max,
  2012. // $col_min, $col_max, $reserved);
  2013. $data = pack("VVvvv", $this->_firstRowIndex, $this->_lastRowIndex + 1,
  2014. $this->_firstColumnIndex, $this->_lastColumnIndex + 1, $reserved);
  2015. }
  2016. $header = pack("vv", $record, $length);
  2017. $this->_prepend($header.$data);
  2018. }
  2019. /**
  2020. * Write BIFF record Window2.
  2021. *
  2022. * @access private
  2023. */
  2024. function _storeWindow2()
  2025. {
  2026. $record = 0x023E; // Record identifier
  2027. if ($this->_BIFF_version == 0x0500) {
  2028. $length = 0x000A; // Number of bytes to follow
  2029. } elseif ($this->_BIFF_version == 0x0600) {
  2030. $length = 0x0012;
  2031. }
  2032. $grbit = 0x00B6; // Option flags
  2033. $rwTop = 0x0000; // Top row visible in window
  2034. $colLeft = 0x0000; // Leftmost column visible in window
  2035. // The options flags that comprise $grbit
  2036. $fDspFmla = 0; // 0 - bit
  2037. $fDspGrid = $this->_screen_gridlines; // 1
  2038. $fDspRwCol = 1; // 2
  2039. $fFrozen = $this->_frozen; // 3
  2040. $fDspZeros = 1; // 4
  2041. $fDefaultHdr = 1; // 5
  2042. $fArabic = 0; // 6
  2043. $fDspGuts = $this->_outline_on; // 7
  2044. $fFrozenNoSplit = 0; // 0 - bit
  2045. $fSelected = $this->selected; // 1
  2046. $fPaged = 1; // 2
  2047. $grbit = $fDspFmla;
  2048. $grbit |= $fDspGrid << 1;
  2049. $grbit |= $fDspRwCol << 2;
  2050. $grbit |= $fFrozen << 3;
  2051. $grbit |= $fDspZeros << 4;
  2052. $grbit |= $fDefaultHdr << 5;
  2053. $grbit |= $fArabic << 6;
  2054. $grbit |= $fDspGuts << 7;
  2055. $grbit |= $fFrozenNoSplit << 8;
  2056. $grbit |= $fSelected << 9;
  2057. $grbit |= $fPaged << 10;
  2058. $header = pack("vv", $record, $length);
  2059. $data = pack("vvv", $grbit, $rwTop, $colLeft);
  2060. // FIXME !!!
  2061. if ($this->_BIFF_version == 0x0500) {
  2062. $rgbHdr = 0x00000000; // Row/column heading and gridline color
  2063. $data .= pack("V", $rgbHdr);
  2064. } elseif ($this->_BIFF_version == 0x0600) {
  2065. $rgbHdr = 0x0040; // Row/column heading and gridline color index
  2066. $zoom_factor_page_break = 0x0000;
  2067. $zoom_factor_normal = 0x0000;
  2068. $data .= pack("vvvvV", $rgbHdr, 0x0000, $zoom_factor_page_break, $zoom_factor_normal, 0x00000000);
  2069. }
  2070. $this->_append($header.$data);
  2071. }
  2072. /**
  2073. * Write BIFF record DEFAULTROWHEIGHT.
  2074. *
  2075. * @access private
  2076. */
  2077. private function _storeDefaultRowHeight()
  2078. {
  2079. if (isset($this->_defaultRowHeight)) {
  2080. $record = 0x0225; // Record identifier
  2081. $length = 0x0004; // Number of bytes to follow
  2082. $header = pack("vv", $record, $length);
  2083. $data = pack("vv", 1, $this->_defaultRowHeight);
  2084. $this->_prepend($header . $data);
  2085. }
  2086. }
  2087. /**
  2088. * Write BIFF record DEFCOLWIDTH if COLINFO records are in use.
  2089. *
  2090. * @access private
  2091. */
  2092. function _storeDefcol()
  2093. {
  2094. $record = 0x0055; // Record identifier
  2095. $length = 0x0002; // Number of bytes to follow
  2096. //$colwidth = 0x0008; // Default column width
  2097. $header = pack("vv", $record, $length);
  2098. //$data = pack("v", $colwidth);
  2099. $data = pack("v", $this->_defColWidth);
  2100. $this->_prepend($header . $data);
  2101. }
  2102. /**
  2103. * Write BIFF record COLINFO to define column widths
  2104. *
  2105. * Note: The SDK says the record length is 0x0B but Excel writes a 0x0C
  2106. * length record.
  2107. *
  2108. * @access private
  2109. * @param array $col_array This is the only parameter received and is composed of the following:
  2110. * 0 => First formatted column,
  2111. * 1 => Last formatted column,
  2112. * 2 => Col width (8.43 is Excel default),
  2113. * 3 => The optional XF format of the column,
  2114. * 4 => Option flags.
  2115. * 5 => Optional outline level
  2116. */
  2117. function _storeColinfo($col_array)
  2118. {
  2119. if (isset($col_array[0])) {
  2120. $colFirst = $col_array[0];
  2121. }
  2122. if (isset($col_array[1])) {
  2123. $colLast = $col_array[1];
  2124. }
  2125. if (isset($col_array[2])) {
  2126. $coldx = $col_array[2];
  2127. } else {
  2128. $coldx = 8.43;
  2129. }
  2130. if (isset($col_array[3])) {
  2131. $format = $col_array[3];
  2132. } else {
  2133. $format = 0;
  2134. }
  2135. if (isset($col_array[4])) {
  2136. $grbit = $col_array[4];
  2137. } else {
  2138. $grbit = 0;
  2139. }
  2140. if (isset($col_array[5])) {
  2141. $level = $col_array[5];
  2142. } else {
  2143. $level = 0;
  2144. }
  2145. $record = 0x007D; // Record identifier
  2146. $length = 0x000B; // Number of bytes to follow
  2147. $coldx += 0.72; // Fudge. Excel subtracts 0.72 !?
  2148. $coldx *= 256; // Convert to units of 1/256 of a char
  2149. $ixfe = $this->_XF($format);
  2150. $reserved = 0x00; // Reserved
  2151. $level = max(0, min($level, 7));
  2152. $grbit |= $level << 8;
  2153. $header = pack("vv", $record, $length);
  2154. $data = pack("vvvvvC", $colFirst, $colLast, $coldx,
  2155. $ixfe, $grbit, $reserved);
  2156. $this->_prepend($header.$data);
  2157. }
  2158. /**
  2159. * Write BIFF record SELECTION.
  2160. *
  2161. * @access private
  2162. * @param array $array array containing ($rwFirst,$colFirst,$rwLast,$colLast)
  2163. * @see setSelection()
  2164. */
  2165. function _storeSelection($array)
  2166. {
  2167. list($rwFirst,$colFirst,$rwLast,$colLast) = $array;
  2168. $record = 0x001D; // Record identifier
  2169. $length = 0x000F; // Number of bytes to follow
  2170. $pnn = $this->_active_pane; // Pane position
  2171. $rwAct = $rwFirst; // Active row
  2172. $colAct = $colFirst; // Active column
  2173. $irefAct = 0; // Active cell ref
  2174. $cref = 1; // Number of refs
  2175. if (!isset($rwLast)) {
  2176. $rwLast = $rwFirst; // Last row in reference
  2177. }
  2178. if (!isset($colLast)) {
  2179. $colLast = $colFirst; // Last col in reference
  2180. }
  2181. // Swap last row/col for first row/col as necessary
  2182. if ($rwFirst > $rwLast) {
  2183. list($rwFirst, $rwLast) = array($rwLast, $rwFirst);
  2184. }
  2185. if ($colFirst > $colLast) {
  2186. list($colFirst, $colLast) = array($colLast, $colFirst);
  2187. }
  2188. $header = pack("vv", $record, $length);
  2189. $data = pack("CvvvvvvCC", $pnn, $rwAct, $colAct,
  2190. $irefAct, $cref,
  2191. $rwFirst, $rwLast,
  2192. $colFirst, $colLast);
  2193. $this->_append($header . $data);
  2194. }
  2195. /**
  2196. * Store the MERGEDCELLS record for all ranges of merged cells
  2197. *
  2198. * @access private
  2199. */
  2200. function _storeMergedCells()
  2201. {
  2202. // if there are no merged cell ranges set, return
  2203. if (count($this->_merged_ranges) == 0) {
  2204. return;
  2205. }
  2206. $record = 0x00E5;
  2207. $length = 2 + count($this->_merged_ranges) * 8;
  2208. $header = pack('vv', $record, $length);
  2209. $data = pack('v', count($this->_merged_ranges));
  2210. foreach ($this->_merged_ranges as $range) {
  2211. $data .= pack('vvvv', $range[0], $range[2], $range[1], $range[3]);
  2212. }
  2213. $this->_append($header . $data);
  2214. }
  2215. /**
  2216. * Write BIFF record EXTERNCOUNT to indicate the number of external sheet
  2217. * references in a worksheet.
  2218. *
  2219. * Excel only stores references to external sheets that are used in formulas.
  2220. * For simplicity we store references to all the sheets in the workbook
  2221. * regardless of whether they are used or not. This reduces the overall
  2222. * complexity and eliminates the need for a two way dialogue between the formula
  2223. * parser the worksheet objects.
  2224. *
  2225. * @access private
  2226. * @param integer $count The number of external sheet references in this worksheet
  2227. */
  2228. function _storeExterncount($count)
  2229. {
  2230. $record = 0x0016; // Record identifier
  2231. $length = 0x0002; // Number of bytes to follow
  2232. $header = pack("vv", $record, $length);
  2233. $data = pack("v", $count);
  2234. $this->_prepend($header . $data);
  2235. }
  2236. /**
  2237. * Writes the Excel BIFF EXTERNSHEET record. These references are used by
  2238. * formulas. A formula references a sheet name via an index. Since we store a
  2239. * reference to all of the external worksheets the EXTERNSHEET index is the same
  2240. * as the worksheet index.
  2241. *
  2242. * @access private
  2243. * @param string $sheetname The name of a external worksheet
  2244. */
  2245. function _storeExternsheet($sheetname)
  2246. {
  2247. $record = 0x0017; // Record identifier
  2248. // References to the current sheet are encoded differently to references to
  2249. // external sheets.
  2250. //
  2251. if ($this->name == $sheetname) {
  2252. $sheetname = '';
  2253. $length = 0x02; // The following 2 bytes
  2254. $cch = 1; // The following byte
  2255. $rgch = 0x02; // Self reference
  2256. } else {
  2257. $length = 0x02 + strlen($sheetname);
  2258. $cch = strlen($sheetname);
  2259. $rgch = 0x03; // Reference to a sheet in the current workbook
  2260. }
  2261. $header = pack("vv", $record, $length);
  2262. $data = pack("CC", $cch, $rgch);
  2263. $this->_prepend($header . $data . $sheetname);
  2264. }
  2265. /**
  2266. * Writes the Excel BIFF PANE record.
  2267. * The panes can either be frozen or thawed (unfrozen).
  2268. * Frozen panes are specified in terms of an integer number of rows and columns.
  2269. * Thawed panes are specified in terms of Excel's units for rows and columns.
  2270. *
  2271. * @access private
  2272. * @param array $panes This is the only parameter received and is composed of the following:
  2273. * 0 => Vertical split position,
  2274. * 1 => Horizontal split position
  2275. * 2 => Top row visible
  2276. * 3 => Leftmost column visible
  2277. * 4 => Active pane
  2278. */
  2279. function _storePanes($panes)
  2280. {
  2281. $y = isset($panes[0]) ? $panes[0] : null;
  2282. $x = isset($panes[1]) ? $panes[1] : null;
  2283. $rwTop = isset($panes[2]) ? $panes[2] : null;
  2284. $colLeft = isset($panes[3]) ? $panes[3] : null;
  2285. if (count($panes) > 4) { // if Active pane was received
  2286. $pnnAct = $panes[4];
  2287. } else {
  2288. $pnnAct = null;
  2289. }
  2290. $record = 0x0041; // Record identifier
  2291. $length = 0x000A; // Number of bytes to follow
  2292. // Code specific to frozen or thawed panes.
  2293. if ($this->_frozen) {
  2294. // Set default values for $rwTop and $colLeft
  2295. if (!isset($rwTop)) {
  2296. $rwTop = $y;
  2297. }
  2298. if (!isset($colLeft)) {
  2299. $colLeft = $x;
  2300. }
  2301. } else {
  2302. // Set default values for $rwTop and $colLeft
  2303. if (!isset($rwTop)) {
  2304. $rwTop = 0;
  2305. }
  2306. if (!isset($colLeft)) {
  2307. $colLeft = 0;
  2308. }
  2309. // Convert Excel's row and column units to the internal units.
  2310. // The default row height is 12.75
  2311. // The default column width is 8.43
  2312. // The following slope and intersection values were interpolated.
  2313. //
  2314. $y = 20*$y + 255;
  2315. $x = 113.879*$x + 390;
  2316. }
  2317. // Determine which pane should be active. There is also the undocumented
  2318. // option to override this should it be necessary: may be removed later.
  2319. //
  2320. if (!isset($pnnAct)) {
  2321. if ($x != 0 && $y != 0) {
  2322. $pnnAct = 0; // Bottom right
  2323. }
  2324. if ($x != 0 && $y == 0) {
  2325. $pnnAct = 1; // Top right
  2326. }
  2327. if ($x == 0 && $y != 0) {
  2328. $pnnAct = 2; // Bottom left
  2329. }
  2330. if ($x == 0 && $y == 0) {
  2331. $pnnAct = 3; // Top left
  2332. }
  2333. }
  2334. $this->_active_pane = $pnnAct; // Used in _storeSelection
  2335. $header = pack("vv", $record, $length);
  2336. $data = pack("vvvvv", $x, $y, $rwTop, $colLeft, $pnnAct);
  2337. $this->_append($header . $data);
  2338. }
  2339. /**
  2340. * Store the page setup SETUP BIFF record.
  2341. *
  2342. * @access private
  2343. */
  2344. function _storeSetup()
  2345. {
  2346. $record = 0x00A1; // Record identifier
  2347. $length = 0x0022; // Number of bytes to follow
  2348. $iPaperSize = $this->_paper_size; // Paper size
  2349. $iScale = $this->_print_scale; // Print scaling factor
  2350. $iPageStart = 0x01; // Starting page number
  2351. $iFitWidth = $this->_fit_width; // Fit to number of pages wide
  2352. $iFitHeight = $this->_fit_height; // Fit to number of pages high
  2353. $grbit = 0x00; // Option flags
  2354. $iRes = 0x0258; // Print resolution
  2355. $iVRes = 0x0258; // Vertical print resolution
  2356. $numHdr = $this->_margin_head; // Header Margin
  2357. $numFtr = $this->_margin_foot; // Footer Margin
  2358. $iCopies = 0x01; // Number of copies
  2359. $fLeftToRight = 0x0; // Print over then down
  2360. $fLandscape = $this->_orientation; // Page orientation
  2361. $fNoPls = 0x0; // Setup not read from printer
  2362. $fNoColor = 0x0; // Print black and white
  2363. $fDraft = 0x0; // Print draft quality
  2364. $fNotes = 0x0; // Print notes
  2365. $fNoOrient = 0x0; // Orientation not set
  2366. $fUsePage = 0x0; // Use custom starting page
  2367. $grbit = $fLeftToRight;
  2368. $grbit |= $fLandscape << 1;
  2369. $grbit |= $fNoPls << 2;
  2370. $grbit |= $fNoColor << 3;
  2371. $grbit |= $fDraft << 4;
  2372. $grbit |= $fNotes << 5;
  2373. $grbit |= $fNoOrient << 6;
  2374. $grbit |= $fUsePage << 7;
  2375. $numHdr = pack("d", $numHdr);
  2376. $numFtr = pack("d", $numFtr);
  2377. if ($this->_byte_order) { // if it's Big Endian
  2378. $numHdr = strrev($numHdr);
  2379. $numFtr = strrev($numFtr);
  2380. }
  2381. $header = pack("vv", $record, $length);
  2382. $data1 = pack("vvvvvvvv", $iPaperSize,
  2383. $iScale,
  2384. $iPageStart,
  2385. $iFitWidth,
  2386. $iFitHeight,
  2387. $grbit,
  2388. $iRes,
  2389. $iVRes);
  2390. $data2 = $numHdr.$numFtr;
  2391. $data3 = pack("v", $iCopies);
  2392. $this->_prepend($header . $data1 . $data2 . $data3);
  2393. }
  2394. /**
  2395. * Store the header caption BIFF record.
  2396. *
  2397. * @access private
  2398. */
  2399. function _storeHeader()
  2400. {
  2401. $record = 0x0014; // Record identifier
  2402. $str = $this->_header; // header string
  2403. $cch = strlen($str); // Length of header string
  2404. if ($this->_BIFF_version == 0x0600) {
  2405. $encoding = 0x0; // TODO: Unicode support
  2406. $length = 3 + $cch; // Bytes to follow
  2407. } else {
  2408. $length = 1 + $cch; // Bytes to follow
  2409. }
  2410. $header = pack("vv", $record, $length);
  2411. if ($this->_BIFF_version == 0x0600) {
  2412. $data = pack("vC", $cch, $encoding);
  2413. } else {
  2414. $data = pack("C", $cch);
  2415. }
  2416. $this->_prepend($header.$data.$str);
  2417. }
  2418. /**
  2419. * Store the footer caption BIFF record.
  2420. *
  2421. * @access private
  2422. */
  2423. function _storeFooter()
  2424. {
  2425. $record = 0x0015; // Record identifier
  2426. $str = $this->_footer; // Footer string
  2427. $cch = strlen($str); // Length of footer string
  2428. if ($this->_BIFF_version == 0x0600) {
  2429. $encoding = 0x0; // TODO: Unicode support
  2430. $length = 3 + $cch; // Bytes to follow
  2431. } else {
  2432. $length = 1 + $cch;
  2433. }
  2434. $header = pack("vv", $record, $length);
  2435. if ($this->_BIFF_version == 0x0600) {
  2436. $data = pack("vC", $cch, $encoding);
  2437. } else {
  2438. $data = pack("C", $cch);
  2439. }
  2440. $this->_prepend($header . $data . $str);
  2441. }
  2442. /**
  2443. * Store the horizontal centering HCENTER BIFF record.
  2444. *
  2445. * @access private
  2446. */
  2447. function _storeHcenter()
  2448. {
  2449. $record = 0x0083; // Record identifier
  2450. $length = 0x0002; // Bytes to follow
  2451. $fHCenter = $this->_hcenter; // Horizontal centering
  2452. $header = pack("vv", $record, $length);
  2453. $data = pack("v", $fHCenter);
  2454. $this->_prepend($header.$data);
  2455. }
  2456. /**
  2457. * Store the vertical centering VCENTER BIFF record.
  2458. *
  2459. * @access private
  2460. */
  2461. function _storeVcenter()
  2462. {
  2463. $record = 0x0084; // Record identifier
  2464. $length = 0x0002; // Bytes to follow
  2465. $fVCenter = $this->_vcenter; // Horizontal centering
  2466. $header = pack("vv", $record, $length);
  2467. $data = pack("v", $fVCenter);
  2468. $this->_prepend($header . $data);
  2469. }
  2470. /**
  2471. * Store the LEFTMARGIN BIFF record.
  2472. *
  2473. * @access private
  2474. */
  2475. function _storeMarginLeft()
  2476. {
  2477. $record = 0x0026; // Record identifier
  2478. $length = 0x0008; // Bytes to follow
  2479. $margin = $this->_margin_left; // Margin in inches
  2480. $header = pack("vv", $record, $length);
  2481. $data = pack("d", $margin);
  2482. if ($this->_byte_order) { // if it's Big Endian
  2483. $data = strrev($data);
  2484. }
  2485. $this->_prepend($header . $data);
  2486. }
  2487. /**
  2488. * Store the RIGHTMARGIN BIFF record.
  2489. *
  2490. * @access private
  2491. */
  2492. function _storeMarginRight()
  2493. {
  2494. $record = 0x0027; // Record identifier
  2495. $length = 0x0008; // Bytes to follow
  2496. $margin = $this->_margin_right; // Margin in inches
  2497. $header = pack("vv", $record, $length);
  2498. $data = pack("d", $margin);
  2499. if ($this->_byte_order) { // if it's Big Endian
  2500. $data = strrev($data);
  2501. }
  2502. $this->_prepend($header . $data);
  2503. }
  2504. /**
  2505. * Store the TOPMARGIN BIFF record.
  2506. *
  2507. * @access private
  2508. */
  2509. function _storeMarginTop()
  2510. {
  2511. $record = 0x0028; // Record identifier
  2512. $length = 0x0008; // Bytes to follow
  2513. $margin = $this->_margin_top; // Margin in inches
  2514. $header = pack("vv", $record, $length);
  2515. $data = pack("d", $margin);
  2516. if ($this->_byte_order) { // if it's Big Endian
  2517. $data = strrev($data);
  2518. }
  2519. $this->_prepend($header . $data);
  2520. }
  2521. /**
  2522. * Store the BOTTOMMARGIN BIFF record.
  2523. *
  2524. * @access private
  2525. */
  2526. function _storeMarginBottom()
  2527. {
  2528. $record = 0x0029; // Record identifier
  2529. $length = 0x0008; // Bytes to follow
  2530. $margin = $this->_margin_bottom; // Margin in inches
  2531. $header = pack("vv", $record, $length);
  2532. $data = pack("d", $margin);
  2533. if ($this->_byte_order) { // if it's Big Endian
  2534. $data = strrev($data);
  2535. }
  2536. $this->_prepend($header . $data);
  2537. }
  2538. /**
  2539. * Merges the area given by its arguments.
  2540. * This is an Excel97/2000 method. It is required to perform more complicated
  2541. * merging than the normal setAlign('merge').
  2542. *
  2543. * @access public
  2544. * @param integer $first_row First row of the area to merge
  2545. * @param integer $first_col First column of the area to merge
  2546. * @param integer $last_row Last row of the area to merge
  2547. * @param integer $last_col Last column of the area to merge
  2548. */
  2549. function mergeCells($first_row, $first_col, $last_row, $last_col)
  2550. {
  2551. $record = 0x00E5; // Record identifier
  2552. $length = 0x000A; // Bytes to follow
  2553. $cref = 1; // Number of refs
  2554. // Swap last row/col for first row/col as necessary
  2555. if ($first_row > $last_row) {
  2556. list($first_row, $last_row) = array($last_row, $first_row);
  2557. }
  2558. if ($first_col > $last_col) {
  2559. list($first_col, $last_col) = array($last_col, $first_col);
  2560. }
  2561. $header = pack("vv", $record, $length);
  2562. $data = pack("vvvvv", $cref, $first_row, $last_row,
  2563. $first_col, $last_col);
  2564. $this->_append($header.$data);
  2565. }
  2566. /**
  2567. * Write the PRINTHEADERS BIFF record.
  2568. *
  2569. * @access private
  2570. */
  2571. function _storePrintHeaders()
  2572. {
  2573. $record = 0x002a; // Record identifier
  2574. $length = 0x0002; // Bytes to follow
  2575. $fPrintRwCol = $this->_print_headers; // Boolean flag
  2576. $header = pack("vv", $record, $length);
  2577. $data = pack("v", $fPrintRwCol);
  2578. $this->_prepend($header . $data);
  2579. }
  2580. /**
  2581. * Write the PRINTGRIDLINES BIFF record. Must be used in conjunction with the
  2582. * GRIDSET record.
  2583. *
  2584. * @access private
  2585. */
  2586. function _storePrintGridlines()
  2587. {
  2588. $record = 0x002b; // Record identifier
  2589. $length = 0x0002; // Bytes to follow
  2590. $fPrintGrid = $this->_print_gridlines; // Boolean flag
  2591. $header = pack("vv", $record, $length);
  2592. $data = pack("v", $fPrintGrid);
  2593. $this->_prepend($header . $data);
  2594. }
  2595. /**
  2596. * Write the GRIDSET BIFF record. Must be used in conjunction with the
  2597. * PRINTGRIDLINES record.
  2598. *
  2599. * @access private
  2600. */
  2601. function _storeGridset()
  2602. {
  2603. $record = 0x0082; // Record identifier
  2604. $length = 0x0002; // Bytes to follow
  2605. $fGridSet = !($this->_print_gridlines); // Boolean flag
  2606. $header = pack("vv", $record, $length);
  2607. $data = pack("v", $fGridSet);
  2608. $this->_prepend($header . $data);
  2609. }
  2610. /**
  2611. * Write the GUTS BIFF record. This is used to configure the gutter margins
  2612. * where Excel outline symbols are displayed. The visibility of the gutters is
  2613. * controlled by a flag in WSBOOL.
  2614. *
  2615. * @see _storeWsbool()
  2616. * @access private
  2617. */
  2618. function _storeGuts()
  2619. {
  2620. $record = 0x0080; // Record identifier
  2621. $length = 0x0008; // Bytes to follow
  2622. $dxRwGut = 0x0000; // Size of row gutter
  2623. $dxColGut = 0x0000; // Size of col gutter
  2624. $row_level = $this->_outline_row_level;
  2625. $col_level = 0;
  2626. // Calculate the maximum column outline level. The equivalent calculation
  2627. // for the row outline level is carried out in setRow().
  2628. $colcount = count($this->_colinfo);
  2629. for ($i = 0; $i < $colcount; ++$i) {
  2630. $col_level = max($this->_colinfo[$i][5], $col_level);
  2631. }
  2632. // Set the limits for the outline levels (0 <= x <= 7).
  2633. $col_level = max(0, min($col_level, 7));
  2634. // The displayed level is one greater than the max outline levels
  2635. if ($row_level) {
  2636. ++$row_level;
  2637. }
  2638. if ($col_level) {
  2639. ++$col_level;
  2640. }
  2641. $header = pack("vv", $record, $length);
  2642. $data = pack("vvvv", $dxRwGut, $dxColGut, $row_level, $col_level);
  2643. $this->_prepend($header.$data);
  2644. }
  2645. /**
  2646. * Write the WSBOOL BIFF record, mainly for fit-to-page. Used in conjunction
  2647. * with the SETUP record.
  2648. *
  2649. * @access private
  2650. */
  2651. function _storeWsbool()
  2652. {
  2653. $record = 0x0081; // Record identifier
  2654. $length = 0x0002; // Bytes to follow
  2655. $grbit = 0x0000;
  2656. // The only option that is of interest is the flag for fit to page. So we
  2657. // set all the options in one go.
  2658. //
  2659. /*if ($this->_fit_page) {
  2660. $grbit = 0x05c1;
  2661. } else {
  2662. $grbit = 0x04c1;
  2663. }*/
  2664. // Set the option flags
  2665. $grbit |= 0x0001; // Auto page breaks visible
  2666. if ($this->_outline_style) {
  2667. $grbit |= 0x0020; // Auto outline styles
  2668. }
  2669. if ($this->_outline_below) {
  2670. $grbit |= 0x0040; // Outline summary below
  2671. }
  2672. if ($this->_outline_right) {
  2673. $grbit |= 0x0080; // Outline summary right
  2674. }
  2675. if ($this->_fit_page) {
  2676. $grbit |= 0x0100; // Page setup fit to page
  2677. }
  2678. if ($this->_outline_on) {
  2679. $grbit |= 0x0400; // Outline symbols displayed
  2680. }
  2681. $header = pack("vv", $record, $length);
  2682. $data = pack("v", $grbit);
  2683. $this->_prepend($header . $data);
  2684. }
  2685. /**
  2686. * Write the HORIZONTALPAGEBREAKS BIFF record.
  2687. *
  2688. * @access private
  2689. */
  2690. function _storeHbreak()
  2691. {
  2692. // Return if the user hasn't specified pagebreaks
  2693. if (empty($this->_hbreaks)) {
  2694. return;
  2695. }
  2696. // Sort and filter array of page breaks
  2697. $breaks = $this->_hbreaks;
  2698. sort($breaks, SORT_NUMERIC);
  2699. if ($breaks[0] == 0) { // don't use first break if it's 0
  2700. array_shift($breaks);
  2701. }
  2702. $record = 0x001b; // Record identifier
  2703. $cbrk = count($breaks); // Number of page breaks
  2704. if ($this->_BIFF_version == 0x0600) {
  2705. $length = 2 + 6*$cbrk; // Bytes to follow
  2706. } else {
  2707. $length = 2 + 2*$cbrk; // Bytes to follow
  2708. }
  2709. $header = pack("vv", $record, $length);
  2710. $data = pack("v", $cbrk);
  2711. // Append each page break
  2712. foreach ($breaks as $break) {
  2713. if ($this->_BIFF_version == 0x0600) {
  2714. $data .= pack("vvv", $break, 0x0000, 0x00ff);
  2715. } else {
  2716. $data .= pack("v", $break);
  2717. }
  2718. }
  2719. $this->_prepend($header.$data);
  2720. }
  2721. /**
  2722. * Write the VERTICALPAGEBREAKS BIFF record.
  2723. *
  2724. * @access private
  2725. */
  2726. function _storeVbreak()
  2727. {
  2728. // Return if the user hasn't specified pagebreaks
  2729. if (empty($this->_vbreaks)) {
  2730. return;
  2731. }
  2732. // 1000 vertical pagebreaks appears to be an internal Excel 5 limit.
  2733. // It is slightly higher in Excel 97/200, approx. 1026
  2734. $breaks = array_slice($this->_vbreaks,0,1000);
  2735. // Sort and filter array of page breaks
  2736. sort($breaks, SORT_NUMERIC);
  2737. if ($breaks[0] == 0) { // don't use first break if it's 0
  2738. array_shift($breaks);
  2739. }
  2740. $record = 0x001a; // Record identifier
  2741. $cbrk = count($breaks); // Number of page breaks
  2742. if ($this->_BIFF_version == 0x0600) {
  2743. $length = 2 + 6*$cbrk; // Bytes to follow
  2744. } else {
  2745. $length = 2 + 2*$cbrk; // Bytes to follow
  2746. }
  2747. $header = pack("vv", $record, $length);
  2748. $data = pack("v", $cbrk);
  2749. // Append each page break
  2750. foreach ($breaks as $break) {
  2751. if ($this->_BIFF_version == 0x0600) {
  2752. $data .= pack("vvv", $break, 0x0000, 0xffff);
  2753. } else {
  2754. $data .= pack("v", $break);
  2755. }
  2756. }
  2757. $this->_prepend($header . $data);
  2758. }
  2759. /**
  2760. * Set the Biff PROTECT record to indicate that the worksheet is protected.
  2761. *
  2762. * @access private
  2763. */
  2764. function _storeProtect()
  2765. {
  2766. // Exit unless sheet protection has been specified
  2767. if ($this->_protect == 0) {
  2768. return;
  2769. }
  2770. $record = 0x0012; // Record identifier
  2771. $length = 0x0002; // Bytes to follow
  2772. $fLock = $this->_protect; // Worksheet is protected
  2773. $header = pack("vv", $record, $length);
  2774. $data = pack("v", $fLock);
  2775. $this->_prepend($header.$data);
  2776. }
  2777. /**
  2778. * Write the worksheet PASSWORD record.
  2779. *
  2780. * @access private
  2781. */
  2782. function _storePassword()
  2783. {
  2784. // Exit unless sheet protection and password have been specified
  2785. if (($this->_protect == 0) || (!isset($this->_password))) {
  2786. return;
  2787. }
  2788. $record = 0x0013; // Record identifier
  2789. $length = 0x0002; // Bytes to follow
  2790. $wPassword = $this->_password; // Encoded password
  2791. $header = pack("vv", $record, $length);
  2792. $data = pack("v", $wPassword);
  2793. $this->_prepend($header . $data);
  2794. }
  2795. /**
  2796. * Insert a 24bit bitmap image in a worksheet.
  2797. *
  2798. * @access public
  2799. * @param integer $row The row we are going to insert the bitmap into
  2800. * @param integer $col The column we are going to insert the bitmap into
  2801. * @param mixed $bitmap The bitmap filename or GD-image resource
  2802. * @param integer $x The horizontal position (offset) of the image inside the cell.
  2803. * @param integer $y The vertical position (offset) of the image inside the cell.
  2804. * @param float $scale_x The horizontal scale
  2805. * @param float $scale_y The vertical scale
  2806. */
  2807. function insertBitmap($row, $col, $bitmap, $x = 0, $y = 0, $scale_x = 1, $scale_y = 1)
  2808. {
  2809. $bitmap_array = (is_resource($bitmap) ? $this->_processBitmapGd($bitmap) : $this->_processBitmap($bitmap));
  2810. list($width, $height, $size, $data) = $bitmap_array; //$this->_processBitmap($bitmap);
  2811. // Scale the frame of the image.
  2812. $width *= $scale_x;
  2813. $height *= $scale_y;
  2814. // Calculate the vertices of the image and write the OBJ record
  2815. $this->_positionImage($col, $row, $x, $y, $width, $height);
  2816. // Write the IMDATA record to store the bitmap data
  2817. $record = 0x007f;
  2818. $length = 8 + $size;
  2819. $cf = 0x09;
  2820. $env = 0x01;
  2821. $lcb = $size;
  2822. $header = pack("vvvvV", $record, $length, $cf, $env, $lcb);
  2823. $this->_append($header.$data);
  2824. }
  2825. /**
  2826. * Calculate the vertices that define the position of the image as required by
  2827. * the OBJ record.
  2828. *
  2829. * +------------+------------+
  2830. * | A | B |
  2831. * +-----+------------+------------+
  2832. * | |(x1,y1) | |
  2833. * | 1 |(A1)._______|______ |
  2834. * | | | | |
  2835. * | | | | |
  2836. * +-----+----| BITMAP |-----+
  2837. * | | | | |
  2838. * | 2 | |______________. |
  2839. * | | | (B2)|
  2840. * | | | (x2,y2)|
  2841. * +---- +------------+------------+
  2842. *
  2843. * Example of a bitmap that covers some of the area from cell A1 to cell B2.
  2844. *
  2845. * Based on the width and height of the bitmap we need to calculate 8 vars:
  2846. * $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
  2847. * The width and height of the cells are also variable and have to be taken into
  2848. * account.
  2849. * The values of $col_start and $row_start are passed in from the calling
  2850. * function. The values of $col_end and $row_end are calculated by subtracting
  2851. * the width and height of the bitmap from the width and height of the
  2852. * underlying cells.
  2853. * The vertices are expressed as a percentage of the underlying cell width as
  2854. * follows (rhs values are in pixels):
  2855. *
  2856. * x1 = X / W *1024
  2857. * y1 = Y / H *256
  2858. * x2 = (X-1) / W *1024
  2859. * y2 = (Y-1) / H *256
  2860. *
  2861. * Where: X is distance from the left side of the underlying cell
  2862. * Y is distance from the top of the underlying cell
  2863. * W is the width of the cell
  2864. * H is the height of the cell
  2865. *
  2866. * @access private
  2867. * @note the SDK incorrectly states that the height should be expressed as a
  2868. * percentage of 1024.
  2869. * @param integer $col_start Col containing upper left corner of object
  2870. * @param integer $row_start Row containing top left corner of object
  2871. * @param integer $x1 Distance to left side of object
  2872. * @param integer $y1 Distance to top of object
  2873. * @param integer $width Width of image frame
  2874. * @param integer $height Height of image frame
  2875. */
  2876. function _positionImage($col_start, $row_start, $x1, $y1, $width, $height)
  2877. {
  2878. // Initialise end cell to the same as the start cell
  2879. $col_end = $col_start; // Col containing lower right corner of object
  2880. $row_end = $row_start; // Row containing bottom right corner of object
  2881. // Zero the specified offset if greater than the cell dimensions
  2882. if ($x1 >= $this->_sizeCol($col_start)) {
  2883. $x1 = 0;
  2884. }
  2885. if ($y1 >= $this->_sizeRow($row_start)) {
  2886. $y1 = 0;
  2887. }
  2888. $width = $width + $x1 -1;
  2889. $height = $height + $y1 -1;
  2890. // Subtract the underlying cell widths to find the end cell of the image
  2891. while ($width >= $this->_sizeCol($col_end)) {
  2892. $width -= $this->_sizeCol($col_end);
  2893. ++$col_end;
  2894. }
  2895. // Subtract the underlying cell heights to find the end cell of the image
  2896. while ($height >= $this->_sizeRow($row_end)) {
  2897. $height -= $this->_sizeRow($row_end);
  2898. ++$row_end;
  2899. }
  2900. // Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
  2901. // with zero eight or width.
  2902. //
  2903. if ($this->_sizeCol($col_start) == 0) {
  2904. return;
  2905. }
  2906. if ($this->_sizeCol($col_end) == 0) {
  2907. return;
  2908. }
  2909. if ($this->_sizeRow($row_start) == 0) {
  2910. return;
  2911. }
  2912. if ($this->_sizeRow($row_end) == 0) {
  2913. return;
  2914. }
  2915. // Convert the pixel values to the percentage value expected by Excel
  2916. $x1 = $x1 / $this->_sizeCol($col_start) * 1024;
  2917. $y1 = $y1 / $this->_sizeRow($row_start) * 256;
  2918. $x2 = $width / $this->_sizeCol($col_end) * 1024; // Distance to right side of object
  2919. $y2 = $height / $this->_sizeRow($row_end) * 256; // Distance to bottom of object
  2920. $this->_storeObjPicture($col_start, $x1,
  2921. $row_start, $y1,
  2922. $col_end, $x2,
  2923. $row_end, $y2);
  2924. }
  2925. /**
  2926. * Convert the width of a cell from user's units to pixels. By interpolation
  2927. * the relationship is: y = 7x +5. If the width hasn't been set by the user we
  2928. * use the default value. If the col is hidden we use a value of zero.
  2929. *
  2930. * @access private
  2931. * @param integer $col The column
  2932. * @return integer The width in pixels
  2933. */
  2934. function _sizeCol($col)
  2935. {
  2936. // Look up the cell value to see if it has been changed
  2937. if (isset($this->col_sizes[$col])) {
  2938. if ($this->col_sizes[$col] == 0) {
  2939. return(0);
  2940. } else {
  2941. return(floor(7 * $this->col_sizes[$col] + 5));
  2942. }
  2943. } else {
  2944. return(64);
  2945. }
  2946. }
  2947. /**
  2948. * Convert the height of a cell from user's units to pixels. By interpolation
  2949. * the relationship is: y = 4/3x. If the height hasn't been set by the user we
  2950. * use the default value. If the row is hidden we use a value of zero. (Not
  2951. * possible to hide row yet).
  2952. *
  2953. * @access private
  2954. * @param integer $row The row
  2955. * @return integer The width in pixels
  2956. */
  2957. function _sizeRow($row)
  2958. {
  2959. // Look up the cell value to see if it has been changed
  2960. if (isset($this->_row_sizes[$row])) {
  2961. if ($this->_row_sizes[$row] == 0) {
  2962. return(0);
  2963. } else {
  2964. return(floor(4/3 * $this->_row_sizes[$row]));
  2965. }
  2966. } else {
  2967. return(17);
  2968. }
  2969. }
  2970. /**
  2971. * Store the OBJ record that precedes an IMDATA record. This could be generalise
  2972. * to support other Excel objects.
  2973. *
  2974. * @access private
  2975. * @param integer $colL Column containing upper left corner of object
  2976. * @param integer $dxL Distance from left side of cell
  2977. * @param integer $rwT Row containing top left corner of object
  2978. * @param integer $dyT Distance from top of cell
  2979. * @param integer $colR Column containing lower right corner of object
  2980. * @param integer $dxR Distance from right of cell
  2981. * @param integer $rwB Row containing bottom right corner of object
  2982. * @param integer $dyB Distance from bottom of cell
  2983. */
  2984. function _storeObjPicture($colL,$dxL,$rwT,$dyT,$colR,$dxR,$rwB,$dyB)
  2985. {
  2986. $record = 0x005d; // Record identifier
  2987. $length = 0x003c; // Bytes to follow
  2988. $cObj = 0x0001; // Count of objects in file (set to 1)
  2989. $OT = 0x0008; // Object type. 8 = Picture
  2990. $id = 0x0001; // Object ID
  2991. $grbit = 0x0614; // Option flags
  2992. $cbMacro = 0x0000; // Length of FMLA structure
  2993. $Reserved1 = 0x0000; // Reserved
  2994. $Reserved2 = 0x0000; // Reserved
  2995. $icvBack = 0x09; // Background colour
  2996. $icvFore = 0x09; // Foreground colour
  2997. $fls = 0x00; // Fill pattern
  2998. $fAuto = 0x00; // Automatic fill
  2999. $icv = 0x08; // Line colour
  3000. $lns = 0xff; // Line style
  3001. $lnw = 0x01; // Line weight
  3002. $fAutoB = 0x00; // Automatic border
  3003. $frs = 0x0000; // Frame style
  3004. $cf = 0x0009; // Image format, 9 = bitmap
  3005. $Reserved3 = 0x0000; // Reserved
  3006. $cbPictFmla = 0x0000; // Length of FMLA structure
  3007. $Reserved4 = 0x0000; // Reserved
  3008. $grbit2 = 0x0001; // Option flags
  3009. $Reserved5 = 0x0000; // Reserved
  3010. $header = pack("vv", $record, $length);
  3011. $data = pack("V", $cObj);
  3012. $data .= pack("v", $OT);
  3013. $data .= pack("v", $id);
  3014. $data .= pack("v", $grbit);
  3015. $data .= pack("v", $colL);
  3016. $data .= pack("v", $dxL);
  3017. $data .= pack("v", $rwT);
  3018. $data .= pack("v", $dyT);
  3019. $data .= pack("v", $colR);
  3020. $data .= pack("v", $dxR);
  3021. $data .= pack("v", $rwB);
  3022. $data .= pack("v", $dyB);
  3023. $data .= pack("v", $cbMacro);
  3024. $data .= pack("V", $Reserved1);
  3025. $data .= pack("v", $Reserved2);
  3026. $data .= pack("C", $icvBack);
  3027. $data .= pack("C", $icvFore);
  3028. $data .= pack("C", $fls);
  3029. $data .= pack("C", $fAuto);
  3030. $data .= pack("C", $icv);
  3031. $data .= pack("C", $lns);
  3032. $data .= pack("C", $lnw);
  3033. $data .= pack("C", $fAutoB);
  3034. $data .= pack("v", $frs);
  3035. $data .= pack("V", $cf);
  3036. $data .= pack("v", $Reserved3);
  3037. $data .= pack("v", $cbPictFmla);
  3038. $data .= pack("v", $Reserved4);
  3039. $data .= pack("v", $grbit2);
  3040. $data .= pack("V", $Reserved5);
  3041. $this->_append($header . $data);
  3042. }
  3043. /**
  3044. * Convert a GD-image into the internal format.
  3045. *
  3046. * @access private
  3047. * @param resource $image The image to process
  3048. * @return array Array with data and properties of the bitmap
  3049. */
  3050. function _processBitmapGd($image) {
  3051. $width = imagesx($image);
  3052. $height = imagesy($image);
  3053. $data = pack("Vvvvv", 0x000c, $width, $height, 0x01, 0x18);
  3054. for ($j=$height; $j--; ) {
  3055. for ($i=0; $i < $width; ++$i) {
  3056. $color = imagecolorsforindex($image, imagecolorat($image, $i, $j));
  3057. foreach (array("red", "green", "blue") as $key) {
  3058. $color[$key] = $color[$key] + round((255 - $color[$key]) * $color["alpha"] / 127);
  3059. }
  3060. $data .= chr($color["blue"]) . chr($color["green"]) . chr($color["red"]);
  3061. }
  3062. if (3*$width % 4) {
  3063. $data .= str_repeat("\x00", 4 - 3*$width % 4);
  3064. }
  3065. }
  3066. return array($width, $height, strlen($data), $data);
  3067. }
  3068. /**
  3069. * Convert a 24 bit bitmap into the modified internal format used by Windows.
  3070. * This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the
  3071. * MSDN library.
  3072. *
  3073. * @access private
  3074. * @param string $bitmap The bitmap to process
  3075. * @return array Array with data and properties of the bitmap
  3076. */
  3077. function _processBitmap($bitmap)
  3078. {
  3079. // Open file.
  3080. $bmp_fd = @fopen($bitmap,"rb");
  3081. if (!$bmp_fd) {
  3082. throw new Exception("Couldn't import $bitmap");
  3083. }
  3084. // Slurp the file into a string.
  3085. $data = fread($bmp_fd, filesize($bitmap));
  3086. // Check that the file is big enough to be a bitmap.
  3087. if (strlen($data) <= 0x36) {
  3088. throw new Exception("$bitmap doesn't contain enough data.\n");
  3089. }
  3090. // The first 2 bytes are used to identify the bitmap.
  3091. $identity = unpack("A2ident", $data);
  3092. if ($identity['ident'] != "BM") {
  3093. throw new Exception("$bitmap doesn't appear to be a valid bitmap image.\n");
  3094. }
  3095. // Remove bitmap data: ID.
  3096. $data = substr($data, 2);
  3097. // Read and remove the bitmap size. This is more reliable than reading
  3098. // the data size at offset 0x22.
  3099. //
  3100. $size_array = unpack("Vsa", substr($data, 0, 4));
  3101. $size = $size_array['sa'];
  3102. $data = substr($data, 4);
  3103. $size -= 0x36; // Subtract size of bitmap header.
  3104. $size += 0x0C; // Add size of BIFF header.
  3105. // Remove bitmap data: reserved, offset, header length.
  3106. $data = substr($data, 12);
  3107. // Read and remove the bitmap width and height. Verify the sizes.
  3108. $width_and_height = unpack("V2", substr($data, 0, 8));
  3109. $width = $width_and_height[1];
  3110. $height = $width_and_height[2];
  3111. $data = substr($data, 8);
  3112. if ($width > 0xFFFF) {
  3113. throw new Exception("$bitmap: largest image width supported is 65k.\n");
  3114. }
  3115. if ($height > 0xFFFF) {
  3116. throw new Exception("$bitmap: largest image height supported is 65k.\n");
  3117. }
  3118. // Read and remove the bitmap planes and bpp data. Verify them.
  3119. $planes_and_bitcount = unpack("v2", substr($data, 0, 4));
  3120. $data = substr($data, 4);
  3121. if ($planes_and_bitcount[2] != 24) { // Bitcount
  3122. throw new Exception("$bitmap isn't a 24bit true color bitmap.\n");
  3123. }
  3124. if ($planes_and_bitcount[1] != 1) {
  3125. throw new Exception("$bitmap: only 1 plane supported in bitmap image.\n");
  3126. }
  3127. // Read and remove the bitmap compression. Verify compression.
  3128. $compression = unpack("Vcomp", substr($data, 0, 4));
  3129. $data = substr($data, 4);
  3130. //$compression = 0;
  3131. if ($compression['comp'] != 0) {
  3132. throw new Exception("$bitmap: compression not supported in bitmap image.\n");
  3133. }
  3134. // Remove bitmap data: data size, hres, vres, colours, imp. colours.
  3135. $data = substr($data, 20);
  3136. // Add the BITMAPCOREHEADER data
  3137. $header = pack("Vvvvv", 0x000c, $width, $height, 0x01, 0x18);
  3138. $data = $header . $data;
  3139. return (array($width, $height, $size, $data));
  3140. }
  3141. /**
  3142. * Store the window zoom factor. This should be a reduced fraction but for
  3143. * simplicity we will store all fractions with a numerator of 100.
  3144. *
  3145. * @access private
  3146. */
  3147. function _storeZoom()
  3148. {
  3149. // If scale is 100 we don't need to write a record
  3150. if ($this->_zoom == 100) {
  3151. return;
  3152. }
  3153. $record = 0x00A0; // Record identifier
  3154. $length = 0x0004; // Bytes to follow
  3155. $header = pack("vv", $record, $length);
  3156. $data = pack("vv", $this->_zoom, 100);
  3157. $this->_append($header . $data);
  3158. }
  3159. /**
  3160. * Store the DVAL and DV records.
  3161. *
  3162. * @access private
  3163. */
  3164. function _storeDataValidity()
  3165. {
  3166. $record = 0x01b2; // Record identifier
  3167. $length = 0x0012; // Bytes to follow
  3168. $grbit = 0x0002; // Prompt box at cell, no cached validity data at DV records
  3169. $horPos = 0x00000000; // Horizontal position of prompt box, if fixed position
  3170. $verPos = 0x00000000; // Vertical position of prompt box, if fixed position
  3171. $objId = 0xffffffff; // Object identifier of drop down arrow object, or -1 if not visible
  3172. $header = pack('vv', $record, $length);
  3173. $data = pack('vVVVV', $grbit, $horPos, $verPos, $objId,
  3174. count($this->_dv));
  3175. $this->_append($header.$data);
  3176. $record = 0x01be; // Record identifier
  3177. foreach ($this->_dv as $dv) {
  3178. $length = strlen($dv); // Bytes to follow
  3179. $header = pack("vv", $record, $length);
  3180. $this->_append($header . $dv);
  3181. }
  3182. }
  3183. /**
  3184. * Set sheet dimensions
  3185. *
  3186. * @param int $firstRowIndex
  3187. * @param int $lastRowIndex
  3188. * @param int $firstColumnIndex
  3189. * @param int $lastColumnIndex
  3190. */
  3191. public function setDimensions($firstRowIndex = 0, $lastRowIndex = -1, $firstColumnIndex = 0, $lastColumnIndex = -1)
  3192. {
  3193. $this->_firstRowIndex = $firstRowIndex;
  3194. $this->_lastRowIndex = $lastRowIndex;
  3195. $this->_firstColumnIndex = $firstColumnIndex;
  3196. $this->_lastColumnIndex = $lastColumnIndex;
  3197. }
  3198. }