rmasalov-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,607 @@
1
+ module Formatting
2
+ COLOURS = {
3
+ 'aqua' => 0x31,
4
+ 'black' => 0x08,
5
+ 'blue' => 0x0C,
6
+ 'blue-grey' => 0x36,
7
+ 'bright-green' => 0xb,
8
+ 'brown' => 0x3c,
9
+ 'coral' => 0x1d,
10
+ 'cornflower-blue' => 0x18,
11
+ 'dark-blue' => 0x12,
12
+ 'dark-green' => 0x3a,
13
+ 'dark-red' => 0x10,
14
+ 'dark-teal' => 0x38,
15
+ 'dark-yellow' => 0x13,
16
+ 'fuchsia' => 0x0E,
17
+ 'gold' => 0x33,
18
+ 'gray' => 0x17,
19
+ 'grey' => 0x17,
20
+ 'green' => 0x11,
21
+ 'grey-25-percent' => 0x16,
22
+ 'grey-40-percent' => 0x37,
23
+ 'grey-50-percent' => 0x17,
24
+ 'grey-80-percent' => 0x3f,
25
+ 'indigo' => 0x3e,
26
+ 'lavender' => 0x2e,
27
+ 'lemon-chiffon' => 0x1a,
28
+ 'light-blue' => 0x30,
29
+ 'light-cornflower-blue' => 0x1f,
30
+ 'light-green' => 0x2a,
31
+ 'light-orange' => 0x34,
32
+ 'light-turquoise' => 0x29,
33
+ 'light-yellow' => 0x2b,
34
+ 'lime' => 0x32,
35
+ 'magenta' => 0x0E,
36
+ 'maroon' => 0x19,
37
+ 'olive-green' => 0x3b,
38
+ 'orange' => 0x35,
39
+ 'orchid' => 0x1c,
40
+ 'pale-blue' => 0x2c,
41
+ 'pink' => 0x21,
42
+ 'plum' => 0x3d,
43
+ 'purple' => 0x14,
44
+ 'red' => 0x0A,
45
+ 'rose' => 0x2d,
46
+ 'royal-blue' => 0x1e,
47
+ 'sea-green' => 0x39,
48
+ 'silver' => 0x16,
49
+ 'sky-blue' => 0x28,
50
+ 'tan' => 0x2f,
51
+ 'teal' => 0x15,
52
+ 'turquoise' => 0xf,
53
+ 'violet' => 0x14,
54
+ 'white' => 0x09,
55
+ 'yellow' => 0x0D
56
+ }
57
+
58
+ COLORS = COLOURS
59
+ end
60
+
61
+ class Font
62
+ ESCAPEMENT_NONE = 0x00
63
+ ESCAPEMENT_SUPERSCRIPT = 0x01
64
+ ESCAPEMENT_SUBSCRIPT = 0x02
65
+
66
+ UNDERLINE_NONE = 0x00
67
+ UNDERLINE_SINGLE = 0x01
68
+ UNDERLINE_SINGLE_ACC = 0x21
69
+ UNDERLINE_DOUBLE = 0x02
70
+ UNDERLINE_DOUBLE_ACC = 0x22
71
+
72
+ FAMILY_NONE = 0x00
73
+ FAMILY_ROMAN = 0x01
74
+ FAMILY_SWISS = 0x02
75
+ FAMILY_MODERN = 0x03
76
+ FAMILY_SCRIPT = 0x04
77
+ FAMILY_DECORATIVE = 0x05
78
+
79
+ CHARSET_ANSI_LATIN = 0x00
80
+ CHARSET_SYS_DEFAULT = 0x01
81
+ CHARSET_SYMBOL = 0x02
82
+ CHARSET_APPLE_ROMAN = 0x4D
83
+ CHARSET_ANSI_JAP_SHIFT_JIS = 0x80
84
+ CHARSET_ANSI_KOR_HANGUL = 0x81
85
+ CHARSET_ANSI_KOR_JOHAB = 0x82
86
+ CHARSET_ANSI_CHINESE_GBK = 0x86
87
+ CHARSET_ANSI_CHINESE_BIG5 = 0x88
88
+ CHARSET_ANSI_GREEK = 0xA1
89
+ CHARSET_ANSI_TURKISH = 0xA2
90
+ CHARSET_ANSI_VIETNAMESE = 0xA3
91
+ CHARSET_ANSI_HEBREW = 0xB1
92
+ CHARSET_ANSI_ARABIC = 0xB2
93
+ CHARSET_ANSI_BALTIC = 0xBA
94
+ CHARSET_ANSI_CYRILLIC = 0xCC
95
+ CHARSET_ANSI_THAI = 0xDE
96
+ CHARSET_ANSI_LATIN_II = 0xEE
97
+ CHARSET_OEM_LATIN_I = 0xFF
98
+
99
+ PLAIN = 0x00
100
+ BOLD = 0x01
101
+ ITALIC = 0x02
102
+ UNDERLINE = 0x04
103
+ STRUCK_OUT = 0x08
104
+ OUTLINE = 0x010
105
+ SHADOW = 0x020
106
+
107
+ attr_accessor :height
108
+ attr_accessor :italic
109
+ attr_accessor :struck_out
110
+ attr_accessor :outline
111
+ attr_accessor :shadow
112
+ attr_accessor :colour_index
113
+ attr_accessor :bold
114
+ attr_accessor :weight # Looks like only 400 = normal, 700 = bold are supported so just use bold = true.
115
+ attr_accessor :escapement
116
+ attr_accessor :charset
117
+ attr_accessor :name
118
+
119
+ attr_reader :family
120
+ attr_reader :underline
121
+
122
+ def initialize(hash = {})
123
+ @height = 200 # font size 10
124
+ @italic = false
125
+ @struck_out = false
126
+ @outline = false
127
+ @shadow = false
128
+ @colour_index = 0x7FFF
129
+ @bold = false
130
+ @weight = 400 # regular
131
+ @escapement = ESCAPEMENT_NONE
132
+ @charset = CHARSET_SYS_DEFAULT
133
+ @name = 'Arial'
134
+ @family = FAMILY_NONE
135
+ @underline = UNDERLINE_NONE
136
+
137
+ hash.each do |k, v|
138
+ self.send((k.to_s + '=').to_sym, v)
139
+ end
140
+ end
141
+
142
+ def family=(arg)
143
+ raise "Oops, font_family doesn't take a string. Do you want font_name instead?" if arg.is_a?(String)
144
+ @family = arg
145
+ end
146
+
147
+ # Convert font size in points to native twips
148
+ def size=(points)
149
+ @height = points * 20
150
+ end
151
+
152
+ def strikethrough=(arg)
153
+ @struck_out = arg
154
+ end
155
+
156
+ def subscript=(arg)
157
+ case arg
158
+ when TrueClass
159
+ @escapement = ESCAPEMENT_SUBSCRIPT
160
+ when FalseClass
161
+ @escapement = ESCAPEMENT_NONE
162
+ else
163
+ raise "I don't know how to set subscript to #{arg.inspect}."
164
+ end
165
+ end
166
+
167
+ def superscript=(arg)
168
+ case arg
169
+ when TrueClass
170
+ @escapement = ESCAPEMENT_SUPERSCRIPT
171
+ when FalseClass
172
+ @escapement = ESCAPEMENT_NONE
173
+ else
174
+ raise "I don't know how to set superscript to #{arg.inspect}."
175
+ end
176
+ end
177
+
178
+ # User-friendly underlining directives.
179
+ def underline=(arg)
180
+ case arg
181
+ when UNDERLINE_NONE, UNDERLINE_SINGLE, UNDERLINE_SINGLE_ACC, UNDERLINE_DOUBLE, UNDERLINE_DOUBLE_ACC
182
+ @underline = arg
183
+ when nil
184
+ @underline ||= UNDERLINE_NONE
185
+ when TrueClass
186
+ @underline = UNDERLINE_SINGLE
187
+ when FalseClass
188
+ @underline = UNDERLINE_NONE
189
+ when :none
190
+ @underline = UNDERLINE_NONE
191
+ when :single
192
+ @underline = UNDERLINE_SINGLE
193
+ when :single_acc, :single_accounting
194
+ @underline = UNDERLINE_SINGLE_ACC
195
+ when :double
196
+ @underline = UNDERLINE_DOUBLE
197
+ when :double_acc, :double_accounting
198
+ @underline = UNDERLINE_DOUBLE_ACC
199
+ else
200
+ raise "I don't know how to set underline to #{arg.inspect}."
201
+ end
202
+ end
203
+
204
+ def colour_index_from_name(colour_name)
205
+ Formatting::COLOURS[colour_name.to_s]
206
+ end
207
+
208
+ def colour=(colour_name)
209
+ new_colour = colour_index_from_name(colour_name)
210
+ if new_colour.nil?
211
+ raise "Invalid Colour #{colour_name}"
212
+ else
213
+ @colour_index = new_colour
214
+ end
215
+ end
216
+ alias :color= :colour=
217
+ alias :color_index= :colour_index=
218
+
219
+ def to_biff
220
+ options = PLAIN
221
+ options |= BOLD if @bold
222
+ options |= ITALIC if @italic
223
+ options |= UNDERLINE if (@underline != UNDERLINE_NONE)
224
+ options |= STRUCK_OUT if @struck_out
225
+ options |= OUTLINE if @outline
226
+ options |= SHADOW if @shadow
227
+
228
+ @weight = 700 if @bold
229
+ args = [@height, options, @colour_index, @weight, @escapement, @underline, @family, @charset, @name]
230
+ FontRecord.new(*args).to_biff
231
+ end
232
+ end
233
+
234
+ class Alignment
235
+ HORZ_GENERAL = 0x00
236
+ HORZ_LEFT = 0x01
237
+ HORZ_CENTER = 0x02
238
+ HORZ_RIGHT = 0x03
239
+ HORZ_FILLED = 0x04
240
+ HORZ_JUSTIFIED = 0x05 # BIFF4-BIFF8X
241
+ HORZ_CENTER_ACROSS_SEL = 0x06 # Centred across selection (BIFF4-BIFF8X)
242
+ HORZ_DISTRIBUTED = 0x07 # Distributed (BIFF8X)
243
+
244
+ VERT_TOP = 0x00
245
+ VERT_CENTER = 0x01
246
+ VERT_BOTTOM = 0x02
247
+ VERT_JUSTIFIED = 0x03 # Justified (BIFF5-BIFF8X)
248
+ VERT_DISTRIBUTED = 0x04 # Distributed (BIFF8X)
249
+
250
+ DIRECTION_GENERAL = 0x00 # BIFF8X
251
+ DIRECTION_LR = 0x01
252
+ DIRECTION_RL = 0x02
253
+
254
+ ORIENTATION_NOT_ROTATED = 0x00
255
+ ORIENTATION_STACKED = 0x01
256
+ ORIENTATION_90_CC = 0x02
257
+ ORIENTATION_90_CW = 0x03
258
+
259
+ ROTATION_0_ANGLE = 0x00
260
+ ROTATION_STACKED = 0xFF
261
+
262
+ WRAP_AT_RIGHT = 0x01
263
+ NOT_WRAP_AT_RIGHT = 0x00
264
+
265
+ SHRINK_TO_FIT = 0x01
266
+ NOT_SHRINK_TO_FIT = 0x00
267
+
268
+ attr_accessor :horz
269
+ attr_accessor :vert
270
+ attr_accessor :dire
271
+ attr_accessor :orie
272
+ attr_accessor :rota
273
+ attr_accessor :shri
274
+ attr_accessor :inde
275
+ attr_accessor :merg
276
+
277
+ attr_reader :wrap
278
+
279
+ def initialize(hash = {})
280
+ # Initialize to defaults.
281
+ @horz = HORZ_GENERAL
282
+ @vert = VERT_BOTTOM
283
+ @wrap = NOT_WRAP_AT_RIGHT
284
+ @dire = DIRECTION_GENERAL
285
+ @orie = ORIENTATION_NOT_ROTATED
286
+ @rota = ROTATION_0_ANGLE
287
+ @shri = NOT_SHRINK_TO_FIT
288
+ @inde = 0
289
+ @merg = 0
290
+
291
+ # Allow defaults to be overridden in hash. Where there is no :align key in hash,
292
+ # this just leaves the default value in place.
293
+ self.align = hash[:align]
294
+ self.wrap = hash[:wrap]
295
+ end
296
+
297
+ # Don't support passing constants here because :horz and :vert are exposed
298
+ # so if someone wants to use nasty HORZ_RIGHT they can do align.vert = HORZ_RIGHT
299
+ def align=(alignment_directives)
300
+ if alignment_directives =~ /\s/
301
+ args = alignment_directives.split
302
+ else
303
+ args = [alignment_directives] # there's just 1 here
304
+ end
305
+
306
+ args.each do |a|
307
+ case a
308
+ when 'right'
309
+ @horz = HORZ_RIGHT
310
+ when 'left'
311
+ @horz = HORZ_LEFT
312
+ when 'center', 'centre'
313
+ @horz = HORZ_CENTER
314
+ when 'general'
315
+ @horz = HORZ_GENERAL
316
+ when 'filled'
317
+ @horz = HORZ_FILLED
318
+ when 'justify'
319
+ @horz = HORZ_JUSTIFIED
320
+ when 'top'
321
+ @vert = VERT_TOP
322
+ when 'middle'
323
+ @vert = VERT_CENTER
324
+ when 'bottom'
325
+ @vert = VERT_BOTTOM
326
+ when nil
327
+ # Do nothing.
328
+ else
329
+ raise "I don't know how to set align to #{a.inspect}."
330
+ end
331
+ end
332
+ end
333
+
334
+ def wrap=(arg)
335
+ case arg
336
+ when TrueClass, WRAP_AT_RIGHT
337
+ @wrap = WRAP_AT_RIGHT
338
+ when FalseClass, NOT_WRAP_AT_RIGHT
339
+ @wrap = NOT_WRAP_AT_RIGHT
340
+ when nil
341
+ # Do nothing.
342
+ else
343
+ raise "I don't know how to set wrap to #{arg.inspect}."
344
+ end
345
+ end
346
+ end
347
+
348
+ class Borders
349
+ attr_reader :left
350
+ attr_reader :right
351
+ attr_reader :top
352
+ attr_reader :bottom
353
+ attr_reader :diag
354
+
355
+ attr_accessor :left_colour
356
+ attr_accessor :right_colour
357
+ attr_accessor :top_colour
358
+ attr_accessor :bottom_colour
359
+ attr_accessor :diag_colour
360
+
361
+ attr_accessor :need_diag1
362
+ attr_accessor :need_diag2
363
+
364
+ NO_LINE = 0x00
365
+ THIN = 0x01
366
+ MEDIUM = 0x02
367
+ DASHED = 0x03
368
+ DOTTED = 0x04
369
+ THICK = 0x05
370
+ DOUBLE = 0x06
371
+ HAIR = 0x07
372
+ #The following for BIFF8
373
+ MEDIUM_DASHED = 0x08
374
+ THIN_DASH_DOTTED = 0x09
375
+ MEDIUM_DASH_DOTTED = 0x0A
376
+ THIN_DASH_DOT_DOTTED = 0x0B
377
+ MEDIUM_DASH_DOT_DOTTED = 0x0C
378
+ SLANTED_MEDIUM_DASH_DOTTED = 0x0D
379
+
380
+ NEED_DIAG1 = 0x01
381
+ NEED_DIAG2 = 0x01
382
+ NO_NEED_DIAG1 = 0x00
383
+ NO_NEED_DIAG2 = 0x00
384
+
385
+ # Want to keep these sorted in this order, so need nested array instead of hash.
386
+ LINE_TYPE_DIRECTIVES = [
387
+ ['none', NO_LINE],
388
+ ['thin', THIN],
389
+ ['medium', MEDIUM],
390
+ ['dashed', DASHED],
391
+ ['dotted', DOTTED],
392
+ ['thick', THICK],
393
+ ['double', DOUBLE],
394
+ ['hair', HAIR],
395
+ ['medium-dashed', MEDIUM_DASHED],
396
+ ['thin-dash-dotted', THIN_DASH_DOTTED],
397
+ ['medium-dash-dotted', MEDIUM_DASH_DOTTED],
398
+ ['thin-dash-dot-dotted', THIN_DASH_DOT_DOTTED],
399
+ ['medium-dash-dot-dotted', MEDIUM_DASH_DOT_DOTTED],
400
+ ['slanted-medium-dash-dotted', SLANTED_MEDIUM_DASH_DOTTED]
401
+ ]
402
+
403
+ def self.line_type_directives
404
+ LINE_TYPE_DIRECTIVES.collect {|k, v| k}
405
+ end
406
+
407
+ def self.line_type_constants
408
+ LINE_TYPE_DIRECTIVES.collect {|k, v| v}
409
+ end
410
+
411
+ def self.line_type_directives_hash
412
+ Hash[*LINE_TYPE_DIRECTIVES.flatten]
413
+ end
414
+
415
+ def initialize(hash = {})
416
+ @left = NO_LINE
417
+ @right = NO_LINE
418
+ @top = NO_LINE
419
+ @bottom = NO_LINE
420
+ @diag = NO_LINE
421
+
422
+ @left_colour = 0x40
423
+ @right_colour = 0x40
424
+ @top_colour = 0x40
425
+ @bottom_colour = 0x40
426
+ @diag_colour = 0x40
427
+
428
+ @need_diag1 = NO_NEED_DIAG1
429
+ @need_diag2 = NO_NEED_DIAG2
430
+
431
+ hash.each do |k, v|
432
+ self.send((k.to_s + '=').to_sym, v)
433
+ end
434
+ end
435
+
436
+ def all=(directives)
437
+ self.left = directives
438
+ self.right = directives
439
+ self.top = directives
440
+ self.bottom = directives
441
+ end
442
+
443
+ def process_directives(directives)
444
+ if directives =~ /\s/
445
+ args = directives.split
446
+ else
447
+ args = [directives] # there's just 1 here, stick it in an array
448
+ end
449
+
450
+ raise "no directives given to process_directives" if args.empty? # maybe don't need this, just get thin black border? but for development I want to know if this happens.
451
+ raise "too many directives given to process_directives" if args.size > 2
452
+
453
+ instructions = [THIN, Formatting::COLOURS['black']]
454
+ args.each do |a|
455
+ if Formatting::COLOURS.include?(a)
456
+ instructions[1] = Formatting::COLOURS[a]
457
+ next
458
+ end
459
+
460
+ if Borders.line_type_directives.include?(a)
461
+ instructions[0] = Borders.line_type_directives_hash[a]
462
+ next
463
+ end
464
+
465
+ if Borders.line_type_constants.include?(a)
466
+ instructions[0] = a
467
+ next
468
+ end
469
+
470
+ raise "I don't know how to format a border with #{a.inspect}."
471
+ end
472
+
473
+ instructions
474
+ end
475
+
476
+ def right=(directives)
477
+ @right, @right_colour = process_directives(directives)
478
+ end
479
+
480
+ def left=(directives)
481
+ @left, @left_colour = process_directives(directives)
482
+ end
483
+
484
+ def top=(directives)
485
+ @top, @top_colour = process_directives(directives)
486
+ end
487
+
488
+ def bottom=(directives)
489
+ @bottom, @bottom_colour = process_directives(directives)
490
+ end
491
+ end
492
+
493
+
494
+ class Pattern
495
+ NO_PATTERN = 0x00
496
+ SOLID_FOREGROUND = 0x01
497
+ SOLID_PATTERN = SOLID_FOREGROUND # for backwards compatibility
498
+ FINE_DOTS = 0x02
499
+ ALT_BARS = 0x03
500
+ SPARSE_DOTS = 0x04
501
+ THICK_HORZ_BANDS = 0x05
502
+ THICK_VERT_BANDS = 0x06
503
+ THICK_BACKWARD_DIAG = 0x07
504
+ THICK_FORWARD_DIAG = 0x08
505
+ BIG_SPOTS = 0x09
506
+ BRICKS = 0x0A
507
+ THIN_HORZ_BANDS = 0x0B
508
+ THIN_VERT_BANDS = 0x0C
509
+ THIN_BACKWARD_DIAG = 0x0D
510
+ THIN_FORWARD_DIAG = 0x0E
511
+ SQUARES = 0x0F
512
+ DIAMONDS = 0x10
513
+ LESS_DOTS = 0x11
514
+ LEAST_DOTS = 0x12
515
+
516
+ # Want to keep these sorted in this order, so need nested array instead of hash.
517
+ PATTERN_DIRECTIVES = [
518
+ ['none', NO_PATTERN],
519
+ ['solid', SOLID_FOREGROUND],
520
+ ['fine-dots', FINE_DOTS],
521
+ ['alt-bars', ALT_BARS],
522
+ ['sparse-dots', SPARSE_DOTS],
523
+ ['thick-horz-bands', THICK_HORZ_BANDS],
524
+ ['thick-vert-bands', THICK_VERT_BANDS],
525
+ ['thick-backward-diag', THICK_BACKWARD_DIAG],
526
+ ['thick-forward-diag', THICK_FORWARD_DIAG],
527
+ ['big-spots', BIG_SPOTS],
528
+ ['bricks', BRICKS],
529
+ ['thin-horz-bands', THIN_HORZ_BANDS],
530
+ ['thin-vert-bands', THIN_VERT_BANDS],
531
+ ['thin-backward-diag', THIN_BACKWARD_DIAG],
532
+ ['thin-forward-diag', THIN_FORWARD_DIAG],
533
+ ['squares', SQUARES],
534
+ ['diamonds', DIAMONDS],
535
+ ['less-dots', LESS_DOTS],
536
+ ['least-dots', LEAST_DOTS]
537
+ ]
538
+
539
+ attr_reader :pattern
540
+ attr_reader :pattern_fore_colour
541
+ attr_reader :pattern_back_colour
542
+
543
+ def self.fill_directives
544
+ PATTERN_DIRECTIVES.collect {|a| a[0]}
545
+ end
546
+
547
+ def self.directives_hash
548
+ Hash[*PATTERN_DIRECTIVES.flatten]
549
+ end
550
+
551
+ def initialize(hash = {})
552
+ @pattern = NO_PATTERN
553
+ @pattern_fore_colour = 0x40
554
+ @pattern_back_colour = 0x41
555
+
556
+ hash.each do |k, v|
557
+ self.send((k.to_s + '=').to_sym, v)
558
+ end
559
+ end
560
+
561
+ def pattern=(arg)
562
+ case arg
563
+ when String
564
+ pattern_index = Pattern.directives_hash[arg]
565
+ when Integer
566
+ pattern_index = arg
567
+ else
568
+ raise "I don't know how to interpret #{arg.inspect} as a pattern!"
569
+ end
570
+ raise "invalid pattern #{arg}" if pattern_index.nil?
571
+
572
+ @pattern = pattern_index
573
+ end
574
+
575
+ def fore_colour=(arg)
576
+ colour_index = Formatting::COLOURS[arg]
577
+ raise "Invalid colour #{arg}" if colour_index.nil?
578
+ @pattern_fore_colour = colour_index
579
+ end
580
+ alias :fore_color= :fore_colour=
581
+
582
+ def back_colour=(arg)
583
+ colour_index = Formatting::COLOURS[arg]
584
+ raise "Invalid colour #{arg}" if colour_index.nil?
585
+ @pattern_back_colour = colour_index
586
+ end
587
+ alias :back_color= :back_colour=
588
+
589
+ # Sets the foreground colour, also if no pattern has been specified
590
+ # will assume you want a solid colour fill.
591
+ def colour=(arg)
592
+ self.fore_colour = arg
593
+ @pattern = SOLID_PATTERN if @pattern == NO_PATTERN
594
+ end
595
+ alias :color= :colour=
596
+ alias :fill= :colour=
597
+ end
598
+
599
+ class Protection
600
+ attr_accessor :cell_locked
601
+ attr_accessor :formula_hidden
602
+
603
+ def initialize
604
+ @cell_locked = 1
605
+ @formula_hidden = 0
606
+ end
607
+ end