slayer-surpass 0.1.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.
@@ -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