~adamzammit/quexs/quexscativm

« back to all changes in this revision

Viewing changes to include/limesurvey/admin/classes/pear/Spreadsheet/Excel/Writer/Worksheet.php

  • Committer: azammitdcarf
  • Date: 2008-10-15 04:55:53 UTC
  • Revision ID: svn-v4:fd4a0071-7450-0410-a91b-842f6942ebe7:trunk:6
Import from DCARF SVN

Show diffs side-by-side

added added

removed removed

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