fast_excel 0.2.1 → 0.3.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.
Files changed (106) hide show
  1. checksums.yaml +5 -5
  2. data/.dockerignore +2 -0
  3. data/.gitignore +7 -0
  4. data/.travis.yml +44 -0
  5. data/CHANGELOG.md +41 -1
  6. data/Dockerfile.test +16 -0
  7. data/Gemfile +5 -2
  8. data/Gemfile.lock +30 -23
  9. data/LICENSE +21 -0
  10. data/Makefile +13 -0
  11. data/README.md +177 -40
  12. data/Rakefile +16 -0
  13. data/appveyor.yml +25 -0
  14. data/benchmarks/1k_rows.rb +17 -4
  15. data/benchmarks/20k_rows.rb +4 -0
  16. data/benchmarks/auto_width.rb +37 -0
  17. data/benchmarks/init.rb +14 -2
  18. data/benchmarks/memory.rb +8 -0
  19. data/benchmarks/profiler.rb +27 -0
  20. data/benchmarks/write_value.rb +62 -0
  21. data/examples/example.rb +3 -4
  22. data/examples/example_align.rb +23 -0
  23. data/examples/example_auto_width.rb +26 -0
  24. data/examples/example_colors.rb +37 -0
  25. data/examples/example_filters.rb +36 -0
  26. data/examples/example_formula.rb +1 -5
  27. data/examples/example_hyperlink.rb +20 -0
  28. data/examples/example_image.rb +1 -1
  29. data/examples/example_styles.rb +27 -0
  30. data/examples/logo.png +0 -0
  31. data/ext/fast_excel/extconf.rb +3 -0
  32. data/ext/fast_excel/text_width_ext.c +460 -0
  33. data/fast_excel.gemspec +2 -3
  34. data/letters.html +114 -0
  35. data/lib/fast_excel.rb +455 -78
  36. data/lib/fast_excel/binding.rb +31 -21
  37. data/lib/fast_excel/binding/chart.rb +20 -1
  38. data/lib/fast_excel/binding/format.rb +11 -4
  39. data/lib/fast_excel/binding/workbook.rb +10 -2
  40. data/lib/fast_excel/binding/worksheet.rb +44 -27
  41. data/libxlsxwriter/.gitignore +1 -0
  42. data/libxlsxwriter/.indent.pro +8 -0
  43. data/libxlsxwriter/.travis.yml +12 -0
  44. data/libxlsxwriter/CMakeLists.txt +338 -0
  45. data/libxlsxwriter/CONTRIBUTING.md +1 -1
  46. data/libxlsxwriter/Changes.txt +162 -0
  47. data/libxlsxwriter/LICENSE.txt +65 -4
  48. data/libxlsxwriter/Makefile +33 -11
  49. data/libxlsxwriter/Readme.md +3 -1
  50. data/libxlsxwriter/cocoapods/libxlsxwriter-umbrella.h +2 -1
  51. data/libxlsxwriter/cocoapods/libxlsxwriter.modulemap +2 -2
  52. data/libxlsxwriter/include/xlsxwriter.h +2 -2
  53. data/libxlsxwriter/include/xlsxwriter/app.h +2 -2
  54. data/libxlsxwriter/include/xlsxwriter/chart.h +164 -13
  55. data/libxlsxwriter/include/xlsxwriter/chartsheet.h +544 -0
  56. data/libxlsxwriter/include/xlsxwriter/common.h +35 -6
  57. data/libxlsxwriter/include/xlsxwriter/content_types.h +5 -2
  58. data/libxlsxwriter/include/xlsxwriter/core.h +2 -2
  59. data/libxlsxwriter/include/xlsxwriter/custom.h +2 -2
  60. data/libxlsxwriter/include/xlsxwriter/drawing.h +3 -2
  61. data/libxlsxwriter/include/xlsxwriter/format.h +8 -8
  62. data/libxlsxwriter/include/xlsxwriter/hash_table.h +1 -1
  63. data/libxlsxwriter/include/xlsxwriter/packager.h +18 -8
  64. data/libxlsxwriter/include/xlsxwriter/relationships.h +2 -2
  65. data/libxlsxwriter/include/xlsxwriter/shared_strings.h +5 -3
  66. data/libxlsxwriter/include/xlsxwriter/styles.h +10 -5
  67. data/libxlsxwriter/include/xlsxwriter/theme.h +2 -2
  68. data/libxlsxwriter/include/xlsxwriter/utility.h +35 -5
  69. data/libxlsxwriter/include/xlsxwriter/workbook.h +234 -57
  70. data/libxlsxwriter/include/xlsxwriter/worksheet.h +780 -91
  71. data/libxlsxwriter/include/xlsxwriter/xmlwriter.h +4 -2
  72. data/libxlsxwriter/libxlsxwriter.podspec +4 -2
  73. data/libxlsxwriter/src/Makefile +31 -6
  74. data/libxlsxwriter/src/app.c +2 -2
  75. data/libxlsxwriter/src/chart.c +116 -23
  76. data/libxlsxwriter/src/chartsheet.c +508 -0
  77. data/libxlsxwriter/src/content_types.c +12 -4
  78. data/libxlsxwriter/src/core.c +11 -11
  79. data/libxlsxwriter/src/custom.c +3 -3
  80. data/libxlsxwriter/src/drawing.c +114 -17
  81. data/libxlsxwriter/src/format.c +5 -5
  82. data/libxlsxwriter/src/hash_table.c +1 -1
  83. data/libxlsxwriter/src/packager.c +378 -61
  84. data/libxlsxwriter/src/relationships.c +2 -2
  85. data/libxlsxwriter/src/shared_strings.c +18 -4
  86. data/libxlsxwriter/src/styles.c +59 -12
  87. data/libxlsxwriter/src/theme.c +2 -2
  88. data/libxlsxwriter/src/utility.c +93 -6
  89. data/libxlsxwriter/src/workbook.c +379 -61
  90. data/libxlsxwriter/src/worksheet.c +1240 -174
  91. data/libxlsxwriter/src/xmlwriter.c +18 -9
  92. data/libxlsxwriter/third_party/minizip/Makefile +6 -1
  93. data/libxlsxwriter/third_party/minizip/ioapi.c +10 -0
  94. data/libxlsxwriter/third_party/minizip/zip.c +2 -0
  95. data/libxlsxwriter/third_party/tmpfileplus/tmpfileplus.c +2 -2
  96. data/libxlsxwriter/version.txt +1 -1
  97. data/test/auto_width_test.rb +19 -0
  98. data/test/date_test.rb +34 -0
  99. data/test/format_test.rb +179 -0
  100. data/test/reopen_test.rb +22 -0
  101. data/test/test_helper.rb +23 -4
  102. data/test/text_width_test.rb +80 -0
  103. data/test/tmpfile_test.rb +1 -0
  104. data/test/validations_test.rb +47 -0
  105. data/test/worksheet_test.rb +129 -0
  106. metadata +34 -5
@@ -1,13 +1,12 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "fast_excel"
3
- s.version = "0.2.1"
3
+ s.version = "0.3.0"
4
4
  s.author = ["Pavel Evstigneev"]
5
5
  s.email = ["pavel.evst@gmail.com"]
6
6
  s.homepage = "https://github.com/paxa/fast_excel"
7
- s.summary = %q{Ultra Fast Excel Writter}
7
+ s.summary = %q{Ultra Fast Excel Writer}
8
8
  s.description = "Wrapper for libxlsxwriter using ffi"
9
9
  s.license = 'MIT'
10
- s.has_rdoc = false
11
10
  s.required_ruby_version = '~> 2.0'
12
11
 
13
12
  s.files = `git ls-files`.split("\n")
@@ -0,0 +1,114 @@
1
+ <style>
2
+ html, body {
3
+ font-family: Arial;
4
+ }
5
+ #result {
6
+ font-size: 14px;
7
+ font-family: Arial;
8
+ white-space: pre;
9
+ }
10
+ </style>
11
+
12
+ <select onchange="generateFor(this.value)">
13
+ <option></option>
14
+ <option>Arial</option>
15
+ <option>Calibri</option>
16
+ <option>Times New Roman</option>
17
+ </select>
18
+
19
+ <pre id="result">
20
+
21
+ </pre>
22
+
23
+ <script>
24
+ function ready(fn){var d=document;(d.readyState=='loading')?d.addEventListener('DOMContentLoaded',fn):fn();}
25
+
26
+ let average = (array) => array.reduce((a, b) => a + b) / array.length;
27
+
28
+ window.widths = {};
29
+ window.combos = {};
30
+ window.characters = [];
31
+
32
+ for (var n = 32; n <= 126; n++) {
33
+ characters.push(String.fromCharCode(n));
34
+ }
35
+
36
+ function generateFor(fontFamily) {
37
+ window.widths = {};
38
+ window.combos = {};
39
+
40
+ var startTime = (new Date()).getTime();
41
+
42
+ var canvas = document.createElement('canvas').getContext('2d');
43
+ canvas.font = `normal 100px ${fontFamily}`;
44
+ canvas.textAlign = 'start';
45
+ canvas.textBaseline = 'alphabetic';
46
+
47
+ window.textWidth = function getWidth(value) {
48
+ return canvas.measureText(value).width;
49
+ }
50
+
51
+ characters.forEach((char) => {
52
+ widths[char] = textWidth(char);
53
+ });
54
+
55
+ characters.forEach((char1) => {
56
+ characters.forEach((char2) => {
57
+ var combined = textWidth(char1 + char2);
58
+ var expected = widths[char1] + widths[char2];
59
+
60
+ if (Math.abs(combined - expected) > 0.001) {
61
+ combos[ char1 + char2 ] = [combined, expected];
62
+ //console.log('delta', char1, char2, Math.abs(el.clientWidth - expected));
63
+ }
64
+ });
65
+ });
66
+
67
+ printOutput(startTime, fontFamily);
68
+ }
69
+
70
+ function printOutput(startTime, fontFamily) {
71
+ //document.querySelector('#result').innerText = JSON.stringify(widths, null, 4) + "\n" + JSON.stringify(combos, null, 4);
72
+ var fontPrefix = fontFamily.replace(/\s+/g, '_').toUpperCase();
73
+ var output = "";
74
+
75
+ var averageSize = average(Object.values(widths));
76
+
77
+ output += `static float ${fontPrefix}_DEFAULT = ${averageSize};\n`;
78
+ output += `static float ${fontPrefix}_CHAR_WIDTH[127] = {0`;
79
+ for (var n = 1; n <= 126; n++) {
80
+ output += `, ${widths[String.fromCharCode(n)] || '0' }`;
81
+ }
82
+ output += "};\n";
83
+
84
+ output += `static float ${fontPrefix}_KERNING[127][127] = {\n`;
85
+ for (var n1 = 0; n1 <= 126; n1++) {
86
+ var char1 = String.fromCharCode(n1);
87
+ var charDisplay = characters.includes(char1) ? char1 : '';
88
+
89
+ var prefix = `/* ${n1} : ${charDisplay} */`;
90
+ if (Object.keys(combos).some((pair) => { return pair[0] == char1 })) {
91
+ output += ` ${prefix} { 0`;
92
+ for (var n2 = 1; n2 <= 126; n2++) {
93
+ var char2 = String.fromCharCode(n2);
94
+ output += `, ${combos[char1 + char2] ? combos[char1 + char2][0] : '0' }`;
95
+ }
96
+ output += ` }${n1 == 126 ? '' : ','}\n`;
97
+ } else {
98
+ output += ` ${prefix} { 0 },\n`;
99
+ }
100
+ }
101
+ output += "};\n";
102
+
103
+ output += "\nKerning:\n";
104
+ Object.keys(combos).forEach((pair) => {
105
+ output += `${pair} = ${combos[pair][0]} (not ${combos[pair][1]})\n`
106
+ });
107
+
108
+ document.querySelector('#result').innerText = output;
109
+
110
+ var endTime = (new Date()).getTime();
111
+ //alert(`done ${endTime - startTime} ms`);
112
+ console.log(`done ${endTime - startTime} ms, ${Object.keys(combos).length} combos`);
113
+ }
114
+ </script>
@@ -1,4 +1,8 @@
1
1
  require_relative './fast_excel/binding'
2
+ require 'set'
3
+
4
+ # not used for now
5
+ #require_relative '../ext/fast_excel/text_width_ext'
2
6
 
3
7
  module FastExcel
4
8
 
@@ -9,20 +13,27 @@ module FastExcel
9
13
  end
10
14
  end
11
15
 
16
+ class URL
17
+ attr_accessor :url
18
+ def initialize(url)
19
+ @url = url
20
+ end
21
+ end
22
+
12
23
  DEF_COL_WIDTH = 8.43
13
24
 
14
25
  def self.open(filename = nil, constant_memory: false, default_format: nil)
15
26
  tmp_file = false
16
- unless filename
27
+ if filename
28
+ if File.exist?(filename) && File.size(filename) > 0
29
+ raise ArgumentError, "File '#{filename}' already exists. FastExcel can not open existing files, only create new files"
30
+ end
31
+ else
17
32
  require 'tmpdir'
18
33
  filename = "#{Dir.mktmpdir}/fast_excel.xlsx"
19
34
  tmp_file = true
20
35
  end
21
36
 
22
- unless filename
23
- raise ArgumentError, "filename is required"
24
- end
25
-
26
37
  filename = filename.to_s if defined?(Pathname) && filename.is_a?(Pathname)
27
38
 
28
39
  workbook = if constant_memory
@@ -110,15 +121,197 @@ module FastExcel
110
121
  nil
111
122
  end
112
123
 
124
+
125
+ COLOR_ENUM = Libxlsxwriter.enum_type(:defined_colors)
126
+ EXTRA_COLORS = {
127
+ alice_blue: 0xF0F8FF,
128
+ antique_white: 0xFAEBD7,
129
+ aqua: 0x00FFFF,
130
+ aquamarine: 0x7FFFD4,
131
+ azure: 0xF0FFFF,
132
+ beige: 0xF5F5DC,
133
+ bisque: 0xFFE4C4,
134
+ black: 0x000000,
135
+ blanched_almond: 0xFFEBCD,
136
+ blue: 0x0000FF,
137
+ blue_violet: 0x8A2BE2,
138
+ brown: 0xA52A2A,
139
+ burly_wood: 0xDEB887,
140
+ cadet_blue: 0x5F9EA0,
141
+ chartreuse: 0x7FFF00,
142
+ chocolate: 0xD2691E,
143
+ coral: 0xFF7F50,
144
+ cornflower_blue: 0x6495ED,
145
+ cornsilk: 0xFFF8DC,
146
+ crimson: 0xDC143C,
147
+ cyan: 0x00FFFF,
148
+ dark_blue: 0x00008B,
149
+ dark_cyan: 0x008B8B,
150
+ dark_golden_rod: 0xB8860B,
151
+ dark_gray: 0xA9A9A9,
152
+ dark_grey: 0xA9A9A9,
153
+ dark_green: 0x006400,
154
+ dark_khaki: 0xBDB76B,
155
+ dark_magenta: 0x8B008B,
156
+ dark_olive_green: 0x556B2F,
157
+ dark_orange: 0xFF8C00,
158
+ dark_orchid: 0x9932CC,
159
+ dark_red: 0x8B0000,
160
+ dark_salmon: 0xE9967A,
161
+ dark_sea_green: 0x8FBC8F,
162
+ dark_slate_blue: 0x483D8B,
163
+ dark_slate_gray: 0x2F4F4F,
164
+ dark_slate_grey: 0x2F4F4F,
165
+ dark_turquoise: 0x00CED1,
166
+ dark_violet: 0x9400D3,
167
+ deep_pink: 0xFF1493,
168
+ deep_sky_blue: 0x00BFFF,
169
+ dim_gray: 0x696969,
170
+ dim_grey: 0x696969,
171
+ dodger_blue: 0x1E90FF,
172
+ fire_brick: 0xB22222,
173
+ floral_white: 0xFFFAF0,
174
+ forest_green: 0x228B22,
175
+ fuchsia: 0xFF00FF,
176
+ gainsboro: 0xDCDCDC,
177
+ ghost_white: 0xF8F8FF,
178
+ gold: 0xFFD700,
179
+ golden_rod: 0xDAA520,
180
+ gray: 0x808080,
181
+ grey: 0x808080,
182
+ green: 0x008000,
183
+ green_yellow: 0xADFF2F,
184
+ honey_dew: 0xF0FFF0,
185
+ hot_pink: 0xFF69B4,
186
+ indian_red: 0xCD5C5C,
187
+ indigo: 0x4B0082,
188
+ ivory: 0xFFFFF0,
189
+ khaki: 0xF0E68C,
190
+ lavender: 0xE6E6FA,
191
+ lavender_blush: 0xFFF0F5,
192
+ lawn_green: 0x7CFC00,
193
+ lemon_chiffon: 0xFFFACD,
194
+ light_blue: 0xADD8E6,
195
+ light_coral: 0xF08080,
196
+ light_cyan: 0xE0FFFF,
197
+ light_golden_rod_yellow: 0xFAFAD2,
198
+ light_gray: 0xD3D3D3,
199
+ light_grey: 0xD3D3D3,
200
+ light_green: 0x90EE90,
201
+ light_pink: 0xFFB6C1,
202
+ light_salmon: 0xFFA07A,
203
+ light_sea_green: 0x20B2AA,
204
+ light_sky_blue: 0x87CEFA,
205
+ light_slate_gray: 0x778899,
206
+ light_slate_grey: 0x778899,
207
+ light_steel_blue: 0xB0C4DE,
208
+ light_yellow: 0xFFFFE0,
209
+ lime: 0x00FF00,
210
+ lime_green: 0x32CD32,
211
+ linen: 0xFAF0E6,
212
+ magenta: 0xFF00FF,
213
+ maroon: 0x800000,
214
+ medium_aqua_marine: 0x66CDAA,
215
+ medium_blue: 0x0000CD,
216
+ medium_orchid: 0xBA55D3,
217
+ medium_purple: 0x9370DB,
218
+ medium_sea_green: 0x3CB371,
219
+ medium_slate_blue: 0x7B68EE,
220
+ medium_spring_green: 0x00FA9A,
221
+ medium_turquoise: 0x48D1CC,
222
+ medium_violet_red: 0xC71585,
223
+ midnight_blue: 0x191970,
224
+ mint_cream: 0xF5FFFA,
225
+ misty_rose: 0xFFE4E1,
226
+ moccasin: 0xFFE4B5,
227
+ navajo_white: 0xFFDEAD,
228
+ navy: 0x000080,
229
+ old_lace: 0xFDF5E6,
230
+ olive: 0x808000,
231
+ olive_drab: 0x6B8E23,
232
+ orange: 0xFFA500,
233
+ orange_red: 0xFF4500,
234
+ orchid: 0xDA70D6,
235
+ pale_golden_rod: 0xEEE8AA,
236
+ pale_green: 0x98FB98,
237
+ pale_turquoise: 0xAFEEEE,
238
+ pale_violet_red: 0xDB7093,
239
+ papaya_whip: 0xFFEFD5,
240
+ peach_puff: 0xFFDAB9,
241
+ peru: 0xCD853F,
242
+ pink: 0xFFC0CB,
243
+ plum: 0xDDA0DD,
244
+ powder_blue: 0xB0E0E6,
245
+ purple: 0x800080,
246
+ rebecca_purple: 0x663399,
247
+ red: 0xFF0000,
248
+ rosy_brown: 0xBC8F8F,
249
+ royal_blue: 0x4169E1,
250
+ saddle_brown: 0x8B4513,
251
+ salmon: 0xFA8072,
252
+ sandy_brown: 0xF4A460,
253
+ sea_green: 0x2E8B57,
254
+ sea_shell: 0xFFF5EE,
255
+ sienna: 0xA0522D,
256
+ silver: 0xC0C0C0,
257
+ sky_blue: 0x87CEEB,
258
+ slate_blue: 0x6A5ACD,
259
+ slate_gray: 0x708090,
260
+ slate_grey: 0x708090,
261
+ snow: 0xFFFAFA,
262
+ spring_green: 0x00FF7F,
263
+ steel_blue: 0x4682B4,
264
+ tan: 0xD2B48C,
265
+ teal: 0x008080,
266
+ thistle: 0xD8BFD8,
267
+ tomato: 0xFF6347,
268
+ turquoise: 0x40E0D0,
269
+ violet: 0xEE82EE,
270
+ wheat: 0xF5DEB3,
271
+ white: 0xFFFFFF,
272
+ white_smoke: 0xF5F5F5,
273
+ yellow: 0xFFFF00,
274
+ yellow_green: 0x9ACD32
275
+ }.freeze
276
+
277
+ # Convert hex string, color name or hex number to color hex number
278
+ def self.color_to_hex(value)
279
+ orig_value = value
280
+ value = value.to_s if value.is_a?(Symbol)
281
+
282
+ if value.is_a?(String)
283
+ if EXTRA_COLORS[value.to_sym]
284
+ return EXTRA_COLORS[value.to_sym]
285
+ elsif COLOR_ENUM.find(value.to_sym)
286
+ return COLOR_ENUM.find(value.to_sym)
287
+ elsif COLOR_ENUM.find("color_#{value.to_sym}")
288
+ return COLOR_ENUM.find("color_#{value.to_sym}")
289
+ elsif value =~ /^#?(0x)?([\da-f]){6}$/i
290
+ value = value.sub('#', '') if value.start_with?('#')
291
+ return value.start_with?('0x') ? value.to_i(16) : "0x#{value}".to_i(16)
292
+ else
293
+ raise ArgumentError, "Unknown color value #{orig_value.inspect}, expected hex string or color name"
294
+ end
295
+ end
296
+
297
+ return value if value.is_a?(Numeric)
298
+
299
+ raise ArgumentError, "Can not use #{value.class} (#{value.inspect}) for color value, expected String or Hex Number"
300
+ end
301
+
113
302
  module AttributeHelper
114
303
  def set(values)
115
304
  values.each do |key, value|
116
305
  if respond_to?("#{key}=")
117
306
  send("#{key}=", value)
307
+ elsif respond_to?("set_#{key}=")
308
+ send("set_#{key}=", value)
118
309
  else
119
310
  self[key] = value
120
311
  end
121
312
  end
313
+
314
+ self
122
315
  end
123
316
 
124
317
  def fields_hash
@@ -141,15 +334,25 @@ module FastExcel
141
334
 
142
335
  def initialize(struct)
143
336
  @is_open = true
337
+ @sheet_names = Set.new
338
+ @sheets = []
144
339
  super(struct)
145
340
  end
146
341
 
342
+ def add_format(options = nil)
343
+ new_format = super()
344
+ new_format.set(options) if options
345
+ new_format
346
+ end
347
+
147
348
  def bold_cell_format
148
349
  bold = add_format
149
350
  bold.set_bold
150
351
  bold
151
352
  end
152
353
 
354
+ alias_method :bold_format, :bold_cell_format
355
+
153
356
  # "#,##0.00"
154
357
  # "[$-409]m/d/yy h:mm AM/PM;@"
155
358
  def number_format(pattern)
@@ -159,11 +362,31 @@ module FastExcel
159
362
  end
160
363
 
161
364
  def add_worksheet(sheetname = nil)
162
- super
365
+ if !sheetname.nil?
366
+ if sheetname.length > Libxlsxwriter::SHEETNAME_MAX
367
+ raise ArgumentError, "Worksheet name '#{sheetname}' exceeds Excel's limit of #{Libxlsxwriter::SHEETNAME_MAX} characters"
368
+ elsif @sheet_names.include?(sheetname)
369
+ raise ArgumentError, "Worksheet name '#{sheetname}' is already in use"
370
+ end
371
+ end
372
+
373
+ sheet = super(sheetname)
374
+ sheet.workbook = self
375
+ @sheets << sheet
376
+ @sheet_names << sheet[:name]
377
+ sheet
378
+ end
379
+
380
+ def get_worksheet_by_name(name)
381
+ sheet = super(name)
382
+ sheet.workbook = self
383
+
384
+ sheet
163
385
  end
164
386
 
165
387
  def close
166
388
  @is_open = false
389
+ @sheets.each(&:close)
167
390
  super
168
391
  end
169
392
 
@@ -171,45 +394,159 @@ module FastExcel
171
394
  close if @is_open
172
395
  File.open(filename, 'rb', &:read)
173
396
  ensure
174
- remove_tmp_file
397
+ remove_tmp_folder
398
+ end
399
+
400
+ def remove_tmp_folder
401
+ FileUtils.remove_entry(File.dirname(filename)) if tmp_file
175
402
  end
176
403
 
177
- def remove_tmp_file
178
- File.delete(filename) if tmp_file
404
+ def constant_memory?
405
+ #FastExcel.print_ffi_obj(self[:options])
406
+ @constant_memory ||= self[:options][:constant_memory] != 0
179
407
  end
180
408
  end
181
409
 
182
410
  module WorksheetExt
411
+ attr_accessor :workbook
412
+
183
413
  include AttributeHelper
184
414
 
415
+ def initialize(struct)
416
+ @is_open = true
417
+ @col_formats = {}
418
+ @last_row_number = -1
419
+ super(struct)
420
+ end
421
+
185
422
  def write_row(row_number, values, formats = nil)
186
423
  values.each_with_index do |value, index|
187
424
  format = if formats
188
425
  formats.is_a?(Array) ? formats[index] : formats
189
426
  end
190
427
 
191
- if value.is_a?(Integer) || value.is_a?(Numeric) || value.is_a?(Float)
192
- write_number(row_number, index, value, format)
193
- elsif defined?(BigDecimal) && value.is_a?(BigDecimal)
194
- write_number(row_number, index, value.to_f, format)
195
- elsif defined?(DateTime) && value.is_a?(DateTime)
196
- write_datetime(row_number, index, FastExcel.lxw_datetime(value), format)
197
- elsif value.is_a?(Time)
198
- write_datetime(row_number, index, FastExcel.lxw_time(value), format)
199
- elsif value.is_a?(Formula)
200
- write_formula(row_number, index, value.fml, format)
201
- else
202
- write_string(row_number, index, value.to_s, format)
428
+ write_value(row_number, index, value, format)
429
+ end
430
+ end
431
+
432
+ def auto_width?
433
+ defined?(@auto_width) && @auto_width
434
+ end
435
+
436
+ def auto_width=(v)
437
+ @auto_width = v
438
+ @column_widths = {}
439
+ end
440
+
441
+ def calculated_column_widths
442
+ @column_widths || {}
443
+ end
444
+
445
+ def write_value(row_number, cell_number, value, format = nil)
446
+
447
+ if workbook.constant_memory? && row_number < @last_row_number
448
+ raise ArgumentError, "Can not write to saved row in constant_memory mode (attempted row: #{row_number}, last saved row: #{last_row_number})"
449
+ end
450
+
451
+ if value.is_a?(Numeric)
452
+ write_number(row_number, cell_number, value, format)
453
+ elsif defined?(Date) && value.is_a?(Date)
454
+ write_datetime(row_number, cell_number, FastExcel.lxw_datetime(value.to_datetime), format)
455
+ elsif value.is_a?(Time)
456
+ write_number(row_number, cell_number, FastExcel.date_num(value), format)
457
+ elsif defined?(DateTime) && value.is_a?(DateTime)
458
+ write_number(row_number, cell_number, FastExcel.date_num(value), format)
459
+ elsif value.is_a?(TrueClass) || value.is_a?(FalseClass)
460
+ write_boolean(row_number, cell_number, value ? 1 : 0, format)
461
+ elsif value.is_a?(FastExcel::Formula)
462
+ write_formula(row_number, cell_number, value.fml, format)
463
+ elsif value.is_a?(FastExcel::URL)
464
+ write_url(row_number, cell_number, value.url, format)
465
+ add_text_width(value.url, format, cell_number) if auto_width?
466
+ else
467
+ write_string(row_number, cell_number, value.to_s, format)
468
+ add_text_width(value, format, cell_number) if auto_width?
469
+ end
470
+
471
+ @last_row_number = row_number > @last_row_number ? row_number : @last_row_number
472
+ end
473
+
474
+ def add_text_width(value, format, cell_number)
475
+ font_size = 0
476
+ if format
477
+ font_size = format.font_size
478
+ end
479
+
480
+ if font_size == 0
481
+ if @col_formats[cell_number] && @col_formats[cell_number].font_size
482
+ font_size = @col_formats[cell_number].font_size
203
483
  end
204
484
  end
485
+
486
+ if font_size == 0
487
+ font_size = workbook.default_format.font_size
488
+ end
489
+
490
+ font_size = 13 if font_size == nil || font_size == 0
491
+
492
+ scale = 0.08
493
+ new_width = (scale * font_size * value.to_s.length )
494
+ @column_widths[cell_number] = if new_width > (@column_widths[cell_number] || 0)
495
+ new_width
496
+ else
497
+ @column_widths[cell_number]
498
+ end
499
+ end
500
+
501
+ def append_row(values, formats = nil)
502
+ @last_row_number += 1
503
+ write_row(last_row_number, values, formats)
504
+ end
505
+
506
+ def <<(values)
507
+ append_row(values)
508
+ end
509
+
510
+ def last_row_number
511
+ @last_row_number
512
+ end
513
+
514
+ def set_column(start_col, end_col, width = nil, format = nil)
515
+ super(start_col, end_col, width || DEF_COL_WIDTH, format)
516
+
517
+ return unless format
518
+ start_col.upto(end_col) do |i|
519
+ @col_formats[i] = format
520
+ end
521
+ end
522
+
523
+ def set_column_width(col, width)
524
+ set_column(col, col, width, @col_formats[col])
525
+ end
526
+
527
+ def set_columns_width(start_col, end_col, width)
528
+ start_col.upto(end_col) do |i|
529
+ set_column_width(i, width)
530
+ end
205
531
  end
206
532
 
533
+ def enable_filters!(start_col: 0, end_col:)
534
+ autofilter(start_col, 0, @last_row_number, end_col)
535
+ end
536
+
537
+ def close
538
+ if auto_width?
539
+ @column_widths.each do |num, width|
540
+ set_column_width(num, width + 0.2)
541
+ end
542
+ end
543
+ end
207
544
  end
208
545
 
209
546
  module FormatExt
210
547
  include AttributeHelper
211
548
 
212
- [:font_size, :underline, :font_script, :align, :rotation, :indent, :pattern, :border].each do |prop|
549
+ [:font_size, :underline, :font_script, :rotation, :indent, :pattern, :border].each do |prop|
213
550
  define_method(prop) do
214
551
  self[prop]
215
552
  end
@@ -237,73 +574,121 @@ module FastExcel
237
574
  end
238
575
  end
239
576
 
240
- def set_font_size(value)
241
- if value < 0
242
- raise ArgumentError, "font size should be >= 0 (use 0 for user default font size)"
577
+ ALIGN_ENUM = Libxlsxwriter.enum_type(:format_alignments)
578
+
579
+ # Can be called as:
580
+ #
581
+ # format.align = :align_center
582
+ # format.align = "align_center"
583
+ # format.align = :center
584
+ # format.align = :align_center
585
+ # format.align = {v: "center", h: "center"}
586
+ #
587
+ # Possible values:
588
+ #
589
+ # :align_none, :align_left, :align_center, :align_right, :align_fill, :align_justify,
590
+ # :align_center_across, :align_distributed, :align_vertical_top, :align_vertical_bottom,
591
+ # :align_vertical_center, :align_vertical_justify, :align_vertical_distributed
592
+ #
593
+ def align=(value)
594
+ value = value.to_sym if value.is_a?(String)
595
+
596
+ if value.is_a?(Symbol)
597
+ if ALIGN_ENUM.find(value)
598
+ set_align(value)
599
+ elsif ALIGN_ENUM.find(prefixed = "align_#{value}".to_sym)
600
+ set_align(prefixed)
601
+ else
602
+ raise ArgumentError, "Can not set align = #{value.inspect}, possible values are: #{ALIGN_ENUM.symbols}"
603
+ end
604
+ elsif value.is_a?(Hash)
605
+ if value[:horizontal]
606
+ self.align = "align_#{value[:horizontal].to_s.sub(/^align_/, '')}".to_sym
607
+ end
608
+ if value[:h]
609
+ self.align = "align_#{value[:h].to_s.sub(/^align_/, '')}".to_sym
610
+ end
611
+ if value[:vertical]
612
+ self.align = "align_vertical_#{value[:vertical].to_s.sub(/^align_vertical_/, '')}".to_sym
613
+ end
614
+ if value[:v]
615
+ self.align = "align_vertical_#{value[:v].to_s.sub(/^align_vertical_/, '')}".to_sym
616
+ end
617
+ possible = [:horizontal, :h, :vertical, :v]
618
+ extras = value.keys - possible
619
+ if extras.size > 0
620
+ raise ArgumentError, "Not allowed keys for align: #{extras.inspect}, possible keys: #{possible.inspect}"
621
+ end
622
+ else
623
+ raise ArgumentError, "value must be a symbol or a hash"
243
624
  end
244
- super(value)
245
625
  end
246
626
 
247
- def font_family
248
- font_name
627
+ def align
628
+ {
629
+ horizontal: ALIGN_ENUM.find(self[:text_h_align]),
630
+ vertical: ALIGN_ENUM.find(self[:text_v_align])
631
+ }
249
632
  end
250
633
 
251
- def font_family=(value)
252
- self.font_name = value
634
+ [:font_color, :bg_color, :fg_color, :bottom_color, :diag_color, :left_color, :right_color, :top_color].each do |prop|
635
+ define_method("#{prop}=") do |value|
636
+ send("set_#{prop}", FastExcel.color_to_hex(value))
637
+ end
638
+ define_method(prop) do
639
+ self[prop]
640
+ end
253
641
  end
254
- end
255
-
256
- module RowExt
257
- include AttributeHelper
258
642
 
259
- def inspect
260
- attr_str = fields_hash.map do |key, val|
261
- "@#{key}=#{val.inspect}"
262
- end
263
- "<Libxlsxwriter::Row #{attr_str.join(" ")}>"
643
+ [:bottom_color, :left_color, :right_color, :top_color].each do |prop|
644
+ alias_method :"border_#{prop}=", :"#{prop}="
645
+ alias_method :"border_#{prop}", :"#{prop}"
264
646
  end
265
- end
266
647
 
267
- module CellExt
268
- include AttributeHelper
648
+ BORDER_ENUM = Libxlsxwriter.enum_type(:format_borders)
269
649
 
270
- def value
271
- if self[:type] == :number_cell
272
- self[:u][:number]
273
- elsif self[:type] == :string_cell
274
- pointer = self[:u][:string]
275
- p pointer
276
- pointer.null? ? nil : pointer.to_ptr.read_string
277
- else
278
- self[:user_data1]
650
+ [:bottom, :diag_border, :left, :right, :top].each do |prop|
651
+ define_method("#{prop}=") do |value|
652
+
653
+ send("set_#{prop}", border_value(value))
654
+ end
655
+ define_method(prop) do
656
+ BORDER_ENUM.find(self[prop])
279
657
  end
280
- end
281
658
 
282
- def user_data1
283
- value
284
- #self[:user_data1] && !self[:user_data1].null? ? self[:user_data1].to_ptr.read_string : nil
659
+ unless prop == :diag_border
660
+ alias_method :"border_#{prop}=", :"#{prop}="
661
+ alias_method :"border_#{prop}", :"#{prop}"
662
+ end
285
663
  end
286
664
 
287
- def user_data2
288
- self[:user_data2] #&& !self[:user_data2].null? ? self[:user_data2].to_ptr.read_string : nil
665
+ def border_value(value)
666
+ # if a number
667
+ return value if value.is_a?(Numeric) && BORDER_ENUM.find(value)
668
+
669
+ orig_value = value
670
+ value = value.to_sym if value.is_a?(String)
671
+
672
+ return BORDER_ENUM.find(value) if BORDER_ENUM.find(value)
673
+ return BORDER_ENUM.find(:"border_#{value}") if BORDER_ENUM.find(:"border_#{value}")
674
+
675
+ short_symbols = BORDER_ENUM.symbols.map {|s| s.to_s.sub(/^border_/, '').to_sym }
676
+ raise ArgumentError, "Unknown value #{orig_value.inspect} for border. Possible values: #{short_symbols}"
289
677
  end
290
678
 
291
- def sst_string
292
- self[:sst_string] #&& !self[:sst_string].null? ? self[:sst_string].to_ptr.read_string : nil
679
+ def set_font_size(value)
680
+ if value < 0
681
+ raise ArgumentError, "font size should be >= 0 (use 0 for user default font size)"
682
+ end
683
+ super(value)
293
684
  end
294
685
 
295
- #def inspect
296
- # attr_str = fields_hash.map do |key, val|
297
- # "@#{key}=#{val.inspect}"
298
- # end
299
- # "<Libxlsxwriter::Row #{attr_str.join(" ")}>"
300
- #end
686
+ def font_family
687
+ font_name
688
+ end
301
689
 
302
- def inspect
303
- attr_str = fields_hash.map do |key, val|
304
- "@#{key}=#{val.inspect}"
305
- end
306
- "<Libxlsxwriter::Row #{attr_str.join(" ")}>"
690
+ def font_family=(value)
691
+ self.font_name = value
307
692
  end
308
693
  end
309
694
  end
@@ -319,11 +704,3 @@ end
319
704
  Libxlsxwriter::Worksheet.instance_eval do
320
705
  include FastExcel::WorksheetExt
321
706
  end
322
-
323
- Libxlsxwriter::Row.instance_eval do
324
- include FastExcel::RowExt
325
- end
326
-
327
- Libxlsxwriter::Cell.instance_eval do
328
- include FastExcel::CellExt
329
- end