writeexcel 0.6.9 → 0.6.10

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 (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