slayer-surpass 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,218 @@
1
+ class ObjBmpRecord < BiffRecord
2
+ RECORD_ID = 0x005D # Record identifier
3
+
4
+ def initialize(row, col, sheet, im_data_bmp, x, y, scale_x, scale_y)
5
+ width = im_data_bmp.width * scale_x
6
+ height = im_data_bmp.height * scale_y
7
+
8
+ col_start, x1, row_start, y1, col_end, x2, row_end, y2 = position_image(sheet, row, col, x, y, width, height)
9
+
10
+ # Store the OBJ record that precedes an IMDATA record. This could be generalise
11
+ # to support other Excel objects.
12
+ cobj = 0x0001 # count of objects in file (set to 1)
13
+ ot = 0x0008 # object type. 8 = picture
14
+ id = 0x0001 # object id
15
+ grbit = 0x0614 # option flags
16
+ coll = col_start # col containing upper left corner of object
17
+ dxl = x1 # distance from left side of cell
18
+ rwt = row_start # row containing top left corner of object
19
+ dyt = y1 # distance from top of cell
20
+ colr = col_end # col containing lower right corner of object
21
+ dxr = x2 # distance from right of cell
22
+ rwb = row_end # row containing bottom right corner of object
23
+ dyb = y2 # distance from bottom of cell
24
+ cbmacro = 0x0000 # length of fmla structure
25
+ reserved1 = 0x0000 # reserved
26
+ reserved2 = 0x0000 # reserved
27
+ icvback = 0x09 # background colour
28
+ icvfore = 0x09 # foreground colour
29
+ fls = 0x00 # fill pattern
30
+ fauto = 0x00 # automatic fill
31
+ icv = 0x08 # line colour
32
+ lns = 0xff # line style
33
+ lnw = 0x01 # line weight
34
+ fautob = 0x00 # automatic border
35
+ frs = 0x0000 # frame style
36
+ cf = 0x0009 # image format, 9 = bitmap
37
+ reserved3 = 0x0000 # reserved
38
+ cbpictfmla = 0x0000 # length of fmla structure
39
+ reserved4 = 0x0000 # reserved
40
+ grbit2 = 0x0001 # option flags
41
+ reserved5 = 0x0000 # reserved
42
+
43
+ args = [cobj, ot, id, grbit, coll, dxl, rwt, dyt, colr, dxr, rwb, dyb, cbmacro, reserved1, reserved2, icvback, icvfore, fls, fauto, icv, lns, lnw, fautob, frs, cf, reserved3, cbpictfmla, reserved4, grbit2, reserved5]
44
+ @record_data = args.pack('L v12 L v C8 v L v4 L')
45
+ end
46
+
47
+ # Calculate the vertices that define the position of the image as required by
48
+ # the OBJ record.
49
+ #
50
+ # +------------+------------+
51
+ # | A | B |
52
+ # +-----+------------+------------+
53
+ # | |(x1,y1) | |
54
+ # | 1 |(A1)._______|______ |
55
+ # | | | | |
56
+ # | | | | |
57
+ # +-----+----| BITMAP |-----+
58
+ # | | | | |
59
+ # | 2 | |______________. |
60
+ # | | | (B2)|
61
+ # | | | (x2,y2)|
62
+ # +---- +------------+------------+
63
+ #
64
+ # Example of a bitmap that covers some of the area from cell A1 to cell B2.
65
+ #
66
+ # Based on the width and height of the bitmap we need to calculate 8 vars:
67
+ # col_start, row_start, col_end, row_end, x1, y1, x2, y2.
68
+ # The width and height of the cells are also variable and have to be taken into
69
+ # account.
70
+ # The values of col_start and row_start are passed in from the calling
71
+ # function. The values of col_end and row_end are calculated by subtracting
72
+ # the width and height of the bitmap from the width and height of the
73
+ # underlying cells.
74
+ # The vertices are expressed as a percentage of the underlying cell width as
75
+ # follows (rhs values are in pixels):
76
+ #
77
+ # x1 = X / W *1024
78
+ # y1 = Y / H *256
79
+ # x2 = (X-1) / W *1024
80
+ # y2 = (Y-1) / H *256
81
+ #
82
+ # Where: X is distance from the left side of the underlying cell
83
+ # Y is distance from the top of the underlying cell
84
+ # W is the width of the cell
85
+ # H is the height of the cell
86
+ #
87
+ # Note: the SDK incorrectly states that the height should be expressed as a
88
+ # percentage of 1024.
89
+ #
90
+ # col_start - Col containing upper left corner of object
91
+ # row_start - Row containing top left corner of object
92
+ # x1 - Distance to left side of object
93
+ # y1 - Distance to top of object
94
+ # width - Width of image frame
95
+ # height - Height of image frame
96
+ def position_image(sheet, row_start, col_start, x1, y1, width, height)
97
+ while x1 >= size_col(sheet, col_start) do
98
+ x1 -= size_col(sheet, col_start)
99
+ col_start += 1
100
+ end
101
+
102
+ # Adjust start row for offsets that are greater than the row height
103
+ while y1 >= size_row(sheet, row_start) do
104
+ y1 -= size_row(sheet, row_start)
105
+ row_start += 1
106
+ end
107
+
108
+ # Initialise end cell to the same as the start cell
109
+ row_end = row_start # Row containing bottom right corner of object
110
+ col_end = col_start # Col containing lower right corner of object
111
+ width = width + x1 - 1
112
+ height = height + y1 - 1
113
+
114
+ # Subtract the underlying cell widths to find the end cell of the image
115
+ while (width >= size_col(sheet, col_end)) do
116
+ width -= size_col(sheet, col_end)
117
+ col_end += 1
118
+ end
119
+
120
+ # Subtract the underlying cell heights to find the end cell of the image
121
+ while (height >= size_row(sheet, row_end)) do
122
+ height -= size_row(sheet, row_end)
123
+ row_end += 1
124
+ end
125
+
126
+ # Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
127
+ # with zero height or width.
128
+ starts_or_ends_in_hidden_cell = ((size_col(sheet, col_start) == 0) or (size_col(sheet, col_end) == 0) or (size_row(sheet, row_start) == 0) or (size_row(sheet, row_end) == 0))
129
+ return if starts_or_ends_in_hidden_cell
130
+
131
+ # Convert the pixel values to the percentage value expected by Excel
132
+ x1 = (x1.to_f / size_col(sheet, col_start) * 1024).to_i
133
+ y1 = (y1.to_f / size_row(sheet, row_start) * 256).to_i
134
+ # Distance to right side of object
135
+ x2 = (width.to_f / size_col(sheet, col_end) * 1024).to_i
136
+ # Distance to bottom of object
137
+ y2 = (height.to_f / size_row(sheet, row_end) * 256).to_i
138
+
139
+ [col_start, x1, row_start, y1, col_end, x2, row_end, y2]
140
+ end
141
+
142
+ def size_col(sheet, col)
143
+ sheet.col_width(col)
144
+ end
145
+
146
+ def size_row(sheet, row)
147
+ sheet.row_height(row)
148
+ end
149
+ end
150
+
151
+ class ImDataBmpRecord < BiffRecord
152
+ RECORD_ID = 0x007F
153
+
154
+ attr_accessor :width
155
+ attr_accessor :height
156
+ attr_accessor :size
157
+
158
+ # Insert a 24bit bitmap image in a worksheet. The main record required is
159
+ # IMDATA but it must be proceeded by a OBJ record to define its position.
160
+ def initialize(filename)
161
+ @width, @height, @size, data = process_bitmap(filename)
162
+
163
+ cf = 0x09
164
+ env = 0x01
165
+ lcb = @size
166
+
167
+ @record_data = [cf, env, lcb].pack('v2L') + data
168
+ end
169
+
170
+ # Convert a 24 bit bitmap into the modified internal format used by Windows.
171
+ # This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the
172
+ # MSDN library.
173
+ def process_bitmap(filename)
174
+ data = nil
175
+ File.open(filename, "rb") do |f|
176
+ data = f.read
177
+ end
178
+
179
+ raise "bitmap #{filename} doesn't contain enough data" if data.length <= 0x36
180
+ raise "bitmap #{filename} is not valid" unless data[0, 2] === "BM"
181
+
182
+ # Remove bitmap data: ID.
183
+ data = data[2..-1]
184
+
185
+ # Read and remove the bitmap size. This is more reliable than reading
186
+ # the data size at offset 0x22.
187
+ size = data[0,4].unpack('L')[0]
188
+ size -= 0x36 # Subtract size of bitmap header.
189
+ size += 0x0C # Add size of BIFF header.
190
+
191
+ data = data[4..-1]
192
+ # Remove bitmap data: reserved, offset, header length.
193
+ data = data[12..-1]
194
+ # Read and remove the bitmap width and height. Verify the sizes.
195
+ width, height = data[0,8].unpack('L2')
196
+ data = data[8..-1]
197
+ raise "bitmap #{filename} largest image width supported is 65k." if (width > 0xFFFF)
198
+ raise "bitmap #{filename} largest image height supported is 65k." if (height > 0xFFFF)
199
+
200
+ # Read and remove the bitmap planes and bpp data. Verify them.
201
+ planes, bitcount = data[0,4].unpack('v2')
202
+ data = data[4..-1]
203
+ raise "bitmap #{filename} isn't a 24bit true color bitmap." if (bitcount != 24)
204
+ raise "bitmap #{filename} only 1 plane supported in bitmap image." if (planes != 1)
205
+
206
+ # Read and remove the bitmap compression. Verify compression.
207
+ compression = data[0,4].unpack('L')[0]
208
+ data = data[4..-1]
209
+ raise "bitmap #{filename} compression not supported in bitmap image." if (compression != 0)
210
+
211
+ # Remove bitmap data: data size, hres, vres, colours, imp. colours.
212
+ data = data[20..-1]
213
+ # Add the BITMAPCOREHEADER data
214
+ header = [0x000c, width, height, 0x01, 0x18].pack('Lv4')
215
+
216
+ [width, height, size, header + data]
217
+ end
218
+ end
@@ -0,0 +1,187 @@
1
+ class Cell
2
+ attr_reader :index
3
+
4
+ def set_style(style)
5
+ style = StyleFormat.new(style) if style.is_a?(Hash)
6
+ @format_index = @parent.parent_wb.styles.add(style)
7
+ end
8
+
9
+ def row
10
+ @parent
11
+ end
12
+
13
+ def col
14
+ @index
15
+ end
16
+ end
17
+
18
+ ### @export "string-cell"
19
+ class StringCell < Cell
20
+ def initialize(parent, index, format_index, sst_index)
21
+ @parent = parent
22
+ @index = index
23
+ @format_index = format_index
24
+ @sst_index = sst_index
25
+ end
26
+
27
+ def to_biff
28
+ LabelSSTRecord.new(@parent.index, @index, @format_index, @sst_index).to_biff
29
+ end
30
+ end
31
+
32
+ ### @export "blank-cell"
33
+ class BlankCell < Cell
34
+ def initialize(parent, index, format_index)
35
+ @parent = parent
36
+ @index = index
37
+ @format_index = format_index
38
+ end
39
+
40
+ def to_biff
41
+ BlankRecord.new(@parent.index, @index, @format_index).to_biff
42
+ end
43
+ end
44
+ ### @end
45
+
46
+ class NumberCell < Cell
47
+ def initialize(parent, index, format_index, number)
48
+ @parent = parent
49
+ @index = index
50
+ @format_index = format_index
51
+ @number = number
52
+ end
53
+
54
+ def rk_record(rk_encoded)
55
+ RKRecord.new(@parent.index, @index, @format_index, rk_encoded).to_biff
56
+ end
57
+
58
+ # TODO test this section to be sure numbers are categorized and packed correctly.
59
+ def to_biff
60
+ # 30 bit signed int
61
+ in_range = (-0x20000000 <= @number) && (@number < 0x20000000)
62
+ is_int = (@number.to_i == @number)
63
+ if in_range && is_int
64
+ rk_encoded = 2 | (@number.to_i << 2)
65
+ return rk_record(rk_encoded)
66
+ end
67
+
68
+ # try scaling by 100 then using a 30 bit signed int
69
+ in_range = (-0x20000000 <= @number * 100) && (@number * 100 < 0x20000000)
70
+ round_trip = (@number.to_i*100) == @number*100
71
+ if in_range && round_trip
72
+ rk_encoded = (3 | (@number.to_i*100 << 2))
73
+ return rk_record(rk_encoded)
74
+ end
75
+
76
+ w0, w1, w2, w3 = [@number].pack('E').unpack('v4')
77
+
78
+ is_float_rk = (w0 == 0) && (w1 == 0) && (w2 & 0xFFFC) == w2
79
+ if is_float_rk
80
+ rk_encoded = (w3 << 16) | w2
81
+ return rk_record(rk_encoded)
82
+ end
83
+
84
+ w0, w1, w2, w3 = [@number * 100].pack('E').unpack('v4')
85
+
86
+ is_float_rk_100 = w0 == 0 && w1 == 0 && w2 & 0xFFFC == w2
87
+ if is_float_rk_100
88
+ rk_encoded = 1 | (w3 << 16) | w2
89
+ return rk_record(rk_encoded)
90
+ end
91
+
92
+ # If not an RK value, use a NumberRecord instead.
93
+ NumberRecord.new(@parent.index, @index, @format_index, @number).to_biff
94
+ end
95
+ end
96
+
97
+ class MulNumberCell < Cell
98
+ def initialize(parent, index, format_index, sst_index)
99
+ @parent = parent
100
+ @index = index
101
+ @format_index = format_index
102
+ @sst_index = sst_index
103
+ end
104
+
105
+ def to_biff
106
+ raise "not implemented"
107
+ end
108
+ end
109
+
110
+ class MulBlankCell < Cell
111
+ def initialize(parent, col1, col2, xf_idx)
112
+ raise unless col1 < col2
113
+ @parent = parent
114
+ @col1 = col1
115
+ @col2 = col2
116
+ @xf_idx = xf_idx
117
+ end
118
+
119
+ def to_biff
120
+ MulBlankRecord.new(@parent.index, @col1, @col2, @xf_idx).to_biff
121
+ end
122
+ end
123
+
124
+ ### @export "formula-cell"
125
+ class FormulaCell < Cell
126
+ def initialize(parent, index, format_index, formula, calc_flags = 0)
127
+ @parent = parent
128
+ @index = index
129
+ @format_index = format_index
130
+ @formula = formula
131
+ @calc_flags = calc_flags
132
+ end
133
+
134
+ def to_biff
135
+ args = [@parent.index, @index, @format_index, @formula.to_biff, @calc_flags]
136
+ FormulaRecord.new(*args).to_biff
137
+ end
138
+ end
139
+ ### @end
140
+
141
+ class BooleanCell < Cell
142
+ def initialize(parent, index, format_index, number)
143
+ @parent = parent
144
+ @index = index
145
+ @format_index = format_index
146
+ @number = number
147
+ @is_error = 0
148
+ end
149
+
150
+ def to_biff
151
+ number = @number ? 1 : 0
152
+ BoolErrRecord.new(@parent.index, @index, @format_index, number, @is_error).to_biff
153
+ end
154
+ end
155
+
156
+ class ErrorCell < Cell
157
+ ERROR_CODES = {
158
+ 0x00 => 0, # Intersection of two cell ranges is empty
159
+ 0x07 => 7, # Division by zero
160
+ 0x0F => 15, # Wrong type of operand
161
+ 0x17 => 23, # Illegal or deleted cell reference
162
+ 0x1D => 29, # Wrong function or range name
163
+ 0x24 => 36, # Value range overflow
164
+ 0x2A => 42, # Argument or function not available
165
+ '#NULL!' => 0, # Intersection of two cell ranges is empty
166
+ '#DIV/0!' => 7, # Division by zero
167
+ '#VALUE!' => 36, # Wrong type of operand
168
+ '#REF!' => 23, # Illegal or deleted cell reference
169
+ '#NAME?' => 29, # Wrong function or range name
170
+ '#NUM!' => 36, # Value range overflow
171
+ '#N/A!' => 42 # Argument or function not available
172
+ }
173
+
174
+ def initialize(parent, index, format_index, error_string_or_code)
175
+ @parent = parent
176
+ @index = index
177
+ @format_index = format_index
178
+ @number = ERROR_CODES[error_string_or_code]
179
+ @is_error = 1
180
+
181
+ raise "invalid error code #{error_string_or_code}" if @number.nil?
182
+ end
183
+
184
+ def to_biff
185
+ BoolErrRecord.new(@parent.index, @index, @format_index, @number, @is_error)
186
+ end
187
+ end
@@ -0,0 +1,16 @@
1
+ class Chart
2
+ def initialize
3
+ raise "not implemented"
4
+ end
5
+
6
+
7
+ # ● OBJ Object description for the chart
8
+ # ● BOF Type = chart (➜5.8)
9
+ # Chart records
10
+ # ● EOF End of the Chart Substream of the chart object (5.37)
11
+ def to_biff
12
+ result = []
13
+ result << Biff8BOFRecord.new(Biff8BOFRecord::CHART).to_biff
14
+ result.join
15
+ end
16
+ end
@@ -0,0 +1,40 @@
1
+ class Column
2
+ attr_accessor :index
3
+ attr_accessor :width
4
+ attr_accessor :hidden
5
+ attr_accessor :level
6
+ attr_accessor :collapse
7
+
8
+ def initialize(index, parent)
9
+ is_int = index.is_a?(Integer)
10
+ in_range = (index >= 0) && (index <= 255)
11
+ raise "column index #{index} is not valid" unless is_int && in_range
12
+
13
+ @index = index
14
+ @parent = parent
15
+ @parent_wb = parent.parent
16
+ @xf_index = 0x0F
17
+
18
+ @width = 0x0B92
19
+ @hidden = 0
20
+ @level = 0
21
+ @collapse = 0
22
+ end
23
+
24
+ def to_biff
25
+ options = (as_numeric(@hidden) & 0x01) << 0
26
+ options |= (@level & 0x07) << 8
27
+ options |= (as_numeric(@collapse) & 0x01) << 12
28
+
29
+ ColInfoRecord.new(@index, @index, @width, @xf_index, options).to_biff
30
+ end
31
+
32
+ def set_style(style)
33
+ @xf_index = @parent_wb.add_style(style)
34
+ end
35
+
36
+ def width_in_pixels
37
+ # *** Approximation ****
38
+ (self.width * 0.0272 + 0.446).round
39
+ end
40
+ end