writeexcel 0.6.9 → 0.6.10
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +2 -0
- data/VERSION +1 -1
- data/lib/writeexcel/biffwriter.rb +29 -43
- data/lib/writeexcel/cell_range.rb +332 -0
- data/lib/writeexcel/chart.rb +50 -51
- data/lib/writeexcel/col_info.rb +87 -0
- data/lib/writeexcel/comments.rb +456 -0
- data/lib/writeexcel/convert_date_time.rb +117 -0
- data/lib/writeexcel/data_validations.rb +370 -0
- data/lib/writeexcel/debug_info.rb +5 -1
- data/lib/writeexcel/embedded_chart.rb +35 -0
- data/lib/writeexcel/format.rb +1 -1
- data/lib/writeexcel/formula.rb +3 -3
- data/lib/writeexcel/helper.rb +3 -0
- data/lib/writeexcel/image.rb +61 -1
- data/lib/writeexcel/olewriter.rb +2 -8
- data/lib/writeexcel/outline.rb +24 -0
- data/lib/writeexcel/shared_string_table.rb +153 -0
- data/lib/writeexcel/workbook.rb +86 -444
- data/lib/writeexcel/worksheet.rb +693 -2029
- data/lib/writeexcel/worksheets.rb +25 -0
- data/lib/writeexcel/write_file.rb +34 -15
- data/test/test_02_merge_formats.rb +0 -4
- data/test/test_04_dimensions.rb +0 -4
- data/test/test_05_rows.rb +0 -4
- data/test/test_06_extsst.rb +3 -6
- data/test/test_11_date_time.rb +0 -4
- data/test/test_12_date_only.rb +262 -231
- data/test/test_13_date_seconds.rb +0 -4
- data/test/test_21_escher.rb +71 -84
- data/test/test_22_mso_drawing_group.rb +0 -4
- data/test/test_23_note.rb +5 -21
- data/test/test_24_txo.rb +6 -7
- data/test/test_25_position_object.rb +0 -4
- data/test/test_26_autofilter.rb +0 -5
- data/test/test_27_autofilter.rb +0 -5
- data/test/test_28_autofilter.rb +0 -5
- data/test/test_29_process_jpg.rb +1 -1
- data/test/test_30_validation_dval.rb +4 -7
- data/test/test_31_validation_dv_strings.rb +9 -12
- data/test/test_32_validation_dv_formula.rb +11 -14
- data/test/test_42_set_properties.rb +0 -3
- data/test/test_50_name_stored.rb +0 -4
- data/test/test_51_name_print_area.rb +0 -4
- data/test/test_52_name_print_titles.rb +0 -4
- data/test/test_53_autofilter.rb +0 -4
- data/test/test_60_chart_generic.rb +42 -46
- data/test/test_61_chart_subclasses.rb +7 -11
- data/test/test_62_chart_formats.rb +12 -16
- data/test/test_63_chart_area_formats.rb +3 -7
- data/test/test_biff.rb +0 -4
- data/test/test_big_workbook.rb +17 -0
- data/test/test_format.rb +0 -3
- data/test/test_ole.rb +0 -4
- data/test/test_storage_lite.rb +0 -9
- data/test/test_workbook.rb +0 -4
- data/test/test_worksheet.rb +3 -7
- data/writeexcel.gemspec +12 -2
- metadata +12 -2
data/lib/writeexcel/chart.rb
CHANGED
@@ -477,15 +477,42 @@ def embedded=(val) # :nodoc:
|
|
477
477
|
end
|
478
478
|
|
479
479
|
#
|
480
|
-
#
|
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
|
487
|
-
@
|
488
|
-
|
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
|