spreadsheet 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. data/GUIDE.txt +209 -0
  2. data/History.txt +8 -0
  3. data/LICENSE.txt +619 -0
  4. data/Manifest.txt +46 -0
  5. data/README.txt +54 -0
  6. data/Rakefile +15 -0
  7. data/lib/parseexcel.rb +27 -0
  8. data/lib/parseexcel/parseexcel.rb +75 -0
  9. data/lib/parseexcel/parser.rb +11 -0
  10. data/lib/spreadsheet.rb +79 -0
  11. data/lib/spreadsheet/datatypes.rb +99 -0
  12. data/lib/spreadsheet/encodings.rb +49 -0
  13. data/lib/spreadsheet/excel.rb +75 -0
  14. data/lib/spreadsheet/excel/error.rb +26 -0
  15. data/lib/spreadsheet/excel/internals.rb +322 -0
  16. data/lib/spreadsheet/excel/internals/biff5.rb +17 -0
  17. data/lib/spreadsheet/excel/internals/biff8.rb +19 -0
  18. data/lib/spreadsheet/excel/offset.rb +37 -0
  19. data/lib/spreadsheet/excel/reader.rb +798 -0
  20. data/lib/spreadsheet/excel/reader/biff5.rb +22 -0
  21. data/lib/spreadsheet/excel/reader/biff8.rb +168 -0
  22. data/lib/spreadsheet/excel/row.rb +67 -0
  23. data/lib/spreadsheet/excel/sst_entry.rb +45 -0
  24. data/lib/spreadsheet/excel/workbook.rb +76 -0
  25. data/lib/spreadsheet/excel/worksheet.rb +85 -0
  26. data/lib/spreadsheet/excel/writer.rb +1 -0
  27. data/lib/spreadsheet/excel/writer/biff8.rb +66 -0
  28. data/lib/spreadsheet/excel/writer/format.rb +270 -0
  29. data/lib/spreadsheet/excel/writer/workbook.rb +586 -0
  30. data/lib/spreadsheet/excel/writer/worksheet.rb +556 -0
  31. data/lib/spreadsheet/font.rb +86 -0
  32. data/lib/spreadsheet/format.rb +172 -0
  33. data/lib/spreadsheet/formula.rb +9 -0
  34. data/lib/spreadsheet/row.rb +87 -0
  35. data/lib/spreadsheet/workbook.rb +120 -0
  36. data/lib/spreadsheet/worksheet.rb +215 -0
  37. data/lib/spreadsheet/writer.rb +29 -0
  38. data/test/data/test_copy.xls +0 -0
  39. data/test/data/test_version_excel5.xls +0 -0
  40. data/test/data/test_version_excel95.xls +0 -0
  41. data/test/data/test_version_excel97.xls +0 -0
  42. data/test/excel/row.rb +29 -0
  43. data/test/font.rb +163 -0
  44. data/test/integration.rb +1021 -0
  45. data/test/workbook.rb +21 -0
  46. data/test/worksheet.rb +62 -0
  47. metadata +113 -0
@@ -0,0 +1,556 @@
1
+ require 'stringio'
2
+ require 'spreadsheet/excel/writer/biff8'
3
+ require 'spreadsheet/excel/internals/biff8'
4
+
5
+ module Spreadsheet
6
+ module Excel
7
+ module Writer
8
+ ##
9
+ # Writer class for Excel Worksheets. Most write_* method correspond to an
10
+ # Excel-Record/Opcode. You should not need to call any of its methods directly.
11
+ # If you think you do, look at #write_worksheet
12
+ class Worksheet
13
+ include Biff8
14
+ include Internals
15
+ include Internals::Biff8
16
+ attr_reader :worksheet
17
+ def initialize workbook, worksheet
18
+ @workbook = workbook
19
+ @worksheet = worksheet
20
+ @io = StringIO.new ''
21
+ @biff_version = 0x0600
22
+ @bof = 0x0809
23
+ @build_id = 3515
24
+ @build_year = 1996
25
+ @bof_types = {
26
+ :globals => 0x0005,
27
+ :visual_basic => 0x0006,
28
+ :worksheet => 0x0010,
29
+ :chart => 0x0020,
30
+ :macro_sheet => 0x0040,
31
+ :workspace => 0x0100,
32
+ }
33
+ end
34
+ ##
35
+ # The number of bytes needed to write a Boundsheet record for this Worksheet
36
+ # Used by Writer::Worksheet to calculate various offsets.
37
+ def boundsheet_size
38
+ name.size + 10
39
+ end
40
+ def data
41
+ @io.rewind
42
+ @io.read
43
+ end
44
+ def encode_date date
45
+ return date if date.is_a? Numeric
46
+ if date.is_a? Time
47
+ date = DateTime.new date.year, date.month, date.day,
48
+ date.hour, date.min, date.sec
49
+ end
50
+ value = date - @worksheet.workbook.date_base
51
+ if date > LEAP_ERROR
52
+ value += 1
53
+ end
54
+ value
55
+ end
56
+ def encode_rk value
57
+ # Bit Mask Contents
58
+ # 0 0x00000001 0 = Value not changed 1 = Value is multiplied by 100
59
+ # 1 0x00000002 0 = Floating-point value 1 = Signed integer value
60
+ # 31-2 0xFFFFFFFC Encoded value
61
+ cent = 0
62
+ int = 2
63
+ higher = value * 100
64
+ if higher == higher.to_i
65
+ value = higher.to_i
66
+ cent = 1
67
+ end
68
+ if value.is_a?(Integer)
69
+ shifted = [value].pack 'l'
70
+ ## I can't find a format for packing a little endian signed integer
71
+ shifted.reverse! if @bigendian
72
+ value, = shifted.unpack 'V'
73
+ value <<= 2
74
+ else
75
+ # FIXME: precision of small numbers
76
+ int = 0
77
+ value, = [value].pack(EIGHT_BYTE_DOUBLE).unpack('x4V')
78
+ value &= 0xfffffffc
79
+ end
80
+ value | cent | int
81
+ end
82
+ def name
83
+ unicode_string @worksheet.name
84
+ end
85
+ def row_blocks
86
+ # All cells in an Excel document are divided into blocks of 32 consecutive
87
+ # rows, called Row Blocks. The first Row Block starts with the first used
88
+ # row in that sheet. Inside each Row Block there will occur ROW records
89
+ # describing the properties of the rows, and cell records with all the cell
90
+ # contents in this Row Block.
91
+ blocks = []
92
+ @worksheet.reject do |row| row.empty? end.each_with_index do |row, idx|
93
+ blocks << [] if idx % 32 == 0
94
+ blocks.last << row
95
+ end
96
+ blocks
97
+ end
98
+ def size
99
+ @io.size
100
+ end
101
+ def strings
102
+ @worksheet.inject [] do |memo, row|
103
+ strings = row.select do |cell| cell.is_a? String end
104
+ memo.concat strings
105
+ end
106
+ end
107
+ ##
108
+ # Write a blank cell
109
+ def write_blank row, idx
110
+ write_cell :blank, row, idx
111
+ end
112
+ def write_bof
113
+ data = [
114
+ @biff_version, # BIFF version (always 0x0600 for BIFF8)
115
+ 0x0010, # Type of the following data:
116
+ # 0x0005 = Workbook globals
117
+ # 0x0006 = Visual Basic module
118
+ # 0x0010 = Worksheet
119
+ # 0x0020 = Chart
120
+ # 0x0040 = Macro sheet
121
+ # 0x0100 = Workspace file
122
+ @build_id, # Build identifier
123
+ @build_year, # Build year
124
+ 0x000, # File history flags
125
+ 0x006, # Lowest Excel version that can read
126
+ # all records in this file
127
+ ]
128
+ write_op @bof, data.pack("v4V2")
129
+ end
130
+ ##
131
+ # Write a cell with a Boolean or Error value
132
+ def write_boolerr row, idx
133
+ value = row[idx]
134
+ type = 0
135
+ numval = 0
136
+ if value.is_a? Error
137
+ type = 1
138
+ numval = value.code
139
+ elsif value
140
+ numval = 1
141
+ end
142
+ data = [
143
+ numval, # Boolean or error value (type depends on the following byte)
144
+ type # 0 = Boolean value; 1 = Error code
145
+ ]
146
+ write_cell :boolerr, row, idx, *data
147
+ end
148
+ def write_calccount
149
+ count = 100 # Maximum number of iterations allowed in circular references
150
+ write_op 0x000c, [count].pack('v')
151
+ end
152
+ def write_cell type, row, idx, *args
153
+ xf_idx = @workbook.xf_index @worksheet.workbook, row.format(idx)
154
+ data = [
155
+ row.idx, # Index to row
156
+ idx, # Index to column
157
+ xf_idx, # Index to XF record (➜ 6.115)
158
+ ].concat args
159
+ write_op opcode(type), data.pack(binfmt(type))
160
+ end
161
+ def write_cellblocks row
162
+ # BLANK ➜ 6.7
163
+ # BOOLERR ➜ 6.10
164
+ # INTEGER ➜ 6.56 (BIFF2 only)
165
+ # LABEL ➜ 6.59 (BIFF2-BIFF7)
166
+ # LABELSST ➜ 6.61 (BIFF8 only)
167
+ # MULBLANK ➜ 6.64 (BIFF5-BIFF8)
168
+ # MULRK ➜ 6.65 (BIFF5-BIFF8)
169
+ # NUMBER ➜ 6.68
170
+ # RK ➜ 6.82 (BIFF3-BIFF8)
171
+ # RSTRING ➜ 6.84 (BIFF5/BIFF7)
172
+ multiples, first_idx = nil
173
+ row.each_with_index do |cell, idx|
174
+ if multiples && (!multiples.last.is_a?(cell.class) \
175
+ || (cell.is_a?(Numeric) && cell.abs < 0.1))
176
+ write_multiples row, first_idx, multiples
177
+ multiples, first_idx = nil
178
+ end
179
+ nxt = idx + 1
180
+ case cell
181
+ when NilClass
182
+ if multiples
183
+ multiples.push cell
184
+ elsif nxt < row.size && row[nxt].nil?
185
+ multiples = [cell]
186
+ first_idx = idx
187
+ else
188
+ write_blank row, idx
189
+ end
190
+ when TrueClass, FalseClass, Error
191
+ write_boolerr row, idx
192
+ when String
193
+ write_labelsst row, idx
194
+ when Numeric
195
+ ## RK encodes Floats with 30 significant bits, which is a bit more than
196
+ # 10^9. Not sure what is a good rule of thumb here, but it seems that
197
+ # Decimal Numbers with more than 4 significant digits are not represented
198
+ # with sufficient precision by RK
199
+ if cell.is_a?(Float) && cell.to_s.length > 5
200
+ write_number row, idx
201
+ elsif multiples
202
+ multiples.push cell
203
+ elsif nxt < row.size && row[nxt].is_a?(Numeric)
204
+ multiples = [cell]
205
+ first_idx = idx
206
+ else
207
+ write_rk row, idx
208
+ end
209
+ when Formula
210
+ write_formula row, idx
211
+ when Date
212
+ write_rk row, idx
213
+ end
214
+ end
215
+ write_multiples row, first_idx, multiples if multiples
216
+ end
217
+ def write_changes reader, endpos, sst_status
218
+ reader.seek @worksheet.offset
219
+ blocks = row_blocks
220
+ lastpos = reader.pos
221
+ offsets = {}
222
+ @worksheet.offsets.each do |key, pair|
223
+ if @worksheet.changes.include?(key) \
224
+ || (sst_status == :complete_update && key.is_a?(Integer))
225
+ offsets.store pair, key
226
+ end
227
+ end
228
+ offsets.invert.sort_by do |key, (pos, len)|
229
+ pos
230
+ end.each do |key, (pos, len)|
231
+ @io.write reader.read(pos - lastpos)
232
+ if key.is_a?(Integer)
233
+ block = blocks.find do |rows| rows.any? do |row| row.idx == key end end
234
+ write_rowblock block
235
+ else
236
+ send "write_#{key}"
237
+ end
238
+ lastpos = pos + len
239
+ reader.seek lastpos
240
+ end
241
+ @io.write reader.read(endpos - lastpos)
242
+ end
243
+ def write_defaultrowheight
244
+ data = [
245
+ 0x00, # Option flags:
246
+ # Bit Mask Contents
247
+ # 0 0x01 1 = Row height and default font height do not match
248
+ # 1 0x02 1 = Row is hidden
249
+ # 2 0x04 1 = Additional space above the row
250
+ # 3 0x08 1 = Additional space below the row
251
+ 0xf2, # Default height for unused rows, in twips = 1/20 of a point
252
+ ]
253
+ write_op 0x0225, data.pack('v2')
254
+ end
255
+ def write_dimensions
256
+ # Offset Size Contents
257
+ # 0 4 Index to first used row
258
+ # 4 4 Index to last used row, increased by 1
259
+ # 8 2 Index to first used column
260
+ # 10 2 Index to last used column, increased by 1
261
+ # 12 2 Not used
262
+ write_op 0x0200, @worksheet.dimensions.pack(binfmt(:dimensions))
263
+ end
264
+ def write_eof
265
+ write_op 0x000a
266
+ end
267
+ ##
268
+ # Write a cell with a Formula. May write an additional String record depending
269
+ # on the stored result of the Formula.
270
+ def write_formula row, idx
271
+ cell = row[idx]
272
+ data1 = [
273
+ row.idx, # Index to row
274
+ idx, # Index to column
275
+ 0, # Index to XF record (➜ 6.115)
276
+ ].pack 'v3'
277
+ data2 = nil
278
+ case value = cell.value
279
+ when Numeric # IEEE 754 floating-point value (64-bit double precision)
280
+ data2 = [value].pack EIGHT_BYTE_DOUBLE
281
+ when String
282
+ data2 = [
283
+ 0x00, # (identifier for a string value)
284
+ 0xffff, #
285
+ ].pack 'Cx5v'
286
+ when true, false
287
+ value = value ? 1 : 0
288
+ data2 = [
289
+ 0x01, # (identifier for a Boolean value)
290
+ value, # 0 = FALSE, 1 = TRUE
291
+ 0xffff, #
292
+ ].pack 'CxCx3v'
293
+ when Error
294
+ data2 = [
295
+ 0x02, # (identifier for an error value)
296
+ value.code, # Error code
297
+ 0xffff, #
298
+ ].pack 'CxCx3v'
299
+ when nil
300
+ data2 = [
301
+ 0x03, # (identifier for an empty cell)
302
+ 0xffff, #
303
+ ].pack 'Cx5v'
304
+ else
305
+ data2 = [
306
+ 0x02, # (identifier for an error value)
307
+ 0x2a, # Error code: #N/A! Argument or function not available
308
+ 0xffff, #
309
+ ].pack 'CxCx3v'
310
+ end
311
+ opts = 0x03
312
+ opts |= 0x08 if cell.shared
313
+ data3 = [
314
+ opts # Option flags:
315
+ # Bit Mask Contents
316
+ # 0 0x0001 1 = Recalculate always
317
+ # 1 0x0002 1 = Calculate on open
318
+ # 3 0x0008 1 = Part of a shared formula
319
+ ].pack 'vx4'
320
+ write_op opcode(:formula), data1, data2, data3, cell.data
321
+ if cell.value.is_a?(String)
322
+ write_op opcode(:string), unicode_string(cell.value, 2)
323
+ end
324
+ end
325
+ ##
326
+ # Write a new Worksheet.
327
+ def write_from_scratch
328
+ # ● BOF Type = worksheet (➜ 6.8)
329
+ write_bof
330
+ # ○ UNCALCED ➜ 6.104
331
+ # ○ INDEX ➜ 5.7 (Row Blocks), ➜ 6.55
332
+ # ○ Calculation Settings Block ➜ 5.3
333
+ write_calccount
334
+ write_refmode
335
+ write_iteration
336
+ write_saverecalc
337
+ # ○ PRINTHEADERS ➜ 6.76
338
+ # ○ PRINTGRIDLINES ➜ 6.75
339
+ # ○ GRIDSET ➜ 6.48
340
+ # ○ GUTS ➜ 6.49
341
+ # ○ DEFAULTROWHEIGHT ➜ 6.28
342
+ write_defaultrowheight
343
+ # ○ WSBOOL ➜ 6.113
344
+ write_wsbool
345
+ # ○ Page Settings Block ➜ 5.4
346
+ # ○ Worksheet Protection Block ➜ 5.18
347
+ # ○ DEFCOLWIDTH ➜ 6.29
348
+ # ○○ COLINFO ➜ 6.18
349
+ # ○ SORT ➜ 6.95
350
+ # ● DIMENSIONS ➜ 6.31
351
+ write_dimensions
352
+ # ○○ Row Blocks ➜ 5.7
353
+ write_rows
354
+ # ● Worksheet View Settings Block ➜ 5.5
355
+ # ○ STANDARDWIDTH ➜ 6.97
356
+ # ○○ MERGEDCELLS ➜ 6.63
357
+ # ○ LABELRANGES ➜ 6.60
358
+ # ○ PHONETIC ➜ 6.73
359
+ # ○ Conditional Formatting Table ➜ 5.12
360
+ # ○ Hyperlink Table ➜ 5.13
361
+ # ○ Data Validity Table ➜ 5.14
362
+ # ○ SHEETLAYOUT ➜ 6.91 (BIFF8X only)
363
+ # ○ SHEETPROTECTION Additional protection, ➜ 6.92 (BIFF8X only)
364
+ # ○ RANGEPROTECTION Additional protection, ➜ 6.79 (BIFF8X only)
365
+ # ● EOF ➜ 6.36
366
+ write_eof
367
+ end
368
+ def write_iteration
369
+ its = 0 # 0 = Iterations off; 1 = Iterations on
370
+ write_op 0x0011, [its].pack('v')
371
+ end
372
+ ##
373
+ # Write a cell with a String value. The String must have been stored in the
374
+ # Shared String Table.
375
+ def write_labelsst row, idx
376
+ write_cell :labelsst, row, idx, @workbook.sst_index(self, row[idx])
377
+ end
378
+ ##
379
+ # Write multiple consecutive blank cells.
380
+ def write_mulblank row, idx, multiples
381
+ data = [
382
+ row.idx, # Index to row
383
+ idx, # Index to first column (fc)
384
+ ]
385
+ # List of nc=lc-fc+1 16-bit indexes to XF records (➜ 6.115)
386
+ multiples.each_with_index do |blank, cell_idx|
387
+ xf_idx = @workbook.xf_index @worksheet.workbook, row.format(idx + cell_idx)
388
+ data.push xf_idx
389
+ end
390
+ # Index to last column (lc)
391
+ data.push idx + multiples.size
392
+ write_op opcode(:mulblank), data.pack('v*')
393
+ end
394
+ ##
395
+ # Write multiple consecutive cells with RK values (see #write_rk)
396
+ def write_mulrk row, idx, multiples
397
+ fmt = 'v2'
398
+ data = [
399
+ row.idx, # Index to row
400
+ idx, # Index to first column (fc)
401
+ ]
402
+ # List of nc=lc-fc+1 16-bit indexes to XF records (➜ 6.115)
403
+ multiples.each do |cell|
404
+ # TODO: XF indices
405
+ data.push 0, encode_rk(cell)
406
+ fmt << 'vV'
407
+ end
408
+ # Index to last column (lc)
409
+ data.push idx + multiples.size
410
+ write_op opcode(:mulrk), data.pack(fmt << 'v')
411
+ end
412
+ def write_multiples row, idx, multiples
413
+ case multiples.last
414
+ when NilClass
415
+ write_mulblank row, idx, multiples
416
+ when Numeric
417
+ write_mulrk row, idx, multiples
418
+ end
419
+ end
420
+ ##
421
+ # Write a cell with a 64-bit double precision Float value
422
+ def write_number row, idx
423
+ # Offset Size Contents
424
+ # 0 2 Index to row
425
+ # 2 2 Index to column
426
+ # 4 2 Index to XF record (➜ 6.115)
427
+ # 6 8 IEEE 754 floating-point value (64-bit double precision)
428
+ write_cell :number, row, idx, row[idx]
429
+ end
430
+ def write_op op, *args
431
+ data = args.join
432
+ @io.write [op,data.size].pack("v2")
433
+ @io.write data
434
+ end
435
+ def write_refmode
436
+ # • The “RC” mode uses numeric indexes for rows and columns, for example
437
+ # “R(1)C(-1)”, or “R1C1:R2C2”.
438
+ # • The “A1” mode uses characters for columns and numbers for rows, for
439
+ # example “B1”, or “$A$1:$B$2”.
440
+ mode = 1 # 0 = RC mode; 1 = A1 mode
441
+ write_op 0x000f, [mode].pack('v')
442
+ end
443
+ ##
444
+ # Write a cell with a Numeric or Date value.
445
+ def write_rk row, idx
446
+ value = row[idx]
447
+ case value
448
+ when Date, DateTime
449
+ value = encode_date(value)
450
+ end
451
+ write_cell :rk, row, idx, encode_rk(value)
452
+ end
453
+ def write_row row
454
+ # Offset Size Contents
455
+ # 0 2 Index of this row
456
+ # 2 2 Index to column of the first cell which
457
+ # is described by a cell record
458
+ # 4 2 Index to column of the last cell which is
459
+ # described by a cell record, increased by 1
460
+ # 6 2 Bit Mask Contents
461
+ # 14-0 0x7fff Height of the row, in twips = 1/20 of a point
462
+ # 15 0x8000 0 = Row has custom height;
463
+ # 1 = Row has default height
464
+ # 8 2 Not used
465
+ # 10 1 0 = No defaults written;
466
+ # 1 = Default row attribute field and XF index occur below (fl)
467
+ # 11 2 Relative offset to calculate stream position of the first
468
+ # cell record for this row (➜ 5.7.1)
469
+ # [13] 3 (written only if fl = 1) Default row attributes (➜ 3.12)
470
+ # [16] 2 (written only if fl = 1) Index to XF record (➜ 6.115)
471
+ has_defaults = row.default_format ? 1 : 0
472
+ data = [
473
+ row.idx,
474
+ row.first_used,
475
+ row.first_unused,
476
+ row.height * TWIPS,
477
+ 0, # Not used
478
+ has_defaults,
479
+ 0, # OOffice does not set this - ignore until someone complains
480
+ ]
481
+ # OpenOffice apparently can't read Rows with a length other than 16 Bytes
482
+ fmt = binfmt(:row) + 'x3'
483
+ =begin
484
+ if format = row.default_format
485
+ fmt = fmt + 'xv'
486
+ data.concat [
487
+ #0, # Row attributes should only matter in BIFF2
488
+ workbook.xf_index(@worksheet.workbook, format),
489
+ ]
490
+ end
491
+ =end
492
+ write_op opcode(:row), data.pack(fmt)
493
+ end
494
+ def write_rowblock block
495
+ # ●● ROW Properties of the used rows
496
+ # ○○ Cell Block(s) Cell records for all used cells
497
+ # ○ DBCELL Stream offsets to the cell records of each row
498
+ block.each do |row|
499
+ write_row row
500
+ end
501
+ block.each do |row|
502
+ write_cellblocks row
503
+ end
504
+ end
505
+ def write_rows
506
+ row_blocks.each do |block|
507
+ write_rowblock block
508
+ end
509
+ end
510
+ def write_saverecalc
511
+ # 0 = Do not recalculate; 1 = Recalculate before saving the document
512
+ write_op 0x005f, [1].pack('v')
513
+ end
514
+ def write_wsbool
515
+ bits = [
516
+ # Bit Mask Contents
517
+ 1, # 0 0x0001 0 = Do not show automatic page breaks
518
+ # 1 = Show automatic page breaks
519
+ 0, # 4 0x0010 0 = Standard sheet
520
+ # 1 = Dialogue sheet (BIFF5-BIFF8)
521
+ 0, # 5 0x0020 0 = No automatic styles in outlines
522
+ # 1 = Apply automatic styles to outlines
523
+ 1, # 6 0x0040 0 = Outline buttons above outline group
524
+ # 1 = Outline buttons below outline group
525
+ 1, # 7 0x0080 0 = Outline buttons left of outline group
526
+ # 1 = Outline buttons right of outline group
527
+ 0, # 8 0x0100 0 = Scale printout in percent (➜ 6.89)
528
+ # 1 = Fit printout to number of pages (➜ 6.89)
529
+ 0, # 9 0x0200 0 = Save external linked values
530
+ # (BIFF3-BIFF4 only, ➜ 5.10)
531
+ # 1 = Do not save external linked values
532
+ # (BIFF3-BIFF4 only, ➜ 5.10)
533
+ 1, # 10 0x0400 0 = Do not show row outline symbols
534
+ # 1 = Show row outline symbols
535
+ 0, # 11 0x0800 0 = Do not show column outline symbols
536
+ # 1 = Show column outline symbols
537
+ 0, # 13-12 0x3000 These flags specify the arrangement of windows.
538
+ # They are stored in BIFF4 only.
539
+ # 00 = Arrange windows tiled
540
+ # 01 = Arrange windows horizontal
541
+ 0, # 10 = Arrange windows vertical
542
+ # 11 = Arrange windows cascaded
543
+ # The following flags are valid for BIFF4-BIFF8 only:
544
+ 0, # 14 0x4000 0 = Standard expression evaluation
545
+ # 1 = Alternative expression evaluation
546
+ 0, # 15 0x8000 0 = Standard formula entries
547
+ # 1 = Alternative formula entries
548
+ ]
549
+ weights = [4,5,6,7,8,9,10,11,12,13,14,15]
550
+ value = bits.inject do |a, b| a | (b << weights.shift) end
551
+ write_op 0x0081, [value].pack('v')
552
+ end
553
+ end
554
+ end
555
+ end
556
+ end