writeexcel 0.6.9 → 0.6.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/README.rdoc +2 -0
  2. data/VERSION +1 -1
  3. data/lib/writeexcel/biffwriter.rb +29 -43
  4. data/lib/writeexcel/cell_range.rb +332 -0
  5. data/lib/writeexcel/chart.rb +50 -51
  6. data/lib/writeexcel/col_info.rb +87 -0
  7. data/lib/writeexcel/comments.rb +456 -0
  8. data/lib/writeexcel/convert_date_time.rb +117 -0
  9. data/lib/writeexcel/data_validations.rb +370 -0
  10. data/lib/writeexcel/debug_info.rb +5 -1
  11. data/lib/writeexcel/embedded_chart.rb +35 -0
  12. data/lib/writeexcel/format.rb +1 -1
  13. data/lib/writeexcel/formula.rb +3 -3
  14. data/lib/writeexcel/helper.rb +3 -0
  15. data/lib/writeexcel/image.rb +61 -1
  16. data/lib/writeexcel/olewriter.rb +2 -8
  17. data/lib/writeexcel/outline.rb +24 -0
  18. data/lib/writeexcel/shared_string_table.rb +153 -0
  19. data/lib/writeexcel/workbook.rb +86 -444
  20. data/lib/writeexcel/worksheet.rb +693 -2029
  21. data/lib/writeexcel/worksheets.rb +25 -0
  22. data/lib/writeexcel/write_file.rb +34 -15
  23. data/test/test_02_merge_formats.rb +0 -4
  24. data/test/test_04_dimensions.rb +0 -4
  25. data/test/test_05_rows.rb +0 -4
  26. data/test/test_06_extsst.rb +3 -6
  27. data/test/test_11_date_time.rb +0 -4
  28. data/test/test_12_date_only.rb +262 -231
  29. data/test/test_13_date_seconds.rb +0 -4
  30. data/test/test_21_escher.rb +71 -84
  31. data/test/test_22_mso_drawing_group.rb +0 -4
  32. data/test/test_23_note.rb +5 -21
  33. data/test/test_24_txo.rb +6 -7
  34. data/test/test_25_position_object.rb +0 -4
  35. data/test/test_26_autofilter.rb +0 -5
  36. data/test/test_27_autofilter.rb +0 -5
  37. data/test/test_28_autofilter.rb +0 -5
  38. data/test/test_29_process_jpg.rb +1 -1
  39. data/test/test_30_validation_dval.rb +4 -7
  40. data/test/test_31_validation_dv_strings.rb +9 -12
  41. data/test/test_32_validation_dv_formula.rb +11 -14
  42. data/test/test_42_set_properties.rb +0 -3
  43. data/test/test_50_name_stored.rb +0 -4
  44. data/test/test_51_name_print_area.rb +0 -4
  45. data/test/test_52_name_print_titles.rb +0 -4
  46. data/test/test_53_autofilter.rb +0 -4
  47. data/test/test_60_chart_generic.rb +42 -46
  48. data/test/test_61_chart_subclasses.rb +7 -11
  49. data/test/test_62_chart_formats.rb +12 -16
  50. data/test/test_63_chart_area_formats.rb +3 -7
  51. data/test/test_biff.rb +0 -4
  52. data/test/test_big_workbook.rb +17 -0
  53. data/test/test_format.rb +0 -3
  54. data/test/test_ole.rb +0 -4
  55. data/test/test_storage_lite.rb +0 -9
  56. data/test/test_workbook.rb +0 -4
  57. data/test/test_worksheet.rb +3 -7
  58. data/writeexcel.gemspec +12 -2
  59. metadata +12 -2
@@ -477,15 +477,42 @@ def embedded=(val) # :nodoc:
477
477
  end
478
478
 
479
479
  #
480
- # The parent Worksheet class needs to store some data in memory and some in
481
- # temporary files for efficiency. The Chart* classes don't need to do this
482
- # since they are dealing with smaller amounts of data so we override
483
- # _prepend() to turn it into an _append() method. This allows for a more
484
- # natural method calling order.
480
+ # Setup the default configuration data for an embedded chart.
485
481
  #
486
- def prepend(*args) # :nodoc:
487
- @using_tmpfile = false
488
- append(*args)
482
+ def set_embedded_config_data # :nodoc:
483
+ @embedded = true
484
+
485
+ @chartarea = {
486
+ :visible => 1,
487
+ :fg_color_index => 0x4E,
488
+ :fg_color_rgb => 0xFFFFFF,
489
+ :bg_color_index => 0x4D,
490
+ :bg_color_rgb => 0x000000,
491
+ :area_pattern => 0x0001,
492
+ :area_options => 0x0001,
493
+ :line_pattern => 0x0000,
494
+ :line_weight => 0x0000,
495
+ :line_color_index => 0x4D,
496
+ :line_color_rgb => 0x000000,
497
+ :line_options => 0x0009,
498
+ }
499
+
500
+ @config = default_config_data.merge({
501
+ :axisparent => [ 0, 0x01D8, 0x031D, 0x0D79, 0x07E9 ],
502
+ :axisparent_pos => [ 2, 2, 0x010C, 0x0292, 0x0E46, 0x09FD ],
503
+ :chart => [ 0x0000, 0x0000, 0x01847FE8, 0x00F47FE8 ],
504
+ :font_numbers => [ 5, 10, 0x1DC4, 0x1284, 0x0000 ],
505
+ :font_series => [ 6, 10, 0x1DC4, 0x1284, 0x0001 ],
506
+ :font_title => [ 7, 12, 0x1DC4, 0x1284, 0x0000 ],
507
+ :font_axes => [ 8, 10, 0x1DC4, 0x1284, 0x0001 ],
508
+ :legend => [ 0x044E, 0x0E4A, 0x088D, 0x0123, 0x0, 0x1, 0xF ],
509
+ :legend_pos => [ 5, 2, 0x044E, 0x0E4A, 0, 0 ],
510
+ :legend_text => [ 0xFFFFFFD9, 0xFFFFFFC1, 0, 0, 0x00B1, 0x0000 ],
511
+ :series_text => [ 0xFFFFFFD9, 0xFFFFFFC1, 0, 0, 0x00B1, 0x1020 ],
512
+ :title_text => [ 0x060F, 0x004C, 0x038A, 0x016F, 0x0081, 0x1030 ],
513
+ :x_axis_text => [ 0x07EF, 0x0C8F, 0x153, 0x123, 0x81, 0x00 ],
514
+ :y_axis_text => [ 0x0057, 0x0564, 0xB5, 0x035D, 0x0281, 0x00, 90 ],
515
+ })
489
516
  end
490
517
 
491
518
  #
@@ -554,6 +581,21 @@ def close # :nodoc:
554
581
  store_eof
555
582
  end
556
583
 
584
+ private
585
+
586
+ #
587
+ # The parent Worksheet class needs to store some data in memory and some in
588
+ # temporary files for efficiency. The Chart* classes don't need to do this
589
+ # since they are dealing with smaller amounts of data so we override
590
+ # _prepend() to turn it into an _append() method. This allows for a more
591
+ # natural method calling order.
592
+ #
593
+ def prepend(*args) # :nodoc:
594
+ @using_tmpfile = false
595
+ append(*args)
596
+ end
597
+
598
+
557
599
  #
558
600
  # Write BIFF record Window2. Note, this overrides the parent Worksheet
559
601
  # record because the Chart version of the record is smaller and is used
@@ -704,7 +746,6 @@ def get_color_rbg(index) # :nodoc:
704
746
  def palette
705
747
  @workbook.palette
706
748
  end
707
- private :palette
708
749
 
709
750
  #
710
751
  # Get the Excel chart index for line pattern that corresponds to the user
@@ -822,7 +863,6 @@ def _formula_type_from_param(t, f, params, key) # :nodoc:
822
863
  (v.nil? || v == [""] || v == '' || v == 0) ? f : t
823
864
  end
824
865
  end
825
- private :_formula_type_from_param
826
866
 
827
867
  #
828
868
  # Write the SERIES chart substream.
@@ -878,7 +918,6 @@ def store_series_text_stream(font_index) # :nodoc:
878
918
  def _formula_type(t, f, formula) # :nodoc:
879
919
  (formula.nil? || formula == [""] || formula == '' || formula == 0) ? f : t
880
920
  end
881
- private :_formula_type
882
921
 
883
922
  #
884
923
  # Write the X-axis TEXT substream.
@@ -1053,7 +1092,6 @@ def store_area_frame_stream_common(type)
1053
1092
 
1054
1093
  store_end
1055
1094
  end
1056
- private :store_area_frame_stream_common
1057
1095
 
1058
1096
  #
1059
1097
  # Write the CHARTFORMAT chart substream.
@@ -1924,46 +1962,7 @@ def default_config_data # :nodoc:
1924
1962
  :y_axis_text_pos => [ 2, 2, 0, 0, 0x17, 0x44 ],
1925
1963
  }
1926
1964
  end
1927
- private :default_config_data
1928
-
1929
- #
1930
- # Setup the default configuration data for an embedded chart.
1931
- #
1932
- def set_embedded_config_data # :nodoc:
1933
- @embedded = true
1934
1965
 
1935
- @chartarea = {
1936
- :visible => 1,
1937
- :fg_color_index => 0x4E,
1938
- :fg_color_rgb => 0xFFFFFF,
1939
- :bg_color_index => 0x4D,
1940
- :bg_color_rgb => 0x000000,
1941
- :area_pattern => 0x0001,
1942
- :area_options => 0x0001,
1943
- :line_pattern => 0x0000,
1944
- :line_weight => 0x0000,
1945
- :line_color_index => 0x4D,
1946
- :line_color_rgb => 0x000000,
1947
- :line_options => 0x0009,
1948
- }
1949
-
1950
- @config = default_config_data.merge({
1951
- :axisparent => [ 0, 0x01D8, 0x031D, 0x0D79, 0x07E9 ],
1952
- :axisparent_pos => [ 2, 2, 0x010C, 0x0292, 0x0E46, 0x09FD ],
1953
- :chart => [ 0x0000, 0x0000, 0x01847FE8, 0x00F47FE8 ],
1954
- :font_numbers => [ 5, 10, 0x1DC4, 0x1284, 0x0000 ],
1955
- :font_series => [ 6, 10, 0x1DC4, 0x1284, 0x0001 ],
1956
- :font_title => [ 7, 12, 0x1DC4, 0x1284, 0x0000 ],
1957
- :font_axes => [ 8, 10, 0x1DC4, 0x1284, 0x0001 ],
1958
- :legend => [ 0x044E, 0x0E4A, 0x088D, 0x0123, 0x0, 0x1, 0xF ],
1959
- :legend_pos => [ 5, 2, 0x044E, 0x0E4A, 0, 0 ],
1960
- :legend_text => [ 0xFFFFFFD9, 0xFFFFFFC1, 0, 0, 0x00B1, 0x0000 ],
1961
- :series_text => [ 0xFFFFFFD9, 0xFFFFFFC1, 0, 0, 0x00B1, 0x1020 ],
1962
- :title_text => [ 0x060F, 0x004C, 0x038A, 0x016F, 0x0081, 0x1030 ],
1963
- :x_axis_text => [ 0x07EF, 0x0C8F, 0x153, 0x123, 0x81, 0x00 ],
1964
- :y_axis_text => [ 0x0057, 0x0564, 0xB5, 0x035D, 0x0281, 0x00, 90 ],
1965
- })
1966
- end
1967
1966
  end # class Chart
1968
1967
 
1969
1968
  end # module Writeexcel
@@ -0,0 +1,87 @@
1
+ module Writeexcel
2
+
3
+ class Worksheet < BIFFWriter
4
+ require 'writeexcel/helper'
5
+
6
+ class ColInfo
7
+ attr_reader :level
8
+
9
+ #
10
+ # new(firstcol, lastcol, width, [format, hidden, level, collapsed])
11
+ #
12
+ # firstcol : First formatted column
13
+ # lastcol : Last formatted column
14
+ # width : Col width in user units, 8.43 is default
15
+ # format : format object
16
+ # hidden : hidden flag
17
+ # level : outline level
18
+ # collapsed : ?
19
+ #
20
+ def initialize(*args)
21
+ @firstcol, @lastcol, @width, @format, @hidden, @level, @collapsed = args
22
+ @width ||= 8.43 # default width
23
+ @level ||= 0 # default level
24
+ end
25
+
26
+ # Write BIFF record COLINFO to define column widths
27
+ #
28
+ # Note: The SDK says the record length is 0x0B but Excel writes a 0x0C
29
+ # length record.
30
+ #
31
+ def biff_record
32
+ record = 0x007D # Record identifier
33
+ length = 0x000B # Number of bytes to follow
34
+
35
+ coldx = (pixels * 256 / 7).to_i # Col width in internal units
36
+ reserved = 0x00 # Reserved
37
+
38
+ header = [record, length].pack("vv")
39
+ data = [@firstcol, @lastcol, coldx,
40
+ ixfe, grbit, reserved].pack("vvvvvC")
41
+ [header, data]
42
+ end
43
+
44
+ # Excel rounds the column width to the nearest pixel. Therefore we first
45
+ # convert to pixels and then to the internal units. The pixel to users-units
46
+ # relationship is different for values less than 1.
47
+ #
48
+ def pixels
49
+ if @width < 1
50
+ result = @width * 12
51
+ else
52
+ result = @width * 7 + 5
53
+ end
54
+ result.to_i
55
+ end
56
+
57
+ def ixfe
58
+ if @format && @format.respond_to?(:xf_index)
59
+ ixfe = @format.xf_index
60
+ else
61
+ ixfe = 0x0F
62
+ end
63
+ end
64
+
65
+ # Set the limits for the outline levels (0 <= x <= 7).
66
+ def level
67
+ if @level < 0
68
+ 0
69
+ elsif 7 < @level
70
+ 7
71
+ else
72
+ @level
73
+ end
74
+ end
75
+
76
+ # Set the options flags. (See set_row() for more details).
77
+ def grbit
78
+ grbit = 0x0000 # Option flags
79
+ grbit |= 0x0001 if @hidden && @hidden != 0
80
+ grbit |= level << 8
81
+ grbit |= 0x1000 if @collapsed && @collapsed != 0
82
+ grbit
83
+ end
84
+ end
85
+ end
86
+
87
+ end
@@ -0,0 +1,456 @@
1
+ module Writeexcel
2
+
3
+ class Worksheet < BIFFWriter
4
+ require 'writeexcel/helper'
5
+
6
+ class Collection
7
+ def initialize
8
+ @items = {}
9
+ end
10
+
11
+ def <<(item)
12
+ @items[item.row] = { item.col => item }
13
+ end
14
+
15
+ def array
16
+ return @array if @array
17
+
18
+ @array = []
19
+ @items.keys.sort.each do |row|
20
+ @items[row].keys.sort.each do |col|
21
+ @array << @items[row][col]
22
+ end
23
+ end
24
+ @array
25
+ end
26
+
27
+ end
28
+
29
+ class Comments < Collection
30
+ attr_writer :visible
31
+
32
+ def initialize
33
+ super
34
+ @visible = false
35
+ end
36
+
37
+ def visible?
38
+ @visible
39
+ end
40
+ end
41
+
42
+ class Comment
43
+ attr_reader :row, :col, :string, :encoding, :author, :author_encoding, :visible, :color, :vertices
44
+
45
+ def initialize(worksheet, row, col, string, options = {})
46
+ @worksheet = worksheet
47
+ @row, @col = row, col
48
+ @params = params_with(options)
49
+ @string, @params[:encoding] = string_and_encoding(string, @params[:encoding], 'comment')
50
+
51
+ # Limit the string to the max number of chars (not bytes).
52
+ max_len = 32767
53
+ max_len = max_len * 2 if @params[:encoding] != 0
54
+
55
+ if @string.bytesize > max_len
56
+ @string = @string[0 .. max_len]
57
+ end
58
+ @encoding = @params[:encoding]
59
+ @author = @params[:author]
60
+ @author_encoding = @params[:author_encoding]
61
+ @visible = @params[:visible]
62
+ @color = @params[:color]
63
+ @vertices = calc_vertices
64
+ end
65
+
66
+ def store_comment_record(i, num_objects, num_comments, spid)
67
+ str_len = string.bytesize
68
+ str_len = str_len / 2 if encoding != 0 # Num of chars not bytes.
69
+
70
+ spid = store_comment_mso_drawing_record(i, num_objects, num_comments, spid, visible, color, vertices)
71
+ store_obj_comment(num_objects + i + 1)
72
+ store_mso_drawing_text_box
73
+ store_txo(str_len)
74
+ store_txo_continue_1(string, encoding)
75
+ formats = [[0, 9], [str_len, 0]]
76
+ store_txo_continue_2(formats)
77
+ spid
78
+ end
79
+
80
+ #
81
+ # Write the worksheet NOTE record that is part of cell comments.
82
+ #
83
+ def store_note_record(obj_id) #:nodoc:
84
+ comment_author = author
85
+ comment_author_enc = author_encoding
86
+ ruby_19 { comment_author = [comment_author].pack('a*') if comment_author.ascii_only? }
87
+ record = 0x001C # Record identifier
88
+ length = 0x000C # Bytes to follow
89
+
90
+ comment_author = '' unless comment_author
91
+ comment_author_enc = 0 unless author_encoding
92
+
93
+ # Use the visible flag if set by the user or else use the worksheet value.
94
+ # The flag is also set in store_mso_opt_comment() but with the opposite
95
+ # value.
96
+ if visible
97
+ comment_visible = visible != 0 ? 0x0002 : 0x0000
98
+ else
99
+ comment_visible = @worksheet.comments_visible? ? 0x0002 : 0x0000
100
+ end
101
+
102
+ # Get the number of chars in the author string (not bytes).
103
+ num_chars = comment_author.bytesize
104
+ num_chars = num_chars / 2 if comment_author_enc != 0 && comment_author_enc
105
+
106
+ # Null terminate the author string.
107
+ comment_author =
108
+ ruby_18 { comment_author + "\0" } ||
109
+ ruby_19 { comment_author.force_encoding('BINARY') + "\0".force_encoding('BINARY') }
110
+
111
+ # Pack the record.
112
+ data = [row, col, comment_visible, obj_id, num_chars, comment_author_enc].pack("vvvvvC")
113
+
114
+ length = data.bytesize + comment_author.bytesize
115
+ header = [record, length].pack("vv")
116
+
117
+ append(header, data, comment_author)
118
+ end
119
+
120
+ #
121
+ # Write the Escher Opt record that is part of MSODRAWING.
122
+ #
123
+ def store_mso_opt_comment(spid, visible = nil, colour = 0x50) #:nodoc:
124
+ type = 0xF00B
125
+ version = 3
126
+ instance = 9
127
+ data = ''
128
+ length = 54
129
+
130
+ # Use the visible flag if set by the user or else use the worksheet value.
131
+ # Note that the value used is the opposite of Comment#note_record.
132
+ #
133
+ if visible
134
+ visible = visible != 0 ? 0x0000 : 0x0002
135
+ else
136
+ visible = @worksheet.comments_visible? ? 0x0000 : 0x0002
137
+ end
138
+
139
+ data = [spid].pack('V') +
140
+ ['0000BF00080008005801000000008101'].pack("H*") +
141
+ [colour].pack("C") +
142
+ ['000008830150000008BF011000110001'+'02000000003F0203000300BF03'].pack("H*") +
143
+ [visible].pack('v') +
144
+ ['0A00'].pack('H*')
145
+
146
+ @worksheet.add_mso_generic(type, version, instance, data, length)
147
+ end
148
+
149
+ #
150
+ # OBJ record that is part of cell comments.
151
+ # obj_id # Object ID number.
152
+ #
153
+ def obj_comment_record(obj_id) #:nodoc:
154
+ record = 0x005D # Record identifier
155
+ length = 0x0034 # Bytes to follow
156
+
157
+ obj_type = 0x0019 # Object type (comment).
158
+ data = '' # Record data.
159
+
160
+ sub_record = 0x0000 # Sub-record identifier.
161
+ sub_length = 0x0000 # Length of sub-record.
162
+ sub_data = '' # Data of sub-record.
163
+ options = 0x4011
164
+ reserved = 0x0000
165
+
166
+ # Add ftCmo (common object data) subobject
167
+ sub_record = 0x0015 # ftCmo
168
+ sub_length = 0x0012
169
+ sub_data = [obj_type, obj_id, options, reserved, reserved, reserved].pack( "vvvVVV")
170
+ data = [sub_record, sub_length].pack("vv") + sub_data
171
+
172
+ # Add ftNts (note structure) subobject
173
+ sub_record = 0x000D # ftNts
174
+ sub_length = 0x0016
175
+ sub_data = [reserved,reserved,reserved,reserved,reserved,reserved].pack( "VVVVVv")
176
+ data += [sub_record, sub_length].pack("vv") + sub_data
177
+
178
+ # Add ftEnd (end of object) subobject
179
+ sub_record = 0x0000 # ftNts
180
+ sub_length = 0x0000
181
+ data += [sub_record, sub_length].pack("vv")
182
+
183
+ # Pack the record.
184
+ header = [record, length].pack("vv")
185
+
186
+ header + data
187
+ end
188
+
189
+ private
190
+
191
+ def params_with(options)
192
+ params = default_params.update(options)
193
+
194
+ # Ensure that a width and height have been set.
195
+ params[:width] = default_width unless params[:width] && params[:width] != 0
196
+ params[:width] = params[:width] * params[:x_scale] if params[:x_scale] != 0
197
+ params[:height] = default_height unless params[:height] && params[:height] != 0
198
+ params[:height] = params[:height] * params[:y_scale] if params[:y_scale] != 0
199
+
200
+ params[:author], params[:author_encoding] =
201
+ string_and_encoding(params[:author], params[:author_encoding], 'author')
202
+
203
+ # Set the comment background colour.
204
+ params[:color] = background_color(params[:color])
205
+
206
+ # Set the default start cell and offsets for the comment. These are
207
+ # generally fixed in relation to the parent cell. However there are
208
+ # some edge cases for cells at the, er, edges.
209
+ #
210
+ params[:start_row] = default_start_row unless params[:start_row]
211
+ params[:y_offset] = default_y_offset unless params[:y_offset]
212
+ params[:start_col] = default_start_col unless params[:start_col]
213
+ params[:x_offset] = default_x_offset unless params[:x_offset]
214
+
215
+ params
216
+ end
217
+
218
+ def default_params
219
+ {
220
+ :author => '',
221
+ :author_encoding => 0,
222
+ :encoding => 0,
223
+ :color => nil,
224
+ :start_cell => nil,
225
+ :start_col => nil,
226
+ :start_row => nil,
227
+ :visible => nil,
228
+ :width => default_width,
229
+ :height => default_height,
230
+ :x_offset => nil,
231
+ :x_scale => 1,
232
+ :y_offset => nil,
233
+ :y_scale => 1
234
+ }
235
+ end
236
+
237
+ def default_width
238
+ 128
239
+ end
240
+
241
+ def default_height
242
+ 74
243
+ end
244
+
245
+ def default_start_row
246
+ case @row
247
+ when 0 then 0
248
+ when 65533 then 65529
249
+ when 65534 then 65530
250
+ when 65535 then 65531
251
+ else @row -1
252
+ end
253
+ end
254
+
255
+ def default_y_offset
256
+ case @row
257
+ when 0 then 2
258
+ when 65533 then 4
259
+ when 65534 then 4
260
+ when 65535 then 2
261
+ else 7
262
+ end
263
+ end
264
+
265
+ def default_start_col
266
+ case @col
267
+ when 253 then 250
268
+ when 254 then 251
269
+ when 255 then 252
270
+ else @col + 1
271
+ end
272
+ end
273
+
274
+ def default_x_offset
275
+ case @col
276
+ when 253 then 49
277
+ when 254 then 49
278
+ when 255 then 49
279
+ else 15
280
+ end
281
+ end
282
+
283
+ def string_and_encoding(string, encoding, type)
284
+ string = convert_to_ascii_if_ascii(string)
285
+ if encoding != 0
286
+ raise "Uneven number of bytes in #{type} string" if string.bytesize % 2 != 0
287
+ # Change from UTF-16BE to UTF-16LE
288
+ string = utf16be_to_16le(string)
289
+ # Handle utf8 strings
290
+ else
291
+ if is_utf8?(string)
292
+ string = NKF.nkf('-w16L0 -m0 -W', string)
293
+ ruby_19 { string.force_encoding('UTF-16LE') }
294
+ encoding = 1
295
+ end
296
+ end
297
+ [string, encoding]
298
+ end
299
+
300
+ def background_color(color)
301
+ color = Colors.new.get_color(color)
302
+ color = 0x50 if color == 0x7FFF # Default color.
303
+ color
304
+ end
305
+
306
+ # Calculate the positions of comment object.
307
+ def calc_vertices
308
+ @worksheet.position_object( @params[:start_col],
309
+ @params[:start_row],
310
+ @params[:x_offset],
311
+ @params[:y_offset],
312
+ @params[:width],
313
+ @params[:height]
314
+ )
315
+ end
316
+
317
+ def store_comment_mso_drawing_record(i, num_objects, num_comments, spid, visible, color, vertices)
318
+ if i == 0 && num_objects == 0
319
+ # Write the parent MSODRAWIING record.
320
+ dg_length = 200 + 128 * (num_comments - 1)
321
+ spgr_length = 176 + 128 * (num_comments - 1)
322
+
323
+ data = @worksheet.store_parent_mso_record(dg_length, spgr_length, spid)
324
+ spid += 1
325
+ else
326
+ data = ''
327
+ end
328
+ data += @worksheet.store_mso_sp_container(120) + @worksheet.store_mso_sp(202, spid, 0x0A00)
329
+ spid += 1
330
+ data +=
331
+ store_mso_opt_comment(0x80, visible, color) +
332
+ @worksheet.store_mso_client_anchor(3, *vertices) +
333
+ @worksheet.store_mso_client_data
334
+ record = 0x00EC # Record identifier
335
+ length = data.bytesize
336
+ header = [record, length].pack("vv")
337
+ append(header, data)
338
+
339
+ spid
340
+ end
341
+
342
+ def store_obj_comment(obj_id)
343
+ append(obj_comment_record(obj_id))
344
+ end
345
+
346
+ #
347
+ # Write the MSODRAWING ClientTextbox record that is part of comments.
348
+ #
349
+ def store_mso_drawing_text_box #:nodoc:
350
+ record = 0x00EC # Record identifier
351
+ length = 0x0008 # Bytes to follow
352
+
353
+ data = store_mso_client_text_box
354
+ header = [record, length].pack('vv')
355
+
356
+ append(header, data)
357
+ end
358
+
359
+ #
360
+ # Write the Escher ClientTextbox record that is part of MSODRAWING.
361
+ #
362
+ def store_mso_client_text_box #:nodoc:
363
+ type = 0xF00D
364
+ version = 0
365
+ instance = 0
366
+ data = ''
367
+ length = 0
368
+
369
+ @worksheet.add_mso_generic(type, version, instance, data, length)
370
+ end
371
+
372
+ #
373
+ # Write the worksheet TXO record that is part of cell comments.
374
+ # string_len # Length of the note text.
375
+ # format_len # Length of the format runs.
376
+ # rotation # Options
377
+ #
378
+ def store_txo(string_len, format_len = 16, rotation = 0) #:nodoc:
379
+ record = 0x01B6 # Record identifier
380
+ length = 0x0012 # Bytes to follow
381
+
382
+ grbit = 0x0212 # Options
383
+ reserved = 0x0000 # Options
384
+
385
+ # Pack the record.
386
+ header = [record, length].pack('vv')
387
+ data = [grbit, rotation, reserved, reserved, string_len, format_len, reserved].pack("vvVvvvV")
388
+ append(header, data)
389
+ end
390
+
391
+ #
392
+ # Write the first CONTINUE record to follow the TXO record. It contains the
393
+ # text data.
394
+ # string # Comment string.
395
+ # encoding # Encoding of the string.
396
+ #
397
+ def store_txo_continue_1(string, encoding = 0) #:nodoc:
398
+ # Split long comment strings into smaller continue blocks if necessary.
399
+ # We can't let BIFFwriter::_add_continue() handled this since an extra
400
+ # encoding byte has to be added similar to the SST block.
401
+ #
402
+ # We make the limit size smaller than the add_continue() size and even
403
+ # so that UTF16 chars occur in the same block.
404
+ #
405
+ limit = 8218
406
+ while string.bytesize > limit
407
+ string[0 .. limit] = ""
408
+ tmp_str = string
409
+ data = [encoding].pack("C") +
410
+ ruby_18 { tmp_str } ||
411
+ ruby_19 { tmp_str.force_encoding('ASCII-8BIT') }
412
+ length = data.bytesize
413
+ header = [record, length].pack('vv')
414
+
415
+ append(header, data)
416
+ end
417
+
418
+ # Pack the record.
419
+ data =
420
+ ruby_18 { [encoding].pack("C") + string } ||
421
+ ruby_19 { [encoding].pack("C") + string.force_encoding('ASCII-8BIT') }
422
+
423
+ record = 0x003C # Record identifier
424
+ length = data.bytesize
425
+ header = [record, length].pack('vv')
426
+
427
+ append(header, data)
428
+ end
429
+
430
+ #
431
+ # Write the second CONTINUE record to follow the TXO record. It contains the
432
+ # formatting information for the string.
433
+ # formats # Formatting information
434
+ #
435
+ def store_txo_continue_2(formats) #:nodoc:
436
+ # Pack the record.
437
+ data = ''
438
+
439
+ formats.each do |a_ref|
440
+ data += [a_ref[0], a_ref[1], 0x0].pack('vvV')
441
+ end
442
+
443
+ record = 0x003C # Record identifier
444
+ length = data.bytesize
445
+ header = [record, length].pack("vv")
446
+
447
+ append(header, data)
448
+ end
449
+
450
+ def append(*args)
451
+ @worksheet.append(*args)
452
+ end
453
+ end
454
+ end
455
+
456
+ end