ILIAS  Release_3_10_x_branch Revision 61812
 All Data Structures Namespaces Files Functions Variables Groups Pages
Worksheet.php
Go to the documentation of this file.
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 * Spreadsheet_Excel_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 
35 require_once('classes/Spreadsheet/Excel/Writer/Parser.php');
36 require_once('classes/Spreadsheet/Excel/Writer/BIFFwriter.php');
37 
47 {
52  var $name;
53 
58  var $index;
59 
65 
70  var $_parser;
71 
77 
83 
89 
95 
101 
108 
115 
122 
129 
135 
141 
146  var $_panes;
147 
153 
158  var $_frozen;
159 
165 
171 
177 
182  var $_header;
183 
188  var $_footer;
189 
195 
201 
207 
213 
219 
225 
231 
237 
243 
249 
255 
261 
267 
273 
279 
285 
291 
297 
303 
309 
315 
321 
327 
333 
339 
345 
351 
363  function Spreadsheet_Excel_Writer_Worksheet($BIFF_version, $name,
364  $index, &$activesheet,
365  &$firstsheet, &$str_total,
366  &$str_unique, &$str_table,
367  &$url_format, &$parser)
368  {
369  // It needs to call its parent's constructor explicitly
371  $this->_BIFF_version = $BIFF_version;
372  $rowmax = 65536; // 16384 in Excel 5
373  $colmax = 256;
374 
375  $this->name = $name;
376  $this->index = $index;
377  $this->activesheet = &$activesheet;
378  $this->firstsheet = &$firstsheet;
379  $this->_str_total = &$str_total;
380  $this->_str_unique = &$str_unique;
381  $this->_str_table = &$str_table;
382  $this->_url_format = &$url_format;
383  $this->_parser = &$parser;
384 
385  //$this->ext_sheets = array();
386  $this->_filehandle = "";
387  $this->_using_tmpfile = true;
388  //$this->fileclosed = 0;
389  //$this->offset = 0;
390  $this->_xls_rowmax = $rowmax;
391  $this->_xls_colmax = $colmax;
392  $this->_xls_strmax = 255;
393  $this->_dim_rowmin = $rowmax + 1;
394  $this->_dim_rowmax = 0;
395  $this->_dim_colmin = $colmax + 1;
396  $this->_dim_colmax = 0;
397  $this->_colinfo = array();
398  $this->_selection = array(0,0,0,0);
399  $this->_panes = array();
400  $this->_active_pane = 3;
401  $this->_frozen = 0;
402  $this->selected = 0;
403 
404  $this->_paper_size = 0x0;
405  $this->_orientation = 0x1;
406  $this->_header = '';
407  $this->_footer = '';
408  $this->_hcenter = 0;
409  $this->_vcenter = 0;
410  $this->_margin_head = 0.50;
411  $this->_margin_foot = 0.50;
412  $this->_margin_left = 0.75;
413  $this->_margin_right = 0.75;
414  $this->_margin_top = 1.00;
415  $this->_margin_bottom = 1.00;
416 
417  $this->title_rowmin = NULL;
418  $this->title_rowmax = NULL;
419  $this->title_colmin = NULL;
420  $this->title_colmax = NULL;
421  $this->print_rowmin = NULL;
422  $this->print_rowmax = NULL;
423  $this->print_colmin = NULL;
424  $this->print_colmax = NULL;
425 
426  $this->_print_gridlines = 1;
427  $this->_print_headers = 0;
428 
429  $this->_fit_page = 0;
430  $this->_fit_width = 0;
431  $this->_fit_height = 0;
432 
433  $this->_hbreaks = array();
434  $this->_vbreaks = array();
435 
436  $this->_protect = 0;
437  $this->_password = NULL;
438 
439  $this->col_sizes = array();
440  $this->row_sizes = array();
441 
442  $this->_zoom = 100;
443  $this->_print_scale = 100;
444 
445  $this->_outline_row_level = 0;
446  $this->_outline_style = 0;
447  $this->_outline_below = 1;
448  $this->_outline_right = 1;
449  $this->_outline_on = 1;
450 
451  $this->_merged_ranges = array();
452 
453  $this->_dv = array();
454 
455  $this->_initialize();
456  }
457 
465  function _initialize()
466  {
467  if ($this->_using_tmpfile == false) {
468  return;
469  }
470  // Open tmp file for storing Worksheet data
471  $tmp_filename = @tempnam("", "Worksheet_Data");
472  $fh = @fopen($tmp_filename, "w+b");
473  if ( $fh) {
474  // Store filehandle
475  $this->_filehandle = $fh;
476  }
477  else {
478  // If tmpfile() fails store data in memory
479  $this->_using_tmpfile = false;
480  }
481  }
482 
492  function close($sheetnames)
493  {
494  $num_sheets = count($sheetnames);
495 
496  /***********************************************
497  * Prepend in reverse order!!
498  */
499 
500  // Prepend the sheet dimensions
501  $this->_storeDimensions();
502 
503  // Prepend the sheet password
504  $this->_storePassword();
505 
506  // Prepend the sheet protection
507  $this->_storeProtect();
508 
509  // Prepend the page setup
510  $this->_storeSetup();
511 
512  /* FIXME: margins are actually appended */
513  // Prepend the bottom margin
514  $this->_storeMarginBottom();
515 
516  // Prepend the top margin
517  $this->_storeMarginTop();
518 
519  // Prepend the right margin
520  $this->_storeMarginRight();
521 
522  // Prepend the left margin
523  $this->_storeMarginLeft();
524 
525  // Prepend the page vertical centering
526  $this->_storeVcenter();
527 
528  // Prepend the page horizontal centering
529  $this->_storeHcenter();
530 
531  // Prepend the page footer
532  $this->_storeFooter();
533 
534  // Prepend the page header
535  $this->_storeHeader();
536 
537  // Prepend the vertical page breaks
538  $this->_storeVbreak();
539 
540  // Prepend the horizontal page breaks
541  $this->_storeHbreak();
542 
543  // Prepend WSBOOL
544  $this->_storeWsbool();
545 
546  // Prepend GRIDSET
547  $this->_storeGridset();
548 
549  // Prepend GUTS
550  if ($this->_BIFF_version == 0x0500) {
551  $this->_storeGuts();
552  }
553 
554  // Prepend PRINTGRIDLINES
555  $this->_storePrintGridlines();
556 
557  // Prepend PRINTHEADERS
558  $this->_storePrintHeaders();
559 
560  // Prepend EXTERNSHEET references
561  if ($this->_BIFF_version == 0x0500) {
562  for ($i = $num_sheets; $i > 0; $i--) {
563  $sheetname = $sheetnames[$i-1];
564  $this->_storeExternsheet($sheetname);
565  }
566  }
567 
568  // Prepend the EXTERNCOUNT of external references.
569  if ($this->_BIFF_version == 0x0500) {
570  $this->_storeExterncount($num_sheets);
571  }
572 
573  // Prepend the COLINFO records if they exist
574  if (!empty($this->_colinfo))
575  {
576  for ($i=0; $i < count($this->_colinfo); $i++) {
577  $this->_storeColinfo($this->_colinfo[$i]);
578  }
579  $this->_storeDefcol();
580  }
581 
582  // Prepend the BOF record
583  $this->_storeBof(0x0010);
584 
585  /*
586  * End of prepend. Read upwards from here.
587  ***********************************************/
588 
589  // Append
590  $this->_storeWindow2();
591  $this->_storeZoom();
592  if (!empty($this->_panes)) {
593  $this->_storePanes($this->_panes);
594  }
595  $this->_storeSelection($this->_selection);
596  $this->_storeMergedCells();
597  /* TODO: add data validity */
598  /*if ($this->_BIFF_version == 0x0600) {
599  $this->_storeDataValidity();
600  }*/
601  $this->_storeEof();
602  }
603 
611  function getName()
612  {
613  return $this->name;
614  }
615 
622  function getData()
623  {
624  $buffer = 4096;
625 
626  // Return data stored in memory
627  if (isset($this->_data))
628  {
629  $tmp = $this->_data;
630  unset($this->_data);
631  $fh = $this->_filehandle;
632  if ($this->_using_tmpfile) {
633  fseek($fh, 0);
634  }
635  return $tmp;
636  }
637  // Return data stored on disk
638  if ($this->_using_tmpfile)
639  {
640  if ($tmp = fread($this->_filehandle, $buffer)) {
641  return $tmp;
642  }
643  }
644 
645  // No data to return
646  return '';
647  }
648 
658  function setMerge($first_row, $first_col, $last_row, $last_col)
659  {
660  if (($last_row < $first_row) or ($last_col < $first_col)) {
661  return;
662  }
663  // don't check rowmin, rowmax, etc... because we don't know when this
664  // is going to be called
665  $this->_merged_ranges[] = array($first_row, $first_col, $last_row, $last_col);
666  }
667 
674  function select()
675  {
676  $this->selected = 1;
677  }
678 
686  function activate()
687  {
688  $this->selected = 1;
689  $this->activesheet = $this->index;
690  }
691 
699  function setFirstSheet()
700  {
701  $this->firstsheet = $this->index;
702  }
703 
712  function protect($password)
713  {
714  $this->_protect = 1;
715  $this->_password = $this->_encodePassword($password);
716  }
717 
729  function setColumn($firstcol, $lastcol, $width, $format = 0, $hidden = 0, $level = 0)
730  {
731  $this->_colinfo[] = array($firstcol, $lastcol, $width, &$format, $hidden, $level);
732 
733  // Set width to zero if column is hidden
734  $width = ($hidden) ? 0 : $width;
735 
736  for ($col = $firstcol; $col <= $lastcol; $col++) {
737  $this->col_sizes[$col] = $width;
738  }
739  }
740 
750  function setSelection($first_row,$first_column,$last_row,$last_column)
751  {
752  $this->_selection = array($first_row,$first_column,$last_row,$last_column);
753  }
754 
766  function freezePanes($panes)
767  {
768  $this->_frozen = 1;
769  $this->_panes = $panes;
770  }
771 
783  function thawPanes($panes)
784  {
785  $this->_frozen = 0;
786  $this->_panes = $panes;
787  }
788 
794  function setPortrait()
795  {
796  $this->_orientation = 1;
797  }
798 
804  function setLandscape()
805  {
806  $this->_orientation = 0;
807  }
808 
815  function setPaper($size = 0)
816  {
817  $this->_paper_size = $size;
818  }
819 
820 
828  function setHeader($string,$margin = 0.50)
829  {
830  if (strlen($string) >= 255) {
831  //carp 'Header string must be less than 255 characters';
832  return;
833  }
834  $this->_header = $string;
835  $this->_margin_head = $margin;
836  }
837 
845  function setFooter($string,$margin = 0.50)
846  {
847  if (strlen($string) >= 255) {
848  //carp 'Footer string must be less than 255 characters';
849  return;
850  }
851  $this->_footer = $string;
852  $this->_margin_foot = $margin;
853  }
854 
861  function centerHorizontally($center = 1)
862  {
863  $this->_hcenter = $center;
864  }
865 
872  function centerVertically($center = 1)
873  {
874  $this->_vcenter = $center;
875  }
876 
883  function setMargins($margin)
884  {
885  $this->setMarginLeft($margin);
886  $this->setMarginRight($margin);
887  $this->setMarginTop($margin);
888  $this->setMarginBottom($margin);
889  }
890 
897  function setMargins_LR($margin)
898  {
899  $this->setMarginLeft($margin);
900  $this->setMarginRight($margin);
901  }
902 
909  function setMargins_TB($margin)
910  {
911  $this->setMarginTop($margin);
912  $this->setMarginBottom($margin);
913  }
914 
921  function setMarginLeft($margin = 0.75)
922  {
923  $this->_margin_left = $margin;
924  }
925 
932  function setMarginRight($margin = 0.75)
933  {
934  $this->_margin_right = $margin;
935  }
936 
943  function setMarginTop($margin = 1.00)
944  {
945  $this->_margin_top = $margin;
946  }
947 
954  function setMarginBottom($margin = 1.00)
955  {
956  $this->_margin_bottom = $margin;
957  }
958 
966  function repeatRows($first_row, $last_row = NULL)
967  {
968  $this->title_rowmin = $first_row;
969  if (isset($last_row)) { //Second row is optional
970  $this->title_rowmax = $last_row;
971  }
972  else {
973  $this->title_rowmax = $first_row;
974  }
975  }
976 
984  function repeatColumns($first_col, $last_col = NULL)
985  {
986  $this->title_colmin = $first_col;
987  if (isset($last_col)) { // Second col is optional
988  $this->title_colmax = $last_col;
989  }
990  else {
991  $this->title_colmax = $first_col;
992  }
993  }
994 
1004  function printArea($first_row, $first_col, $last_row, $last_col)
1005  {
1006  $this->print_rowmin = $first_row;
1007  $this->print_colmin = $first_col;
1008  $this->print_rowmax = $last_row;
1009  $this->print_colmax = $last_col;
1010  }
1011 
1012 
1018  function hideGridlines()
1019  {
1020  $this->_print_gridlines = 0;
1021  }
1022 
1029  function printRowColHeaders($print = 1)
1030  {
1031  $this->_print_headers = $print;
1032  }
1033 
1043  function fitToPages($width, $height)
1044  {
1045  $this->_fit_page = 1;
1046  $this->_fit_width = $width;
1047  $this->_fit_height = $height;
1048  }
1049 
1057  function setHPagebreaks($breaks)
1058  {
1059  foreach($breaks as $break) {
1060  array_push($this->_hbreaks,$break);
1061  }
1062  }
1063 
1071  function setVPagebreaks($breaks)
1072  {
1073  foreach($breaks as $break) {
1074  array_push($this->_vbreaks,$break);
1075  }
1076  }
1077 
1078 
1085  function setZoom($scale = 100)
1086  {
1087  // Confine the scale to Excel's range
1088  if ($scale < 10 or $scale > 400)
1089  {
1090  $this->raiseError("Zoom factor $scale outside range: 10 <= zoom <= 400");
1091  $scale = 100;
1092  }
1093 
1094  $this->_zoom = floor($scale);
1095  }
1096 
1104  function setPrintScale($scale = 100)
1105  {
1106  // Confine the scale to Excel's range
1107  if ($scale < 10 or $scale > 400)
1108  {
1109  $this->raiseError("Print scale $scale outside range: 10 <= zoom <= 400");
1110  $scale = 100;
1111  }
1112 
1113  // Turn off "fit to page" option
1114  $this->_fit_page = 0;
1115 
1116  $this->_print_scale = floor($scale);
1117  }
1118 
1128  function write($row, $col, $token, $format = 0)
1129  {
1130  // Check for a cell reference in A1 notation and substitute row and column
1131  /*if ($_[0] =~ /^\D/) {
1132  @_ = $this->_substituteCellref(@_);
1133  }*/
1134 
1135 
1136  // Match number
1137  if (preg_match("/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/",$token)) {
1138  return $this->writeNumber($row,$col,$token,$format);
1139  }
1140  // Match http or ftp URL
1141  elseif (preg_match("/^[fh]tt?p:\/\//",$token)) {
1142  return $this->writeUrl($row, $col, $token, '', $format);
1143  }
1144  // Match mailto:
1145  elseif (preg_match("/^mailto:/",$token)) {
1146  return $this->writeUrl($row, $col, $token, '', $format);
1147  }
1148  // Match internal or external sheet link
1149  elseif (preg_match("/^(?:in|ex)ternal:/",$token)) {
1150  return $this->writeUrl($row, $col, $token, '', $format);
1151  }
1152  // Match formula
1153  elseif (preg_match("/^=/",$token)) {
1154  return $this->writeFormula($row, $col, $token, $format);
1155  }
1156  // Match formula
1157  elseif (preg_match("/^@/",$token)) {
1158  return $this->writeFormula($row, $col, $token, $format);
1159  }
1160  // Match blank
1161  elseif ($token == '') {
1162  return $this->writeBlank($row,$col,$format);
1163  }
1164  // Default: match string
1165  else {
1166  return $this->writeString($row,$col,$token,$format);
1167  }
1168  }
1169 
1181  function writeRow($row, $col, $val, $format=0)
1182  {
1183  $retval = '';
1184  if (is_array($val)) {
1185  foreach($val as $v) {
1186  if (is_array($v)) {
1187  $this->writeCol($row, $col, $v, $format);
1188  } else {
1189  $this->write($row, $col, $v, $format);
1190  }
1191  $col++;
1192  }
1193  } else {
1194  $retval = new PEAR_Error('$val needs to be an array');
1195  }
1196  return($retval);
1197  }
1198 
1210  function writeCol($row, $col, $val, $format=0)
1211  {
1212  $retval = '';
1213  if (is_array($val)) {
1214  foreach($val as $v) {
1215  $this->write($row, $col, $v, $format);
1216  $row++;
1217  }
1218  } else {
1219  $retval = new PEAR_Error('$val needs to be an array');
1220  }
1221  return($retval);
1222  }
1223 
1231  function _XF(&$format)
1232  {
1233  if ($format != 0) {
1234  return($format->getXfIndex());
1235  }
1236  else {
1237  return(0x0F);
1238  }
1239  }
1240 
1241 
1242  /******************************************************************************
1243  *******************************************************************************
1244  *
1245  * Internal methods
1246  */
1247 
1248 
1256  function _append($data)
1257  {
1258  if ($this->_using_tmpfile)
1259  {
1260  // Add CONTINUE records if necessary
1261  if (strlen($data) > $this->_limit) {
1262  $data = $this->_addContinue($data);
1263  }
1264  fwrite($this->_filehandle,$data);
1265  $this->_datasize += strlen($data);
1266  }
1267  else {
1269  }
1270  }
1271 
1282  function _substituteCellref($cell)
1283  {
1284  $cell = strtoupper($cell);
1285 
1286  // Convert a column range: 'A:A' or 'B:G'
1287  if (preg_match("/([A-I]?[A-Z]):([A-I]?[A-Z])/",$cell,$match)) {
1288  list($no_use, $col1) = $this->_cellToRowcol($match[1] .'1'); // Add a dummy row
1289  list($no_use, $col2) = $this->_cellToRowcol($match[2] .'1'); // Add a dummy row
1290  return(array($col1, $col2));
1291  }
1292 
1293  // Convert a cell range: 'A1:B7'
1294  if (preg_match("/\$?([A-I]?[A-Z]\$?\d+):\$?([A-I]?[A-Z]\$?\d+)/",$cell,$match)) {
1295  list($row1, $col1) = $this->_cellToRowcol($match[1]);
1296  list($row2, $col2) = $this->_cellToRowcol($match[2]);
1297  return(array($row1, $col1, $row2, $col2));
1298  }
1299 
1300  // Convert a cell reference: 'A1' or 'AD2000'
1301  if (preg_match("/\$?([A-I]?[A-Z]\$?\d+)/",$cell)) {
1302  list($row1, $col1) = $this->_cellToRowcol($match[1]);
1303  return(array($row1, $col1));
1304  }
1305 
1306  // TODO use real error codes
1307  $this->raiseError("Unknown cell reference $cell", 0, PEAR_ERROR_DIE);
1308  }
1309 
1318  function _cellToRowcol($cell)
1319  {
1320  preg_match("/\$?([A-I]?[A-Z])\$?(\d+)/",$cell,$match);
1321  $col = $match[1];
1322  $row = $match[2];
1323 
1324  // Convert base26 column string to number
1325  $chars = split('', $col);
1326  $expn = 0;
1327  $col = 0;
1328 
1329  while ($chars) {
1330  $char = array_pop($chars); // LS char first
1331  $col += (ord($char) -ord('A') +1) * pow(26,$expn);
1332  $expn++;
1333  }
1334 
1335  // Convert 1-index to zero-index
1336  $row--;
1337  $col--;
1338 
1339  return(array($row, $col));
1340  }
1341 
1349  function _encodePassword($plaintext)
1350  {
1351  $password = 0x0000;
1352  $i = 1; // char position
1353 
1354  // split the plain text password in its component characters
1355  $chars = preg_split('//', $plaintext, -1, PREG_SPLIT_NO_EMPTY);
1356  foreach($chars as $char)
1357  {
1358  $value = ord($char) << $i; // shifted ASCII value
1359  $rotated_bits = $value >> 15; // rotated bits beyond bit 15
1360  $value &= 0x7fff; // first 15 bits
1361  $password ^= ($value | $rotated_bits);
1362  $i++;
1363  }
1364 
1365  $password ^= strlen($plaintext);
1366  $password ^= 0xCE4B;
1367 
1368  return($password);
1369  }
1370 
1380  function setOutline($visible = true, $symbols_below = true, $symbols_right = true, $auto_style = false)
1381  {
1382  $this->_outline_on = $visible;
1383  $this->_outline_below = $symbols_below;
1384  $this->_outline_right = $symbols_right;
1385  $this->_outline_style = $auto_style;
1386 
1387  // Ensure this is a boolean vale for Window2
1388  if ($this->_outline_on) {
1389  $this->_outline_on = 1;
1390  }
1391  }
1392 
1393  /******************************************************************************
1394  *******************************************************************************
1395  *
1396  * BIFF RECORDS
1397  */
1398 
1399 
1415  function writeNumber($row, $col, $num, $format = 0)
1416  {
1417  $record = 0x0203; // Record identifier
1418  $length = 0x000E; // Number of bytes to follow
1419 
1420  $xf = $this->_XF($format); // The cell format
1421 
1422  // Check that row and col are valid and store max and min values
1423  if ($row >= $this->_xls_rowmax)
1424  {
1425  return(-2);
1426  }
1427  if ($col >= $this->_xls_colmax)
1428  {
1429  return(-2);
1430  }
1431  if ($row < $this->_dim_rowmin)
1432  {
1433  $this->_dim_rowmin = $row;
1434  }
1435  if ($row > $this->_dim_rowmax)
1436  {
1437  $this->_dim_rowmax = $row;
1438  }
1439  if ($col < $this->_dim_colmin)
1440  {
1441  $this->_dim_colmin = $col;
1442  }
1443  if ($col > $this->_dim_colmax)
1444  {
1445  $this->_dim_colmax = $col;
1446  }
1447 
1448  $header = pack("vv", $record, $length);
1449  $data = pack("vvv", $row, $col, $xf);
1450  $xl_double = pack("d", $num);
1451  if ($this->_byte_order) // if it's Big Endian
1452  {
1453  $xl_double = strrev($xl_double);
1454  }
1455 
1456  $this->_append($header.$data.$xl_double);
1457  return(0);
1458  }
1459 
1475  function writeString($row, $col, $str, $format = 0)
1476  {
1477  if ($this->_BIFF_version == 0x0600) {
1478  return $this->writeStringBIFF8($row, $col, $str, $format);
1479  }
1480  $strlen = strlen($str);
1481  $record = 0x0204; // Record identifier
1482  $length = 0x0008 + $strlen; // Bytes to follow
1483  $xf = $this->_XF($format); // The cell format
1484 
1485  $str_error = 0;
1486 
1487  // Check that row and col are valid and store max and min values
1488  if ($row >= $this->_xls_rowmax)
1489  {
1490  return(-2);
1491  }
1492  if ($col >= $this->_xls_colmax)
1493  {
1494  return(-2);
1495  }
1496  if ($row < $this->_dim_rowmin)
1497  {
1498  $this->_dim_rowmin = $row;
1499  }
1500  if ($row > $this->_dim_rowmax)
1501  {
1502  $this->_dim_rowmax = $row;
1503  }
1504  if ($col < $this->_dim_colmin)
1505  {
1506  $this->_dim_colmin = $col;
1507  }
1508  if ($col > $this->_dim_colmax)
1509  {
1510  $this->_dim_colmax = $col;
1511  }
1512 
1513  if ($strlen > $this->_xls_strmax) // LABEL must be < 255 chars
1514  {
1515  $str = substr($str, 0, $this->_xls_strmax);
1516  $length = 0x0008 + $this->_xls_strmax;
1517  $strlen = $this->_xls_strmax;
1518  $str_error = -3;
1519  }
1520 
1521  $header = pack("vv", $record, $length);
1522  $data = pack("vvvv", $row, $col, $xf, $strlen);
1523  $this->_append($header.$data.$str);
1524  return($str_error);
1525  }
1526 
1527  function writeStringBIFF8($row, $col, $str, $format = 0)
1528  {
1529  $strlen = strlen($str);
1530  $record = 0x00FD; // Record identifier
1531  $length = 0x000A; // Bytes to follow
1532  $xf = $this->_XF($format); // The cell format
1533  $encoding = 0x0;
1534 
1535  $str_error = 0;
1536 
1537  // Check that row and col are valid and store max and min values
1538  if ($this->_checkRowCol($row, $col) == false) {
1539  return -2;
1540  }
1541 
1542  $str = pack('vC', $strlen, $encoding).$str;
1543 
1544  /* check if string is already present */
1545  if (!isset($this->_str_table[$str])) {
1546  $this->_str_table[$str] = $this->_str_unique++;
1547  }
1548  $this->_str_total++;
1549 
1550  $header = pack('vv', $record, $length);
1551  $data = pack('vvvV', $row, $col, $xf, $this->_str_table[$str]);
1552  $this->_append($header.$data);
1553  return $str_error;
1554  }
1555 
1566  function _checkRowCol($row, $col)
1567  {
1568  if ($row >= $this->_xls_rowmax) {
1569  return false;
1570  }
1571  if ($col >= $this->_xls_colmax) {
1572  return false;
1573  }
1574  if ($row < $this->_dim_rowmin) {
1575  $this->_dim_rowmin = $row;
1576  }
1577  if ($row > $this->_dim_rowmax) {
1578  $this->_dim_rowmax = $row;
1579  }
1580  if ($col < $this->_dim_colmin) {
1581  $this->_dim_colmin = $col;
1582  }
1583  if ($col > $this->_dim_colmax) {
1584  $this->_dim_colmax = $col;
1585  }
1586  return true;
1587  }
1588 
1598  function writeNote($row, $col, $note)
1599  {
1600  $note_length = strlen($note);
1601  $record = 0x001C; // Record identifier
1602  $max_length = 2048; // Maximun length for a NOTE record
1603  //$length = 0x0006 + $note_length; // Bytes to follow
1604 
1605  // Check that row and col are valid and store max and min values
1606  if ($row >= $this->_xls_rowmax)
1607  {
1608  return(-2);
1609  }
1610  if ($col >= $this->_xls_colmax)
1611  {
1612  return(-2);
1613  }
1614  if ($row < $this->_dim_rowmin)
1615  {
1616  $this->_dim_rowmin = $row;
1617  }
1618  if ($row > $this->_dim_rowmax)
1619  {
1620  $this->_dim_rowmax = $row;
1621  }
1622  if ($col < $this->_dim_colmin)
1623  {
1624  $this->_dim_colmin = $col;
1625  }
1626  if ($col > $this->_dim_colmax)
1627  {
1628  $this->_dim_colmax = $col;
1629  }
1630 
1631  // Length for this record is no more than 2048 + 6
1632  $length = 0x0006 + min($note_length, 2048);
1633  $header = pack("vv", $record, $length);
1634  $data = pack("vvv", $row, $col, $note_length);
1635  $this->_append($header.$data.substr($note, 0, 2048));
1636 
1637  for($i = $max_length; $i < $note_length; $i += $max_length)
1638  {
1639  $chunk = substr($note, $i, $max_length);
1640  $length = 0x0006 + strlen($chunk);
1641  $header = pack("vv", $record, $length);
1642  $data = pack("vvv", -1, 0, strlen($chunk));
1643  $this->_append($header.$data.$chunk);
1644  }
1645  return(0);
1646  }
1647 
1665  function writeBlank($row, $col, $format)
1666  {
1667  // Don't write a blank cell unless it has a format
1668  if ($format == 0)
1669  {
1670  return(0);
1671  }
1672 
1673  $record = 0x0201; // Record identifier
1674  $length = 0x0006; // Number of bytes to follow
1675  $xf = $this->_XF($format); // The cell format
1676 
1677  // Check that row and col are valid and store max and min values
1678  if ($row >= $this->_xls_rowmax)
1679  {
1680  return(-2);
1681  }
1682  if ($col >= $this->_xls_colmax)
1683  {
1684  return(-2);
1685  }
1686  if ($row < $this->_dim_rowmin)
1687  {
1688  $this->_dim_rowmin = $row;
1689  }
1690  if ($row > $this->_dim_rowmax)
1691  {
1692  $this->_dim_rowmax = $row;
1693  }
1694  if ($col < $this->_dim_colmin)
1695  {
1696  $this->_dim_colmin = $col;
1697  }
1698  if ($col > $this->_dim_colmax)
1699  {
1700  $this->_dim_colmax = $col;
1701  }
1702 
1703  $header = pack("vv", $record, $length);
1704  $data = pack("vvv", $row, $col, $xf);
1705  $this->_append($header.$data);
1706  return 0;
1707  }
1708 
1725  function writeFormula($row, $col, $formula, $format = 0)
1726  {
1727  $record = 0x0006; // Record identifier
1728 
1729  // Excel normally stores the last calculated value of the formula in $num.
1730  // Clearly we are not in a position to calculate this a priori. Instead
1731  // we set $num to zero and set the option flags in $grbit to ensure
1732  // automatic calculation of the formula when the file is opened.
1733  //
1734  $xf = $this->_XF($format); // The cell format
1735  $num = 0x00; // Current value of formula
1736  $grbit = 0x03; // Option flags
1737  $unknown = 0x0000; // Must be zero
1738 
1739 
1740  // Check that row and col are valid and store max and min values
1741  if ($this->_checkRowCol($row, $col) == false) {
1742  return -2;
1743  }
1744 
1745  // Strip the '=' or '@' sign at the beginning of the formula string
1746  if (preg_match("/^=/",$formula)) {
1747  $formula = preg_replace("/(^=)/","",$formula);
1748  }
1749  elseif (preg_match("/^@/",$formula)) {
1750  $formula = preg_replace("/(^@)/","",$formula);
1751  }
1752  else
1753  {
1754  // Error handling
1755  $this->writeString($row, $col, 'Unrecognised character for formula');
1756  return -1;
1757  }
1758 
1759  // Parse the formula using the parser in Parser.php
1760  $error = $this->_parser->parse($formula);
1761  if ($this->isError($error))
1762  {
1763  $this->writeString($row, $col, $error->getMessage());
1764  return -1;
1765  }
1766 
1767  $formula = $this->_parser->toReversePolish();
1768  if ($this->isError($formula))
1769  {
1770  $this->writeString($row, $col, $formula->getMessage());
1771  return -1;
1772  }
1773 
1774  $formlen = strlen($formula); // Length of the binary string
1775  $length = 0x16 + $formlen; // Length of the record data
1776 
1777  $header = pack("vv", $record, $length);
1778  $data = pack("vvvdvVv", $row, $col, $xf, $num,
1779  $grbit, $unknown, $formlen);
1780 
1781  $this->_append($header.$data.$formula);
1782  return 0;
1783  }
1784 
1808  function writeUrl($row, $col, $url, $string = '', $format = 0)
1809  {
1810  // Add start row and col to arg list
1811  return($this->_writeUrlRange($row, $col, $row, $col, $url, $string, $format));
1812  }
1813 
1832  function _writeUrlRange($row1, $col1, $row2, $col2, $url, $string = '', $format = 0)
1833  {
1834 
1835  // Check for internal/external sheet links or default to web link
1836  if (preg_match('[^internal:]', $url)) {
1837  return($this->_writeUrlInternal($row1, $col1, $row2, $col2, $url, $string, $format));
1838  }
1839  if (preg_match('[^external:]', $url)) {
1840  return($this->_writeUrlExternal($row1, $col1, $row2, $col2, $url, $string, $format));
1841  }
1842  return($this->_writeUrlWeb($row1, $col1, $row2, $col2, $url, $string, $format));
1843  }
1844 
1845 
1862  function _writeUrlWeb($row1, $col1, $row2, $col2, $url, $str, $format = 0)
1863  {
1864  $record = 0x01B8; // Record identifier
1865  $length = 0x00000; // Bytes to follow
1866 
1867  if ($format == 0) {
1868  $format = $this->_url_format;
1869  }
1870 
1871  // Write the visible label using the writeString() method.
1872  if ($str == '') {
1873  $str = $url;
1874  }
1875  $str_error = $this->writeString($row1, $col1, $str, $format);
1876  if (($str_error == -2) or ($str_error == -3)) {
1877  return $str_error;
1878  }
1879 
1880  // Pack the undocumented parts of the hyperlink stream
1881  $unknown1 = pack("H*", "D0C9EA79F9BACE118C8200AA004BA90B02000000");
1882  $unknown2 = pack("H*", "E0C9EA79F9BACE118C8200AA004BA90B");
1883 
1884  // Pack the option flags
1885  $options = pack("V", 0x03);
1886 
1887  // Convert URL to a null terminated wchar string
1888  $url = join("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));
1889  $url = $url . "\0\0\0";
1890 
1891  // Pack the length of the URL
1892  $url_len = pack("V", strlen($url));
1893 
1894  // Calculate the data length
1895  $length = 0x34 + strlen($url);
1896 
1897  // Pack the header data
1898  $header = pack("vv", $record, $length);
1899  $data = pack("vvvv", $row1, $row2, $col1, $col2);
1900 
1901  // Write the packed data
1902  $this->_append( $header. $data.
1903  $unknown1. $options.
1904  $unknown2. $url_len. $url);
1905  return($str_error);
1906  }
1907 
1922  function _writeUrlInternal($row1, $col1, $row2, $col2, $url, $str, $format = 0)
1923  {
1924  $record = 0x01B8; // Record identifier
1925  $length = 0x00000; // Bytes to follow
1926 
1927  if ($format == 0) {
1928  $format = $this->_url_format;
1929  }
1930 
1931  // Strip URL type
1932  $url = preg_replace('s[^internal:]', '', $url);
1933 
1934  // Write the visible label
1935  if ($str == '') {
1936  $str = $url;
1937  }
1938  $str_error = $this->writeString($row1, $col1, $str, $format);
1939  if (($str_error == -2) or ($str_error == -3)) {
1940  return $str_error;
1941  }
1942 
1943  // Pack the undocumented parts of the hyperlink stream
1944  $unknown1 = pack("H*", "D0C9EA79F9BACE118C8200AA004BA90B02000000");
1945 
1946  // Pack the option flags
1947  $options = pack("V", 0x08);
1948 
1949  // Convert the URL type and to a null terminated wchar string
1950  $url = join("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));
1951  $url = $url . "\0\0\0";
1952 
1953  // Pack the length of the URL as chars (not wchars)
1954  $url_len = pack("V", floor(strlen($url)/2));
1955 
1956  // Calculate the data length
1957  $length = 0x24 + strlen($url);
1958 
1959  // Pack the header data
1960  $header = pack("vv", $record, $length);
1961  $data = pack("vvvv", $row1, $row2, $col1, $col2);
1962 
1963  // Write the packed data
1964  $this->_append($header. $data.
1965  $unknown1. $options.
1966  $url_len. $url);
1967  return($str_error);
1968  }
1969 
1988  function _writeUrlExternal($row1, $col1, $row2, $col2, $url, $str, $format = 0)
1989  {
1990  // Network drives are different. We will handle them separately
1991  // MS/Novell network drives and shares start with \\
1992  if (preg_match('[^external:\\\\]', $url)) {
1993  return; //($this->_writeUrlExternal_net($row1, $col1, $row2, $col2, $url, $str, $format));
1994  }
1995 
1996  $record = 0x01B8; // Record identifier
1997  $length = 0x00000; // Bytes to follow
1998 
1999  if ($format == 0) {
2000  $format = $this->_url_format;
2001  }
2002 
2003  // Strip URL type and change Unix dir separator to Dos style (if needed)
2004  //
2005  $url = preg_replace('[^external:]', '', $url);
2006  $url = preg_replace('[/]', "\\", $url);
2007 
2008  // Write the visible label
2009  if ($str == '') {
2010  $str = preg_replace('[\#]', ' - ', $url);
2011  }
2012  $str_error = $this->writeString($row1, $col1, $str, $format);
2013  if (($str_error == -2) or ($str_error == -3)) {
2014  return $str_error;
2015  }
2016 
2017  // Determine if the link is relative or absolute:
2018  // relative if link contains no dir separator, "somefile.xls"
2019  // relative if link starts with up-dir, "..\..\somefile.xls"
2020  // otherwise, absolute
2021 
2022  $absolute = 0x02; // Bit mask
2023  if (!preg_match('[\\]', $url)) {
2024  $absolute = 0x00;
2025  }
2026  if (preg_match('[^\.\.\\]', $url)) {
2027  $absolute = 0x00;
2028  }
2029 
2030  // Determine if the link contains a sheet reference and change some of the
2031  // parameters accordingly.
2032  // Split the dir name and sheet name (if it exists)
2033  list($dir_long , $sheet) = split('/\#/', $url);
2035 
2036  if (isset($sheet)) {
2037  $link_type |= 0x08;
2038  $sheet_len = pack("V", strlen($sheet) + 0x01);
2039  $sheet = join("\0", split('', $sheet));
2040  $sheet .= "\0\0\0";
2041  }
2042  else {
2043  $sheet_len = '';
2044  $sheet = '';
2045  }
2046 
2047  // Pack the link type
2048  $link_type = pack("V", $link_type);
2049 
2050  // Calculate the up-level dir count e.g.. (..\..\..\ == 3)
2051  $up_count = preg_match_all("/\.\.\\/", $dir_long, $useless);
2052  $up_count = pack("v", $up_count);
2053 
2054  // Store the short dos dir name (null terminated)
2055  $dir_short = preg_replace('/\.\.\\/', '', $dir_long) . "\0";
2056 
2057  // Store the long dir name as a wchar string (non-null terminated)
2058  $dir_long = join("\0", split('', $dir_long));
2059  $dir_long = $dir_long . "\0";
2060 
2061  // Pack the lengths of the dir strings
2062  $dir_short_len = pack("V", strlen($dir_short) );
2063  $dir_long_len = pack("V", strlen($dir_long) );
2064  $stream_len = pack("V", strlen($dir_long) + 0x06);
2065 
2066  // Pack the undocumented parts of the hyperlink stream
2067  $unknown1 = pack("H*",'D0C9EA79F9BACE118C8200AA004BA90B02000000' );
2068  $unknown2 = pack("H*",'0303000000000000C000000000000046' );
2069  $unknown3 = pack("H*",'FFFFADDE000000000000000000000000000000000000000');
2070  $unknown4 = pack("v", 0x03 );
2071 
2072  // Pack the main data stream
2073  $data = pack("vvvv", $row1, $row2, $col1, $col2) .
2074  $unknown1 .
2075  $link_type .
2076  $unknown2 .
2077  $up_count .
2079  $dir_short .
2080  $unknown3 .
2081  $stream_len .
2082  $dir_long_len .
2083  $unknown4 .
2084  $dir_long .
2085  $sheet_len .
2086  $sheet ;
2087 
2088  // Pack the header data
2089  $length = strlen($data);
2090  $header = pack("vv", $record, $length);
2091 
2092  // Write the packed data
2093  $this->_append($header. $data);
2094  return($str_error);
2095  }
2096 
2097 
2109  function setRow($row, $height, $format = 0, $hidden = false, $level = 0)
2110  {
2111  $record = 0x0208; // Record identifier
2112  $length = 0x0010; // Number of bytes to follow
2113 
2114  $colMic = 0x0000; // First defined column
2115  $colMac = 0x0000; // Last defined column
2116  $irwMac = 0x0000; // Used by Excel to optimise loading
2117  $reserved = 0x0000; // Reserved
2118  $grbit = 0x0000; // Option flags
2119  $ixfe = $this->_XF($format); // XF index
2120 
2121  // Use setRow($row, NULL, $XF) to set XF format without setting height
2122  if ($height != NULL) {
2123  $miyRw = $height * 20; // row height
2124  }
2125  else {
2126  $miyRw = 0xff; // default row height is 256
2127  }
2128 
2129  $level = max(0, min($level, 7)); // level should be between 0 and 7
2130  $this->_outline_row_level = max($level, $this->_outline_row_level);
2131 
2132 
2133  // Set the options flags. fUnsynced is used to show that the font and row
2134  // heights are not compatible. This is usually the case for WriteExcel.
2135  // The collapsed flag 0x10 doesn't seem to be used to indicate that a row
2136  // is collapsed. Instead it is used to indicate that the previous row is
2137  // collapsed. The zero height flag, 0x20, is used to collapse a row.
2138 
2139  $grbit |= $level;
2140  if ($hidden) {
2141  $grbit |= 0x0020;
2142  }
2143  $grbit |= 0x0040; // fUnsynced
2144  if ($format) {
2145  $grbit |= 0x0080;
2146  }
2147  $grbit |= 0x0100;
2148 
2149  $header = pack("vv", $record, $length);
2150  $data = pack("vvvvvvvv", $row, $colMic, $colMac, $miyRw,
2151  $irwMac,$reserved, $grbit, $ixfe);
2152  $this->_append($header.$data);
2153  }
2154 
2160  function _storeDimensions()
2161  {
2162  $record = 0x0200; // Record identifier
2163  $row_min = $this->_dim_rowmin; // First row
2164  $row_max = $this->_dim_rowmax + 1; // Last row plus 1
2165  $col_min = $this->_dim_colmin; // First column
2166  $col_max = $this->_dim_colmax + 1; // Last column plus 1
2167  $reserved = 0x0000; // Reserved by Excel
2168 
2169  if ($this->_BIFF_version == 0x0500) {
2170  $length = 0x000A; // Number of bytes to follow
2171  $data = pack("vvvvv", $row_min, $row_max,
2172  $col_min, $col_max, $reserved);
2173  }
2174  elseif ($this->_BIFF_version == 0x0600) {
2175  $length = 0x000E;
2176  $data = pack("VVvvv", $row_min, $row_max,
2177  $col_min, $col_max, $reserved);
2178  }
2179  $header = pack("vv", $record, $length);
2180  $this->_prepend($header.$data);
2181  }
2182 
2188  function _storeWindow2()
2189  {
2190  $record = 0x023E; // Record identifier
2191  if ($this->_BIFF_version == 0x0500) {
2192  $length = 0x000A; // Number of bytes to follow
2193  }
2194  elseif ($this->_BIFF_version == 0x0600) {
2195  $length = 0x0012;
2196  }
2197 
2198  $grbit = 0x00B6; // Option flags
2199  $rwTop = 0x0000; // Top row visible in window
2200  $colLeft = 0x0000; // Leftmost column visible in window
2201 
2202 
2203  // The options flags that comprise $grbit
2204  $fDspFmla = 0; // 0 - bit
2205  $fDspGrid = 1; // 1
2206  $fDspRwCol = 1; // 2
2207  $fFrozen = $this->_frozen; // 3
2208  $fDspZeros = 1; // 4
2209  $fDefaultHdr = 1; // 5
2210  $fArabic = 0; // 6
2211  $fDspGuts = $this->_outline_on; // 7
2212  $fFrozenNoSplit = 0; // 0 - bit
2213  $fSelected = $this->selected; // 1
2214  $fPaged = 1; // 2
2215 
2216  $grbit = $fDspFmla;
2217  $grbit |= $fDspGrid << 1;
2218  $grbit |= $fDspRwCol << 2;
2219  $grbit |= $fFrozen << 3;
2220  $grbit |= $fDspZeros << 4;
2221  $grbit |= $fDefaultHdr << 5;
2222  $grbit |= $fArabic << 6;
2223  $grbit |= $fDspGuts << 7;
2224  $grbit |= $fFrozenNoSplit << 8;
2225  $grbit |= $fSelected << 9;
2226  $grbit |= $fPaged << 10;
2227 
2228  $header = pack("vv", $record, $length);
2229  $data = pack("vvv", $grbit, $rwTop, $colLeft);
2230  // FIXME !!!
2231  if ($this->_BIFF_version == 0x0500) {
2232  $rgbHdr = 0x00000000; // Row/column heading and gridline color
2233  $data .= pack("V", $rgbHdr);
2234  }
2235  elseif ($this->_BIFF_version == 0x0600) {
2236  $rgbHdr = 0x0040; // Row/column heading and gridline color index
2237  $zoom_factor_page_break = 0x0000;
2238  $zoom_factor_normal = 0x0000;
2239  $data .= pack("vvvvV", $rgbHdr, 0x0000, $zoom_factor_page_break, $zoom_factor_normal, 0x00000000);
2240  }
2241  $this->_append($header.$data);
2242  }
2243 
2249  function _storeDefcol()
2250  {
2251  $record = 0x0055; // Record identifier
2252  $length = 0x0002; // Number of bytes to follow
2253  $colwidth = 0x0008; // Default column width
2254 
2255  $header = pack("vv", $record, $length);
2256  $data = pack("v", $colwidth);
2257  $this->_prepend($header.$data);
2258  }
2259 
2275  function _storeColinfo($col_array)
2276  {
2277  if (isset($col_array[0])) {
2278  $colFirst = $col_array[0];
2279  }
2280  if (isset($col_array[1])) {
2281  $colLast = $col_array[1];
2282  }
2283  if (isset($col_array[2])) {
2284  $coldx = $col_array[2];
2285  }
2286  else {
2287  $coldx = 8.43;
2288  }
2289  if (isset($col_array[3])) {
2290  $format = $col_array[3];
2291  }
2292  else {
2293  $format = 0;
2294  }
2295  if (isset($col_array[4])) {
2296  $grbit = $col_array[4];
2297  }
2298  else {
2299  $grbit = 0;
2300  }
2301  if (isset($col_array[5])) {
2302  $level = $col_array[5];
2303  }
2304  else {
2305  $level = 0;
2306  }
2307  $record = 0x007D; // Record identifier
2308  $length = 0x000B; // Number of bytes to follow
2309 
2310  $coldx += 0.72; // Fudge. Excel subtracts 0.72 !?
2311  $coldx *= 256; // Convert to units of 1/256 of a char
2312 
2313  $ixfe = $this->_XF($format);
2314  $reserved = 0x00; // Reserved
2315 
2316  $level = max(0, min($level, 7));
2317  $grbit |= $level << 8;
2318 
2319  $header = pack("vv", $record, $length);
2320  $data = pack("vvvvvC", $colFirst, $colLast, $coldx,
2321  $ixfe, $grbit, $reserved);
2322  $this->_prepend($header.$data);
2323  }
2324 
2332  function _storeSelection($array)
2333  {
2334  list($rwFirst,$colFirst,$rwLast,$colLast) = $array;
2335  $record = 0x001D; // Record identifier
2336  $length = 0x000F; // Number of bytes to follow
2337 
2338  $pnn = $this->_active_pane; // Pane position
2339  $rwAct = $rwFirst; // Active row
2340  $colAct = $colFirst; // Active column
2341  $irefAct = 0; // Active cell ref
2342  $cref = 1; // Number of refs
2343 
2344  if (!isset($rwLast)) {
2345  $rwLast = $rwFirst; // Last row in reference
2346  }
2347  if (!isset($colLast)) {
2348  $colLast = $colFirst; // Last col in reference
2349  }
2350 
2351  // Swap last row/col for first row/col as necessary
2352  if ($rwFirst > $rwLast)
2353  {
2354  list($rwFirst, $rwLast) = array($rwLast, $rwFirst);
2355  }
2356 
2357  if ($colFirst > $colLast)
2358  {
2359  list($colFirst, $colLast) = array($colLast, $colFirst);
2360  }
2361 
2362  $header = pack("vv", $record, $length);
2363  $data = pack("CvvvvvvCC", $pnn, $rwAct, $colAct,
2364  $irefAct, $cref,
2365  $rwFirst, $rwLast,
2366  $colFirst, $colLast);
2367  $this->_append($header.$data);
2368  }
2369 
2375  function _storeMergedCells()
2376  {
2377  // if there are no merged cell ranges set, return
2378  if (count($this->_merged_ranges) == 0) {
2379  return;
2380  }
2381  $record = 0x00E5;
2382  $length = 2 + count($this->_merged_ranges) * 8;
2383 
2384  $header = pack('vv', $record, $length);
2385  $data = pack('v', count($this->_merged_ranges));
2386  foreach ($this->_merged_ranges as $range) {
2387  $data .= pack('vvvv', $range[0], $range[2], $range[1], $range[3]);
2388  }
2389  $this->_append($header.$data);
2390  }
2391 
2405  function _storeExterncount($count)
2406  {
2407  $record = 0x0016; // Record identifier
2408  $length = 0x0002; // Number of bytes to follow
2409 
2410  $header = pack("vv", $record, $length);
2411  $data = pack("v", $count);
2412  $this->_prepend($header.$data);
2413  }
2414 
2424  function _storeExternsheet($sheetname)
2425  {
2426  $record = 0x0017; // Record identifier
2427 
2428  // References to the current sheet are encoded differently to references to
2429  // external sheets.
2430  //
2431  if ($this->name == $sheetname) {
2432  $sheetname = '';
2433  $length = 0x02; // The following 2 bytes
2434  $cch = 1; // The following byte
2435  $rgch = 0x02; // Self reference
2436  }
2437  else {
2438  $length = 0x02 + strlen($sheetname);
2439  $cch = strlen($sheetname);
2440  $rgch = 0x03; // Reference to a sheet in the current workbook
2441  }
2442 
2443  $header = pack("vv", $record, $length);
2444  $data = pack("CC", $cch, $rgch);
2445  $this->_prepend($header.$data.$sheetname);
2446  }
2447 
2462  function _storePanes($panes)
2463  {
2464  $y = $panes[0];
2465  $x = $panes[1];
2466  $rwTop = $panes[2];
2467  $colLeft = $panes[3];
2468  if (count($panes) > 4) { // if Active pane was received
2469  $pnnAct = $panes[4];
2470  }
2471  else {
2472  $pnnAct = NULL;
2473  }
2474  $record = 0x0041; // Record identifier
2475  $length = 0x000A; // Number of bytes to follow
2476 
2477  // Code specific to frozen or thawed panes.
2478  if ($this->_frozen)
2479  {
2480  // Set default values for $rwTop and $colLeft
2481  if (!isset($rwTop)) {
2482  $rwTop = $y;
2483  }
2484  if (!isset($colLeft)) {
2485  $colLeft = $x;
2486  }
2487  }
2488  else
2489  {
2490  // Set default values for $rwTop and $colLeft
2491  if (!isset($rwTop)) {
2492  $rwTop = 0;
2493  }
2494  if (!isset($colLeft)) {
2495  $colLeft = 0;
2496  }
2497 
2498  // Convert Excel's row and column units to the internal units.
2499  // The default row height is 12.75
2500  // The default column width is 8.43
2501  // The following slope and intersection values were interpolated.
2502  //
2503  $y = 20*$y + 255;
2504  $x = 113.879*$x + 390;
2505  }
2506 
2507 
2508  // Determine which pane should be active. There is also the undocumented
2509  // option to override this should it be necessary: may be removed later.
2510  //
2511  if (!isset($pnnAct))
2512  {
2513  if ($x != 0 and $y != 0)
2514  $pnnAct = 0; // Bottom right
2515  if ($x != 0 and $y == 0)
2516  $pnnAct = 1; // Top right
2517  if ($x == 0 and $y != 0)
2518  $pnnAct = 2; // Bottom left
2519  if ($x == 0 and $y == 0)
2520  $pnnAct = 3; // Top left
2521  }
2522 
2523  $this->_active_pane = $pnnAct; // Used in _storeSelection
2524 
2525  $header = pack("vv", $record, $length);
2526  $data = pack("vvvvv", $x, $y, $rwTop, $colLeft, $pnnAct);
2527  $this->_append($header.$data);
2528  }
2529 
2535  function _storeSetup()
2536  {
2537  $record = 0x00A1; // Record identifier
2538  $length = 0x0022; // Number of bytes to follow
2539 
2540  $iPaperSize = $this->_paper_size; // Paper size
2541  $iScale = $this->_print_scale; // Print scaling factor
2542  $iPageStart = 0x01; // Starting page number
2543  $iFitWidth = $this->_fit_width; // Fit to number of pages wide
2544  $iFitHeight = $this->_fit_height; // Fit to number of pages high
2545  $grbit = 0x00; // Option flags
2546  $iRes = 0x0258; // Print resolution
2547  $iVRes = 0x0258; // Vertical print resolution
2548  $numHdr = $this->_margin_head; // Header Margin
2549  $numFtr = $this->_margin_foot; // Footer Margin
2550  $iCopies = 0x01; // Number of copies
2551 
2552  $fLeftToRight = 0x0; // Print over then down
2553  $fLandscape = $this->_orientation; // Page orientation
2554  $fNoPls = 0x0; // Setup not read from printer
2555  $fNoColor = 0x0; // Print black and white
2556  $fDraft = 0x0; // Print draft quality
2557  $fNotes = 0x0; // Print notes
2558  $fNoOrient = 0x0; // Orientation not set
2559  $fUsePage = 0x0; // Use custom starting page
2560 
2561  $grbit = $fLeftToRight;
2562  $grbit |= $fLandscape << 1;
2563  $grbit |= $fNoPls << 2;
2564  $grbit |= $fNoColor << 3;
2565  $grbit |= $fDraft << 4;
2566  $grbit |= $fNotes << 5;
2567  $grbit |= $fNoOrient << 6;
2568  $grbit |= $fUsePage << 7;
2569 
2570  $numHdr = pack("d", $numHdr);
2571  $numFtr = pack("d", $numFtr);
2572  if ($this->_byte_order) // if it's Big Endian
2573  {
2574  $numHdr = strrev($numHdr);
2575  $numFtr = strrev($numFtr);
2576  }
2577 
2578  $header = pack("vv", $record, $length);
2579  $data1 = pack("vvvvvvvv", $iPaperSize,
2580  $iScale,
2581  $iPageStart,
2582  $iFitWidth,
2583  $iFitHeight,
2584  $grbit,
2585  $iRes,
2586  $iVRes);
2587  $data2 = $numHdr.$numFtr;
2588  $data3 = pack("v", $iCopies);
2589  $this->_prepend($header.$data1.$data2.$data3);
2590  }
2591 
2597  function _storeHeader()
2598  {
2599  $record = 0x0014; // Record identifier
2600 
2601  $str = $this->_header; // header string
2602  $cch = strlen($str); // Length of header string
2603  if ($this->_BIFF_version == 0x0600) {
2604  $encoding = 0x0; // TODO: Unicode support
2605  $length = 3 + $cch; // Bytes to follow
2606  }
2607  else {
2608  $length = 1 + $cch; // Bytes to follow
2609  }
2610  $header = pack("vv", $record, $length);
2611  if ($this->_BIFF_version == 0x0600) {
2612  $data = pack("vC", $cch, $encoding);
2613  }
2614  else {
2615  $data = pack("C", $cch);
2616  }
2617 
2618  $this->_append($header.$data.$str);
2619  }
2620 
2626  function _storeFooter()
2627  {
2628  $record = 0x0015; // Record identifier
2629 
2630  $str = $this->_footer; // Footer string
2631  $cch = strlen($str); // Length of footer string
2632  if ($this->_BIFF_version == 0x0600) {
2633  $encoding = 0x0; // TODO: Unicode support
2634  $length = 3 + $cch; // Bytes to follow
2635  }
2636  else {
2637  $length = 1 + $cch;
2638  }
2639  $header = pack("vv", $record, $length);
2640  if ($this->_BIFF_version == 0x0600) {
2641  $data = pack("vC", $cch, $encoding);
2642  }
2643  else {
2644  $data = pack("C", $cch);
2645  }
2646 
2647  $this->_append($header.$data.$str);
2648  }
2649 
2655  function _storeHcenter()
2656  {
2657  $record = 0x0083; // Record identifier
2658  $length = 0x0002; // Bytes to follow
2659 
2660  $fHCenter = $this->_hcenter; // Horizontal centering
2661 
2662  $header = pack("vv", $record, $length);
2663  $data = pack("v", $fHCenter);
2664 
2665  $this->_append($header.$data);
2666  }
2667 
2673  function _storeVcenter()
2674  {
2675  $record = 0x0084; // Record identifier
2676  $length = 0x0002; // Bytes to follow
2677 
2678  $fVCenter = $this->_vcenter; // Horizontal centering
2679 
2680  $header = pack("vv", $record, $length);
2681  $data = pack("v", $fVCenter);
2682  $this->_append($header.$data);
2683  }
2684 
2690  function _storeMarginLeft()
2691  {
2692  $record = 0x0026; // Record identifier
2693  $length = 0x0008; // Bytes to follow
2694 
2695  $margin = $this->_margin_left; // Margin in inches
2696 
2697  $header = pack("vv", $record, $length);
2698  $data = pack("d", $margin);
2699  if ($this->_byte_order) // if it's Big Endian
2700  {
2701  $data = strrev($data);
2702  }
2703 
2704  $this->_append($header.$data);
2705  }
2706 
2712  function _storeMarginRight()
2713  {
2714  $record = 0x0027; // Record identifier
2715  $length = 0x0008; // Bytes to follow
2716 
2717  $margin = $this->_margin_right; // Margin in inches
2718 
2719  $header = pack("vv", $record, $length);
2720  $data = pack("d", $margin);
2721  if ($this->_byte_order) // if it's Big Endian
2722  {
2723  $data = strrev($data);
2724  }
2725 
2726  $this->_append($header.$data);
2727  }
2728 
2734  function _storeMarginTop()
2735  {
2736  $record = 0x0028; // Record identifier
2737  $length = 0x0008; // Bytes to follow
2738 
2739  $margin = $this->_margin_top; // Margin in inches
2740 
2741  $header = pack("vv", $record, $length);
2742  $data = pack("d", $margin);
2743  if ($this->_byte_order) // if it's Big Endian
2744  {
2745  $data = strrev($data);
2746  }
2747 
2748  $this->_append($header.$data);
2749  }
2750 
2756  function _storeMarginBottom()
2757  {
2758  $record = 0x0029; // Record identifier
2759  $length = 0x0008; // Bytes to follow
2760 
2761  $margin = $this->_margin_bottom; // Margin in inches
2762 
2763  $header = pack("vv", $record, $length);
2764  $data = pack("d", $margin);
2765  if ($this->_byte_order) // if it's Big Endian
2766  {
2767  $data = strrev($data);
2768  }
2769 
2770  $this->_append($header.$data);
2771  }
2772 
2784  function mergeCells($first_row, $first_col, $last_row, $last_col)
2785  {
2786  $record = 0x00E5; // Record identifier
2787  $length = 0x000A; // Bytes to follow
2788  $cref = 1; // Number of refs
2789 
2790  // Swap last row/col for first row/col as necessary
2791  if ($first_row > $last_row) {
2792  list($first_row, $last_row) = array($last_row, $first_row);
2793  }
2794 
2795  if ($first_col > $last_col) {
2796  list($first_col, $last_col) = array($last_col, $first_col);
2797  }
2798 
2799  $header = pack("vv", $record, $length);
2800  $data = pack("vvvvv", $cref, $first_row, $last_row,
2801  $first_col, $last_col);
2802 
2803  $this->_append($header.$data);
2804  }
2805 
2811  function _storePrintHeaders()
2812  {
2813  $record = 0x002a; // Record identifier
2814  $length = 0x0002; // Bytes to follow
2815 
2816  $fPrintRwCol = $this->_print_headers; // Boolean flag
2817 
2818  $header = pack("vv", $record, $length);
2819  $data = pack("v", $fPrintRwCol);
2820  $this->_prepend($header.$data);
2821  }
2822 
2829  function _storePrintGridlines()
2830  {
2831  $record = 0x002b; // Record identifier
2832  $length = 0x0002; // Bytes to follow
2833 
2834  $fPrintGrid = $this->_print_gridlines; // Boolean flag
2835 
2836  $header = pack("vv", $record, $length);
2837  $data = pack("v", $fPrintGrid);
2838  $this->_prepend($header.$data);
2839  }
2840 
2847  function _storeGridset()
2848  {
2849  $record = 0x0082; // Record identifier
2850  $length = 0x0002; // Bytes to follow
2851 
2852  $fGridSet = !($this->_print_gridlines); // Boolean flag
2853 
2854  $header = pack("vv", $record, $length);
2855  $data = pack("v", $fGridSet);
2856  $this->_prepend($header.$data);
2857  }
2858 
2867  function _storeGuts()
2868  {
2869  $record = 0x0080; // Record identifier
2870  $length = 0x0008; // Bytes to follow
2871 
2872  $dxRwGut = 0x0000; // Size of row gutter
2873  $dxColGut = 0x0000; // Size of col gutter
2874 
2875  $row_level = $this->_outline_row_level;
2876  $col_level = 0;
2877 
2878  // Calculate the maximum column outline level. The equivalent calculation
2879  // for the row outline level is carried out in setRow().
2880  for ($i=0; $i < count($this->_colinfo); $i++)
2881  {
2882  // Skip cols without outline level info.
2883  if (count($col_level) >= 6) {
2884  $col_level = max($this->_colinfo[$i][5], $col_level);
2885  }
2886  }
2887 
2888  // Set the limits for the outline levels (0 <= x <= 7).
2889  $col_level = max(0, min($col_level, 7));
2890 
2891  // The displayed level is one greater than the max outline levels
2892  if ($row_level) {
2893  $row_level++;
2894  }
2895  if ($col_level) {
2896  $col_level++;
2897  }
2898 
2899  $header = pack("vv", $record, $length);
2900  $data = pack("vvvv", $dxRwGut, $dxColGut, $row_level, $col_level);
2901 
2902  $this->_prepend($header.$data);
2903  }
2904 
2905 
2912  function _storeWsbool()
2913  {
2914  $record = 0x0081; // Record identifier
2915  $length = 0x0002; // Bytes to follow
2916  $grbit = 0x0000;
2917 
2918  // The only option that is of interest is the flag for fit to page. So we
2919  // set all the options in one go.
2920  //
2921  /*if ($this->_fit_page) {
2922  $grbit = 0x05c1;
2923  }
2924  else {
2925  $grbit = 0x04c1;
2926  }*/
2927  // Set the option flags
2928  $grbit |= 0x0001; // Auto page breaks visible
2929  if ($this->_outline_style) {
2930  $grbit |= 0x0020; // Auto outline styles
2931  }
2932  if ($this->_outline_below) {
2933  $grbit |= 0x0040; // Outline summary below
2934  }
2935  if ($this->_outline_right) {
2936  $grbit |= 0x0080; // Outline summary right
2937  }
2938  if ($this->_fit_page) {
2939  $grbit |= 0x0100; // Page setup fit to page
2940  }
2941  if ($this->_outline_on) {
2942  $grbit |= 0x0400; // Outline symbols displayed
2943  }
2944 
2945  $header = pack("vv", $record, $length);
2946  $data = pack("v", $grbit);
2947  $this->_prepend($header.$data);
2948  }
2949 
2955  function _storeHbreak()
2956  {
2957  // Return if the user hasn't specified pagebreaks
2958  if (empty($this->_hbreaks)) {
2959  return;
2960  }
2961 
2962  // Sort and filter array of page breaks
2963  $breaks = $this->_hbreaks;
2964  sort($breaks, SORT_NUMERIC);
2965  if ($breaks[0] == 0) { // don't use first break if it's 0
2966  array_shift($breaks);
2967  }
2968 
2969  $record = 0x001b; // Record identifier
2970  $cbrk = count($breaks); // Number of page breaks
2971  $length = 2 + 6*$cbrk; // Bytes to follow
2972 
2973  $header = pack("vv", $record, $length);
2974  $data = pack("v", $cbrk);
2975 
2976  // Append each page break
2977  foreach($breaks as $break) {
2978  $data .= pack("vvv", $break, 0x0000, 0x00ff);
2979  }
2980 
2981  $this->_prepend($header.$data);
2982  }
2983 
2984 
2990  function _storeVbreak()
2991  {
2992  // Return if the user hasn't specified pagebreaks
2993  if (empty($this->_vbreaks)) {
2994  return;
2995  }
2996 
2997  // 1000 vertical pagebreaks appears to be an internal Excel 5 limit.
2998  // It is slightly higher in Excel 97/200, approx. 1026
2999  $breaks = array_slice($this->_vbreaks,0,1000);
3000 
3001  // Sort and filter array of page breaks
3002  sort($breaks, SORT_NUMERIC);
3003  if ($breaks[0] == 0) { // don't use first break if it's 0
3004  array_shift($breaks);
3005  }
3006 
3007  $record = 0x001a; // Record identifier
3008  $cbrk = count($breaks); // Number of page breaks
3009  $length = 2 + 6*$cbrk; // Bytes to follow
3010 
3011  $header = pack("vv", $record, $length);
3012  $data = pack("v", $cbrk);
3013 
3014  // Append each page break
3015  foreach ($breaks as $break) {
3016  $data .= pack("vvv", $break, 0x0000, 0xffff);
3017  }
3018 
3019  $this->_prepend($header.$data);
3020  }
3021 
3027  function _storeProtect()
3028  {
3029  // Exit unless sheet protection has been specified
3030  if ($this->_protect == 0) {
3031  return;
3032  }
3033 
3034  $record = 0x0012; // Record identifier
3035  $length = 0x0002; // Bytes to follow
3036 
3037  $fLock = $this->_protect; // Worksheet is protected
3038 
3039  $header = pack("vv", $record, $length);
3040  $data = pack("v", $fLock);
3041 
3042  $this->_prepend($header.$data);
3043  }
3044 
3050  function _storePassword()
3051  {
3052  // Exit unless sheet protection and password have been specified
3053  if (($this->_protect == 0) or (!isset($this->_password))) {
3054  return;
3055  }
3056 
3057  $record = 0x0013; // Record identifier
3058  $length = 0x0002; // Bytes to follow
3059 
3060  $wPassword = $this->_password; // Encoded password
3061 
3062  $header = pack("vv", $record, $length);
3063  $data = pack("v", $wPassword);
3064 
3065  $this->_prepend($header.$data);
3066  }
3067 
3068 
3081  function insertBitmap($row, $col, $bitmap, $x = 0, $y = 0, $scale_x = 1, $scale_y = 1)
3082  {
3083  $bitmap_array = $this->_processBitmap($bitmap);
3084  if ($this->isError($bitmap_array))
3085  {
3086  $this->writeString($row, $col, $bitmap_array->getMessage());
3087  return;
3088  }
3089  list($width, $height, $size, $data) = $bitmap_array; //$this->_processBitmap($bitmap);
3090 
3091  // Scale the frame of the image.
3092  $width *= $scale_x;
3093  $height *= $scale_y;
3094 
3095  // Calculate the vertices of the image and write the OBJ record
3096  $this->_positionImage($col, $row, $x, $y, $width, $height);
3097 
3098  // Write the IMDATA record to store the bitmap data
3099  $record = 0x007f;
3100  $length = 8 + $size;
3101  $cf = 0x09;
3102  $env = 0x01;
3103  $lcb = $size;
3104 
3105  $header = pack("vvvvV", $record, $length, $cf, $env, $lcb);
3106  $this->_append($header.$data);
3107  }
3108 
3160  function _positionImage($col_start, $row_start, $x1, $y1, $width, $height)
3161  {
3162  // Initialise end cell to the same as the start cell
3163  $col_end = $col_start; // Col containing lower right corner of object
3164  $row_end = $row_start; // Row containing bottom right corner of object
3165 
3166  // Zero the specified offset if greater than the cell dimensions
3167  if ($x1 >= $this->_sizeCol($col_start))
3168  {
3169  $x1 = 0;
3170  }
3171  if ($y1 >= $this->_sizeRow($row_start))
3172  {
3173  $y1 = 0;
3174  }
3175 
3176  $width = $width + $x1 -1;
3177  $height = $height + $y1 -1;
3178 
3179  // Subtract the underlying cell widths to find the end cell of the image
3180  while ($width >= $this->_sizeCol($col_end)) {
3181  $width -= $this->_sizeCol($col_end);
3182  $col_end++;
3183  }
3184 
3185  // Subtract the underlying cell heights to find the end cell of the image
3186  while ($height >= $this->_sizeRow($row_end)) {
3187  $height -= $this->_sizeRow($row_end);
3188  $row_end++;
3189  }
3190 
3191  // Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
3192  // with zero eight or width.
3193  //
3194  if ($this->_sizeCol($col_start) == 0)
3195  return;
3196  if ($this->_sizeCol($col_end) == 0)
3197  return;
3198  if ($this->_sizeRow($row_start) == 0)
3199  return;
3200  if ($this->_sizeRow($row_end) == 0)
3201  return;
3202 
3203  // Convert the pixel values to the percentage value expected by Excel
3204  $x1 = $x1 / $this->_sizeCol($col_start) * 1024;
3205  $y1 = $y1 / $this->_sizeRow($row_start) * 256;
3206  $x2 = $width / $this->_sizeCol($col_end) * 1024; // Distance to right side of object
3207  $y2 = $height / $this->_sizeRow($row_end) * 256; // Distance to bottom of object
3208 
3209  $this->_storeObjPicture( $col_start, $x1,
3210  $row_start, $y1,
3211  $col_end, $x2,
3212  $row_end, $y2
3213  );
3214  }
3215 
3225  function _sizeCol($col)
3226  {
3227  // Look up the cell value to see if it has been changed
3228  if (isset($this->col_sizes[$col])) {
3229  if ($this->col_sizes[$col] == 0) {
3230  return(0);
3231  }
3232  else {
3233  return(floor(7 * $this->col_sizes[$col] + 5));
3234  }
3235  }
3236  else {
3237  return(64);
3238  }
3239  }
3240 
3251  function _sizeRow($row)
3252  {
3253  // Look up the cell value to see if it has been changed
3254  if (isset($this->row_sizes[$row])) {
3255  if ($this->row_sizes[$row] == 0) {
3256  return(0);
3257  }
3258  else {
3259  return(floor(4/3 * $this->row_sizes[$row]));
3260  }
3261  }
3262  else {
3263  return(17);
3264  }
3265  }
3266 
3281  function _storeObjPicture($colL,$dxL,$rwT,$dyT,$colR,$dxR,$rwB,$dyB)
3282  {
3283  $record = 0x005d; // Record identifier
3284  $length = 0x003c; // Bytes to follow
3285 
3286  $cObj = 0x0001; // Count of objects in file (set to 1)
3287  $OT = 0x0008; // Object type. 8 = Picture
3288  $id = 0x0001; // Object ID
3289  $grbit = 0x0614; // Option flags
3290 
3291  $cbMacro = 0x0000; // Length of FMLA structure
3292  $Reserved1 = 0x0000; // Reserved
3293  $Reserved2 = 0x0000; // Reserved
3294 
3295  $icvBack = 0x09; // Background colour
3296  $icvFore = 0x09; // Foreground colour
3297  $fls = 0x00; // Fill pattern
3298  $fAuto = 0x00; // Automatic fill
3299  $icv = 0x08; // Line colour
3300  $lns = 0xff; // Line style
3301  $lnw = 0x01; // Line weight
3302  $fAutoB = 0x00; // Automatic border
3303  $frs = 0x0000; // Frame style
3304  $cf = 0x0009; // Image format, 9 = bitmap
3305  $Reserved3 = 0x0000; // Reserved
3306  $cbPictFmla = 0x0000; // Length of FMLA structure
3307  $Reserved4 = 0x0000; // Reserved
3308  $grbit2 = 0x0001; // Option flags
3309  $Reserved5 = 0x0000; // Reserved
3310 
3311 
3312  $header = pack("vv", $record, $length);
3313  $data = pack("V", $cObj);
3314  $data .= pack("v", $OT);
3315  $data .= pack("v", $id);
3316  $data .= pack("v", $grbit);
3317  $data .= pack("v", $colL);
3318  $data .= pack("v", $dxL);
3319  $data .= pack("v", $rwT);
3320  $data .= pack("v", $dyT);
3321  $data .= pack("v", $colR);
3322  $data .= pack("v", $dxR);
3323  $data .= pack("v", $rwB);
3324  $data .= pack("v", $dyB);
3325  $data .= pack("v", $cbMacro);
3326  $data .= pack("V", $Reserved1);
3327  $data .= pack("v", $Reserved2);
3328  $data .= pack("C", $icvBack);
3329  $data .= pack("C", $icvFore);
3330  $data .= pack("C", $fls);
3331  $data .= pack("C", $fAuto);
3332  $data .= pack("C", $icv);
3333  $data .= pack("C", $lns);
3334  $data .= pack("C", $lnw);
3335  $data .= pack("C", $fAutoB);
3336  $data .= pack("v", $frs);
3337  $data .= pack("V", $cf);
3338  $data .= pack("v", $Reserved3);
3339  $data .= pack("v", $cbPictFmla);
3340  $data .= pack("v", $Reserved4);
3341  $data .= pack("v", $grbit2);
3342  $data .= pack("V", $Reserved5);
3343 
3344  $this->_append($header.$data);
3345  }
3346 
3356  function _processBitmap($bitmap)
3357  {
3358  // Open file.
3359  $bmp_fd = @fopen($bitmap,"rb");
3360  if (!$bmp_fd) {
3361  $this->raiseError("Couldn't import $bitmap");
3362  }
3363 
3364  // Slurp the file into a string.
3365  $data = fread($bmp_fd, filesize($bitmap));
3366 
3367  // Check that the file is big enough to be a bitmap.
3368  if (strlen($data) <= 0x36) {
3369  $this->raiseError("$bitmap doesn't contain enough data.\n");
3370  }
3371 
3372  // The first 2 bytes are used to identify the bitmap.
3373  $identity = unpack("A2", $data);
3374  if ($identity[''] != "BM") {
3375  $this->raiseError("$bitmap doesn't appear to be a valid bitmap image.\n");
3376  }
3377 
3378  // Remove bitmap data: ID.
3379  $data = substr($data, 2);
3380 
3381  // Read and remove the bitmap size. This is more reliable than reading
3382  // the data size at offset 0x22.
3383  //
3384  $size_array = unpack("V", substr($data, 0, 4));
3385  $size = $size_array[''];
3386  $data = substr($data, 4);
3387  $size -= 0x36; // Subtract size of bitmap header.
3388  $size += 0x0C; // Add size of BIFF header.
3389 
3390  // Remove bitmap data: reserved, offset, header length.
3391  $data = substr($data, 12);
3392 
3393  // Read and remove the bitmap width and height. Verify the sizes.
3394  $width_and_height = unpack("V2", substr($data, 0, 8));
3395  $width = $width_and_height[1];
3396  $height = $width_and_height[2];
3397  $data = substr($data, 8);
3398  if ($width > 0xFFFF) {
3399  $this->raiseError("$bitmap: largest image width supported is 65k.\n");
3400  }
3401  if ($height > 0xFFFF) {
3402  $this->raiseError("$bitmap: largest image height supported is 65k.\n");
3403  }
3404 
3405  // Read and remove the bitmap planes and bpp data. Verify them.
3406  $planes_and_bitcount = unpack("v2", substr($data, 0, 4));
3407  $data = substr($data, 4);
3408  if ($planes_and_bitcount[2] != 24) { // Bitcount
3409  $this->raiseError("$bitmap isn't a 24bit true color bitmap.\n");
3410  }
3411  if ($planes_and_bitcount[1] != 1) {
3412  $this->raiseError("$bitmap: only 1 plane nupported in bitmap image.\n");
3413  }
3414 
3415  // Read and remove the bitmap compression. Verify compression.
3416  $compression = unpack("V", substr($data, 0, 4));
3417  $data = substr($data, 4);
3418 
3419  //$compression = 0;
3420  if ($compression[""] != 0) {
3421  $this->raiseError("$bitmap: compression not supported in bitmap image.\n");
3422  }
3423 
3424  // Remove bitmap data: data size, hres, vres, colours, imp. colours.
3425  $data = substr($data, 20);
3426 
3427  // Add the BITMAPCOREHEADER data
3428  $header = pack("Vvvvv", 0x000c, $width, $height, 0x01, 0x18);
3429  $data = $header . $data;
3430 
3431  return (array($width, $height, $size, $data));
3432  }
3433 
3440  function _storeZoom()
3441  {
3442  // If scale is 100 we don't need to write a record
3443  if ($this->_zoom == 100) {
3444  return;
3445  }
3446 
3447  $record = 0x00A0; // Record identifier
3448  $length = 0x0004; // Bytes to follow
3449 
3450  $header = pack("vv", $record, $length);
3451  $data = pack("vv", $this->_zoom, 100);
3452  $this->_append($header.$data);
3453  }
3454 
3458  function setValidation($row1, $col1, $row2, $col2, &$validator)
3459  {
3460  $this->_dv[] = $validator->_getData() .
3461  pack("vvvvv", 1, $row1, $row2, $col1, $col2);
3462  }
3463 
3469  function _storeDataValidity()
3470  {
3471  $record = 0x01b2; // Record identifier
3472  $length = 0x0012; // Bytes to follow
3473 
3474  $grbit = 0x0002; // Prompt box at cell, no cached validity data at DV records
3475  $horPos = 0x00000000; // Horizontal position of prompt box, if fixed position
3476  $verPos = 0x00000000; // Vertical position of prompt box, if fixed position
3477  $objId = 0xffffffff; // Object identifier of drop down arrow object, or -1 if not visible
3478 
3479  $header = pack('vv', $record, $length);
3480  $data = pack('vVVVV', $grbit, $horPos, $verPos, $objId,
3481  count($this->_dv));
3482  $this->_append($header.$data);
3483 
3484  $record = 0x01be; // Record identifier
3485  foreach($this->_dv as $dv)
3486  {
3487  $length = strlen($dv); // Bytes to follow
3488  $header = pack("vv", $record, $length);
3489  $this->_append($header.$dv);
3490  }
3491  }
3492 }
3493 ?>