axlsx 1.0.13 → 1.0.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. data/README.md +161 -134
  2. data/examples/example.rb +133 -153
  3. data/lib/axlsx.rb +3 -1
  4. data/lib/axlsx/#cfb.xlsx# +0 -0
  5. data/lib/axlsx/drawing/axis.rb +13 -10
  6. data/lib/axlsx/drawing/bar_3D_chart.rb +19 -11
  7. data/lib/axlsx/drawing/bar_series.rb +1 -1
  8. data/lib/axlsx/drawing/cat_axis.rb +5 -5
  9. data/lib/axlsx/drawing/cat_axis_data.rb +7 -7
  10. data/lib/axlsx/drawing/chart.rb +29 -15
  11. data/lib/axlsx/drawing/drawing.rb +1 -1
  12. data/lib/axlsx/drawing/graphic_frame.rb +10 -10
  13. data/lib/axlsx/drawing/line_3D_chart.rb +15 -7
  14. data/lib/axlsx/drawing/marker.rb +1 -1
  15. data/lib/axlsx/drawing/one_cell_anchor.rb +4 -4
  16. data/lib/axlsx/drawing/pic.rb +14 -14
  17. data/lib/axlsx/drawing/pie_3D_chart.rb +2 -2
  18. data/lib/axlsx/drawing/pie_series.rb +1 -1
  19. data/lib/axlsx/drawing/scaling.rb +5 -5
  20. data/lib/axlsx/drawing/ser_axis.rb +3 -3
  21. data/lib/axlsx/drawing/series.rb +3 -3
  22. data/lib/axlsx/drawing/series_title.rb +7 -7
  23. data/lib/axlsx/drawing/title.rb +13 -9
  24. data/lib/axlsx/drawing/two_cell_anchor.rb +4 -4
  25. data/lib/axlsx/drawing/val_axis.rb +2 -2
  26. data/lib/axlsx/drawing/val_axis_data.rb +7 -9
  27. data/lib/axlsx/drawing/view_3D.rb +7 -7
  28. data/lib/axlsx/package.rb +8 -0
  29. data/lib/axlsx/util/constants.rb +0 -4
  30. data/lib/axlsx/util/ms_off_crypto.rb +88 -0
  31. data/lib/axlsx/util/ms_off_crypto.rb~ +3 -0
  32. data/lib/axlsx/util/ms_offcrypto.rb~ +0 -0
  33. data/lib/axlsx/version.rb +1 -1
  34. data/lib/axlsx/workbook/workbook.rb +1 -0
  35. data/lib/axlsx/workbook/worksheet/cell.rb +21 -8
  36. data/lib/axlsx/workbook/worksheet/worksheet.rb +74 -18
  37. data/test/content_type/tc_content_type.rb +81 -0
  38. data/test/content_type/tc_default.rb +40 -0
  39. data/test/content_type/tc_override.rb +40 -0
  40. data/test/doc_props/tc_app.rb +19 -0
  41. data/test/doc_props/tc_core.rb +34 -0
  42. data/test/drawing/tc_axis.rb +40 -0
  43. data/test/drawing/tc_bar_3D_chart.rb +66 -0
  44. data/test/drawing/tc_bar_series.rb +34 -0
  45. data/test/drawing/tc_cat_axis.rb +32 -0
  46. data/test/drawing/tc_cat_axis_data.rb +18 -0
  47. data/test/drawing/tc_chart.rb +73 -0
  48. data/test/drawing/tc_drawing.rb +80 -0
  49. data/test/drawing/tc_graphic_frame.rb +26 -0
  50. data/test/drawing/tc_line_3d_chart.rb +48 -0
  51. data/test/drawing/tc_line_series.rb +27 -0
  52. data/test/drawing/tc_line_series.tc~ +34 -0
  53. data/test/drawing/tc_marker.rb +45 -0
  54. data/test/drawing/tc_one_cell_anchor.rb +67 -0
  55. data/test/drawing/tc_pic.rb +71 -0
  56. data/test/drawing/tc_picture_locking.rb +73 -0
  57. data/test/drawing/tc_picture_locking.rb~ +77 -0
  58. data/test/drawing/tc_pie_3D_chart.rb +33 -0
  59. data/test/drawing/tc_pie_series.rb +35 -0
  60. data/test/drawing/tc_scaling.rb +37 -0
  61. data/test/drawing/tc_ser_axis.rb +31 -0
  62. data/test/drawing/tc_series.rb +24 -0
  63. data/test/drawing/tc_series_title.rb +34 -0
  64. data/test/drawing/tc_title.rb +34 -0
  65. data/test/drawing/tc_two_cell_anchor.rb +38 -0
  66. data/test/drawing/tc_val_axis.rb +25 -0
  67. data/test/drawing/tc_val_axis_data.rb +18 -0
  68. data/test/drawing/tc_view_3D.rb +55 -0
  69. data/test/rels/tc_relationship.rb +16 -0
  70. data/test/rels/tc_relationships.rb +27 -0
  71. data/test/stylesheet/tc_border.rb +38 -0
  72. data/test/stylesheet/tc_border_pr.rb +33 -0
  73. data/test/stylesheet/tc_cell_alignment.rb +77 -0
  74. data/test/stylesheet/tc_cell_protection.rb +30 -0
  75. data/test/stylesheet/tc_cell_style.rb +58 -0
  76. data/test/stylesheet/tc_color.rb +38 -0
  77. data/test/stylesheet/tc_fill.rb +19 -0
  78. data/test/stylesheet/tc_font.rb +114 -0
  79. data/test/stylesheet/tc_gradient_fill.rb +65 -0
  80. data/test/stylesheet/tc_gradient_stop.rb +32 -0
  81. data/test/stylesheet/tc_num_fmt.rb +31 -0
  82. data/test/stylesheet/tc_pattern_fill.rb +38 -0
  83. data/test/stylesheet/tc_styles.rb +52 -0
  84. data/test/stylesheet/tc_table_style.rb +37 -0
  85. data/test/stylesheet/tc_table_style_element.rb +37 -0
  86. data/test/stylesheet/tc_table_styles.rb +30 -0
  87. data/test/stylesheet/tc_xf.rb +121 -0
  88. data/test/tc_package.rb +78 -0
  89. data/test/util/tc_simple_typed_list.rb +66 -0
  90. data/test/util/tc_validators.rb +76 -0
  91. data/test/workbook/tc_workbook.rb +60 -0
  92. data/test/workbook/worksheet/tc_cell.rb +194 -0
  93. data/test/workbook/worksheet/tc_row.rb +36 -0
  94. data/test/workbook/worksheet/tc_worksheet.rb +159 -0
  95. metadata +191 -31
  96. data/lib/axlsx/workbook/#workbook.rb# +0 -165
@@ -6,13 +6,13 @@ module Axlsx
6
6
  # @param [Nokogiri::XML::Builder] xml The document builder instance this objects xml will be added to.
7
7
  # @return [String]
8
8
  def to_xml(xml)
9
- xml.send('c:tx') {
10
- xml.send('c:strRef') {
11
- xml.send('c:f', Axlsx::cell_range([@cell]))
12
- xml.send('c:strCache') {
13
- xml.send('c:ptCount', :val=>1)
14
- xml.send('c:pt', :idx=>0) {
15
- xml.send('c:v', @text)
9
+ xml[:c].tx {
10
+ xml[:c].strRef {
11
+ xml[:c].f Axlsx::cell_range([@cell])
12
+ xml[:c].strCache {
13
+ xml[:c].ptCount :val=>1
14
+ xml[:c].pt(:idx=>0) {
15
+ xml[:c].v @text
16
16
  }
17
17
  }
18
18
  }
@@ -42,18 +42,22 @@ module Axlsx
42
42
  # @param [Nokogiri::XML::Builder] xml The document builder instance this objects xml will be added to.
43
43
  # @return [String]
44
44
  def to_xml(xml)
45
- xml.send('c:title') {
46
- xml.send('c:tx') {
47
- xml.send('c:strRef') {
48
- xml.send('c:f', Axlsx::cell_range([@cell]))
49
- xml.send('c:strCache') {
50
- xml.send('c:ptCount', :val=>1)
51
- xml.send('c:pt', :idx=>0) {
52
- xml.send('c:v', @text)
45
+ xml[:c].title {
46
+ unless @text.empty?
47
+ xml[:c].tx {
48
+ xml[:c].strRef {
49
+ xml[:c].f Axlsx::cell_range([@cell])
50
+ xml[:c].strCache {
51
+ xml[:c].ptCount :val=>1
52
+ xml[:c].pt(:idx=>0) {
53
+ xml[:c].v @text
54
+ }
53
55
  }
54
56
  }
55
57
  }
56
- }
58
+ end
59
+ xml[:c].layout
60
+ xml[:c].overlay :val=>0
57
61
  }
58
62
  end
59
63
 
@@ -58,15 +58,15 @@ module Axlsx
58
58
  # @return [String]
59
59
  def to_xml(xml)
60
60
  #build it for now, break it down later!
61
- xml.send('xdr:twoCellAnchor') {
62
- xml.send('xdr:from') {
61
+ xml[:xdr].twoCellAnchor {
62
+ xml.from {
63
63
  from.to_xml(xml)
64
64
  }
65
- xml.send('xdr:to') {
65
+ xml.to {
66
66
  to.to_xml(xml)
67
67
  }
68
68
  @object.to_xml(xml)
69
- xml.send('xdr:clientData')
69
+ xml.clientData
70
70
  }
71
71
  end
72
72
 
@@ -25,9 +25,9 @@ module Axlsx
25
25
  # @param [Nokogiri::XML::Builder] xml The document builder instance this objects xml will be added to.
26
26
  # @return [String]
27
27
  def to_xml(xml)
28
- xml.send('c:valAx') {
28
+ xml.valAx {
29
29
  super(xml)
30
- xml.send('c:crossBetween', :val=>@crossBetween)
30
+ xml.crossBetween :val=>@crossBetween
31
31
  }
32
32
  end
33
33
  end
@@ -6,17 +6,15 @@ module Axlsx
6
6
  # @param [Nokogiri::XML::Builder] xml The document builder instance this objects xml will be added to.
7
7
  # @return [String]
8
8
  def to_xml(xml)
9
- xml.send('c:val') {
10
- xml.send('c:numRef') {
11
- xml.send('c:f', Axlsx::cell_range(@list))
12
- xml.send('c:numCache') {
13
- xml.send('c:formatCode', 'General')
14
- xml.send('c:ptCount', :val=>size)
9
+ xml.val {
10
+ xml.numRef {
11
+ xml.f Axlsx::cell_range(@list)
12
+ xml.numCache {
13
+ xml.formatCode 'General'
14
+ xml.ptCount :val=>size
15
15
  each_with_index do |item, index|
16
16
  v = item.is_a?(Cell) ? item.value : item
17
- xml.send('c:pt', :idx=>index) {
18
- xml.send('c:v', v)
19
- }
17
+ xml.pt(:idx=>index) { xml.v v }
20
18
  end
21
19
  }
22
20
  }
@@ -72,13 +72,13 @@ module Axlsx
72
72
  # @param [Nokogiri::XML::Builder] xml The document builder instance this objects xml will be added to.
73
73
  # @return [String]
74
74
  def to_xml(xml)
75
- xml.send('c:view3D') {
76
- xml.send('c:rotX', :val=>@rotX) unless @rotX.nil?
77
- xml.send('c:hPercent', :val=>@hPercent) unless @hPercent.nil?
78
- xml.send('c:rotY', :val=>@rotY) unless @rotY.nil?
79
- xml.send('c:depthPercent', :val=>@depthPercent) unless @depthPercent.nil?
80
- xml.send('c:rAngAx', :val=>@rAngAx) unless @rAngAx.nil?
81
- xml.send('c:perspective', :val=>@perspective) unless @perspective.nil?
75
+ xml[:c].view3D {
76
+ xml[:c].rotX :val=>@rotX unless @rotX.nil?
77
+ xml[:c].hPercent :val=>@hPercent unless @hPercent.nil?
78
+ xml[:c].rotY :val=>@rotY unless @rotY.nil?
79
+ xml[:c].depthPercent :val=>@depthPercent unless @depthPercent.nil?
80
+ xml[:c].rAngAx :val=>@rAngAx unless @rAngAx.nil?
81
+ xml[:c].perspective :val=>@perspective unless @perspective.nil?
82
82
  }
83
83
  end
84
84
  end
@@ -4,6 +4,14 @@ module Axlsx
4
4
  # xlsx document including valdation and serialization.
5
5
  class Package
6
6
 
7
+ # provides access to the app doc properties for this package
8
+ # see App
9
+ attr_reader :app
10
+
11
+ # provides access to the core doc properties for the package
12
+ # see Core
13
+ attr_reader :core
14
+
7
15
  # Initializes your package
8
16
  #
9
17
  # @param [Hash] options A hash that you can use to specify the author and workbook for this package.
@@ -12,10 +12,6 @@ module Axlsx
12
12
  # extended-properties namespace
13
13
  APP_NS = "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
14
14
 
15
- XML_NS_MC="http://schemas.openxmlformats.org/markup-compatibility/2006"
16
-
17
- XML_NS_X14AC = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"
18
-
19
15
  # doc props namespace
20
16
  APP_NS_VT = "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
21
17
 
@@ -0,0 +1,88 @@
1
+ require 'digest'
2
+ require 'base64'
3
+ require 'openssl'
4
+ module Axlsx
5
+ class MsOffCrypto
6
+
7
+ attr_reader :verifier
8
+ attr_reader :key
9
+
10
+ def initialize(password = "passowrd")
11
+ @password = password
12
+ @salt_size = 0x10
13
+ @key_size = 0x100
14
+ @verifier = rand(16**16).to_s
15
+
16
+ #fixed salt for testing
17
+ @salt = [0x90,0xAC,0x68,0x0E,0x76,0xF9,0x43,0x2B,0x8D,0x13,0xB7,0x1D,0xB7,0xC0,0xFC,0x0D].join
18
+ # @salt =Digest::SHA1.digest(rand(16**16).to_s)
19
+ end
20
+
21
+ def encryption_info
22
+ # v.major v.minor flags header length flags size # AES 128 bit
23
+ header = [3, 0, 2, 0, 0x24, 0, 0, 0, 0xA4, 0, 0, 0, 0x24, 0, 0, 0, 0, 0, 0, 0, 0x0E, 0x66, 0, 0]
24
+ header.concat [0x04, 0x80, 0, 0, 0x80, 0, 0, 0, 0x18, 0, 0, 0, 0xA0, 0xC7, 0xDC, 0x2, 0, 0, 0, 0]
25
+ header.concat "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)".bytes.to_a.pack('s*').bytes.to_a
26
+ header.concat [0, 0]
27
+ header.concat [0x10, 0, 0, 0]
28
+ header.concat [0x90,0xAC,0x68,0x0E,0x76,0xF9,0x43,0x2B,0x8D,0x13,0xB7,0x1D,0xB7,0xC0,0xFC,0x0D]
29
+ header.concat encrypted_verifier.bytes.to_a.pack('c*').bytes.to_a
30
+ header.concat [20, 0,0,0]
31
+ header.concat encrypted_verifier_hash.bytes.to_a.pack('c*').bytes.to_a
32
+ header.flatten!
33
+ header.pack('c*')
34
+ end
35
+
36
+ def encryption_verifier
37
+ {:salt_size => @salt_size,
38
+ :salt => @salt,
39
+ :encrypted_verifier => encrypted_verifier,
40
+ :varifier_hash_size => 0x14,
41
+ :encrypted_verifier_hash => encrypted_verifier_hash}
42
+ end
43
+
44
+ # 2.3.3
45
+ def encrypted_verifier
46
+ @encrypted_verifier ||= encrypt(@verifier)
47
+ end
48
+
49
+ # 2.3.3
50
+ def encrypted_verifier_hash
51
+ verifier_hash = Digest::SHA1.digest(@verifier)
52
+ verifier_hash << Array.new(32 - verifier_hash.size, 0).join('')
53
+ @encrypted_verifier_hash ||= encrypt(verifier_hash)
54
+ end
55
+
56
+ # 2.3.4.7 ECMA-376 Document Encryption Key Generation (Standard Encryption)
57
+ def key
58
+ sha = Digest::SHA1.new() << (@salt + @password)
59
+ (0..49999).each { |i| sha.update(i.to_s+sha.to_s) }
60
+ key = sha.update(sha.to_s+'0').digest
61
+ a = key.bytes.each_with_index.map { |item, i| 0x36 ^ item }
62
+ x1 = Digest::SHA1.digest((a.concat Array.new(64 - key.size, 0x36)).to_s)
63
+ a = key.bytes.each_with_index.map { |item, i| 0x5C ^ item }
64
+ x2 = Digest::SHA1.digest( (a.concat Array.new(64 - key.size, 0x5C) ).to_s)
65
+ x3 = x1 + x2
66
+ @key ||= x3.bytes.to_a[(0..31)].pack('c*')
67
+ end
68
+
69
+ def verify_password
70
+ puts decrypt(@encrypted_verifier)
71
+ end
72
+
73
+ def encrypt(data)
74
+ aes = OpenSSL::Cipher.new("AES-128-ECB")
75
+ aes.encrypt
76
+ aes.key = key
77
+ aes.update(data)
78
+ end
79
+
80
+ def decrypt(data)
81
+ aes = OpenSSL::Cipher.new("AES-128-ECB")
82
+ aes.decrypt
83
+ aes.key = key
84
+ aes.update(data)
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,3 @@
1
+ module Axlsx
2
+ class MsOffcrypto
3
+ end
File without changes
@@ -1,4 +1,4 @@
1
1
  module Axlsx
2
2
  # version
3
- VERSION="1.0.13"
3
+ VERSION="1.0.14"
4
4
  end
@@ -147,6 +147,7 @@ require 'axlsx/workbook/worksheet/worksheet.rb'
147
147
  builder = Nokogiri::XML::Builder.new(:encoding => ENCODING) do |xml|
148
148
  xml.workbook(:xmlns => XML_NS, :'xmlns:r' => XML_NS_R) {
149
149
  xml.workbookPr(:date1904=>@@date1904)
150
+ #<x:workbookProtection workbookPassword="xsd:hexBinary data" lockStructure="1" lockWindows="1" />
150
151
  xml.sheets {
151
152
  @worksheets.each_with_index do |sheet, index|
152
153
  xml.sheet(:name=>sheet.name, :sheetId=>index+1, :"r:id"=>sheet.rId)
@@ -219,6 +219,18 @@ module Axlsx
219
219
  [index, row.index]
220
220
  end
221
221
 
222
+ # Merges all the cells in a range created between this cell and the cell or string name for a cell provided
223
+ # @see worksheet.merge_cells
224
+ # @param [Cell, String] target The last cell, or str ref for the cell in the merge range
225
+ def merge(target)
226
+ range_end = if target.is_a?(String)
227
+ target
228
+ elsif(target.is_a?(Cell))
229
+ target.r
230
+ end
231
+ self.row.worksheet.merge_cells "#{self.r}:#{range_end}" unless range_end.nil?
232
+ end
233
+
222
234
  # Serializes the cell
223
235
  # @param [Nokogiri::XML::Builder] xml The document builder instance this objects xml will be added to.
224
236
  # @return [String] xml text for the cell
@@ -266,6 +278,14 @@ module Axlsx
266
278
  }
267
279
  }
268
280
  end
281
+ elsif @type == :time
282
+ # Using hardcoded offsets here as some operating systems will not except a 'negative' offset from the ruby epoc.
283
+ # (1970)
284
+ epoc1900 = -2209021200 #Time.local(1900, 1, 1)
285
+ epoc1904 = -2082877200 #Time.local(1904, 1, 1)
286
+ epoc = Workbook.date1904 ? epoc1904 : epoc1900
287
+ v = ((@value.localtime.to_f - epoc) /60.0/60.0/24.0).to_f
288
+ xml.c(:r => r, :s => style) { xml.v v }
269
289
  else
270
290
  xml.c(:r => r, :s => style) { xml.v value }
271
291
  end
@@ -315,15 +335,8 @@ module Axlsx
315
335
  # @see Axlsx#date1904
316
336
  def cast_value(v)
317
337
  if (@type == :time && v.is_a?(Time)) || (@type == :time && v.respond_to?(:to_time))
318
- v = v.respond_to?(:to_time) ? v.to_time : v
319
338
  self.style = STYLE_DATE if self.style == 0
320
- # Using hardcoded offsets here as some operating systems will not except a 'negative' offset from the ruby epoc.
321
- # (1970)
322
- epoc1900 = -2209021200 #Time.local(1900, 1, 1)
323
- epoc1904 = -2082877200 #Time.local(1904, 1, 1)
324
- epoc = Workbook.date1904 ? epoc1904 : epoc1900
325
- v = ((v.localtime.to_f - epoc) /60.0/60.0/24.0).to_f
326
- ((v * 10**11).round.to_f / 10**11)
339
+ v.respond_to?(:to_time) ? v.to_time : v
327
340
  elsif @type == :float
328
341
  v.to_f
329
342
  elsif @type == :integer
@@ -24,8 +24,16 @@ module Axlsx
24
24
  # @return [Array] of Hash
25
25
  attr_reader :auto_fit_data
26
26
 
27
- # TODO Merge Cells
28
- # attr_reader :merge_cells
27
+ # An array of merged cell ranges e.d "A1:B3"
28
+ # Content and formatting is read from the first cell.
29
+ # @return Array
30
+ attr_reader :merged_cells
31
+
32
+ # An range that excel will apply an autfilter to "A1:B3"
33
+ # This will turn filtering on for the cells in the range.
34
+ # The first row is considered the header, while subsequent rows are considerd to be data.
35
+ # @return Array
36
+ attr_reader :auto_filter
29
37
 
30
38
  # Creates a new worksheet.
31
39
  # @note the recommended way to manage worksheets is Workbook#add_worksheet
@@ -40,8 +48,28 @@ module Axlsx
40
48
  self.name = options[:name] || "Sheet" + (index+1).to_s
41
49
  @magick_draw = Magick::Draw.new
42
50
  @cols = SimpleTypedList.new Cell
51
+ @merged_cells = []
52
+ end
53
+
54
+ # Creates merge information for this worksheet.
55
+ # Cells can be merged by calling the merge_cells method on a worksheet.
56
+ # @example This would merge the three cells C1..E1 #
57
+ # worksheet.merge_cells "C1:E1"
58
+ # # you can also provide an array of cells to be merged
59
+ # worksheet.merge_cells worksheet.rows.first.cells[(2..4)]
60
+ # #alternatively you can do it from a single cell
61
+ # worksheet["C1"].merge worksheet["E1"]
62
+ # @param [Array, string]
63
+ def merge_cells(cells)
64
+ @merged_cells << if cells.is_a?(String)
65
+ cells
66
+ elsif cells.is_a?(Array)
67
+ cells = cells.sort { |x, y| x.r <=> y.r }
68
+ "#{cells.first.r}:#{cells.last.r}"
69
+ end
43
70
  end
44
71
 
72
+
45
73
  # Returns the cell or cells defined using excel style A1:B3 references.
46
74
  # @param [String] cell_def the string defining the cell or range of cells
47
75
  # @return [Cell, Array]
@@ -81,6 +109,14 @@ module Axlsx
81
109
  @name=v
82
110
  end
83
111
 
112
+ # The auto filter range for the worksheet
113
+ # @param [String] v
114
+ # @see auto_filter
115
+ def auto_filter=(v)
116
+ DataTypeValidator.validate "Worksheet.auto_filter", String, v
117
+ @auto_filter = v
118
+ end
119
+
84
120
  # The part name of this worksheet
85
121
  # @return [String]
86
122
  def pn
@@ -214,6 +250,8 @@ module Axlsx
214
250
  row.to_xml(xml)
215
251
  end
216
252
  }
253
+ xml.autoFilter :ref=>@auto_filter if @auto_filter
254
+ xml.mergeCells(:count=>@merged_cells.size) { @merged_cells.each { | mc | xml.mergeCell(:ref=>mc) } } unless @merged_cells.empty?
217
255
  xml.drawing :"r:id"=>"rId1" if @drawing
218
256
  }
219
257
  end
@@ -234,15 +272,21 @@ module Axlsx
234
272
  def workbook=(v) DataTypeValidator.validate "Worksheet.workbook", Workbook, v; @workbook = v; end
235
273
 
236
274
  # Updates auto fit data.
237
- # Autofit data attempts to determine the cell in a column that has the greatest width by comparing the length of the text multiplied by the size of the font.
275
+ # We store an auto_fit_data item for each column. when a row is added we multiple the font size by the length of the text to
276
+ # attempt to identify the longest cell in the column. This is not 100% accurate as it needs to take into account
277
+ # any formatting that will be applied to the data, as well as the actual rendering size when the length and size is equal
278
+ # for two cells.
238
279
  # @return [Array] of Cell objects
239
280
  # @param [Array] cells an array of cells
240
281
  def update_auto_fit_data(cells)
282
+ # TODO delay this until rendering. too much work when we dont know what they are going to do to the sheet.
241
283
  styles = self.workbook.styles
242
284
  cellXfs, fonts = styles.cellXfs, styles.fonts
243
- sz = fonts[0].sz
285
+ sz = 11
244
286
  cells.each_with_index do |item, index|
287
+ # ignore formula - there is no way for us to know the result
245
288
  next if item.value.is_a?(String) && item.value.start_with?('=')
289
+
246
290
  col = @auto_fit_data[index] || {:longest=>"", :sz=>sz}
247
291
  cell_xf = cellXfs[item.style]
248
292
  font = fonts[cell_xf.fontId || 0]
@@ -255,25 +299,37 @@ module Axlsx
255
299
  end
256
300
  cells
257
301
  end
258
-
302
+
259
303
  # Determines the proper width for a column based on content.
260
304
  # @note
261
- # From ECMA docs
262
- # Column width measured as the number of characters of the maximum digit width of the numbers 0 .. 9 as
263
- # rendered in the normal style's font. There are 4 pixels of margin padding (two on each side), plus 1 pixel padding for the gridlines.
264
- # width = Truncate([!{Number of Characters} * !{Maximum Digit Width} + !{5 pixel padding}]/{Maximum Digit Width}*256)/256
305
+ # width = Truncate([!{Number of Characters} * !{Maximum Digit Width} + !{5 pixel padding}]/!{Maximum Digit Width}*256)/256
265
306
  # @return [Float]
266
307
  # @param [Hash] A hash of auto_fit_data
267
308
  def auto_width(col)
268
- mdw = 6.0 # maximum digit with is always 6.0 with RMagick's default font
269
- mdw_count = 0
270
- best_guess = 1.5 #direct testing shows the results of the documented formula to be a bit too small. This is a best guess scaling
271
- font_scale = col[:sz].to_f / (self.workbook.styles.fonts[0].sz.to_f || 11.0)
272
-
273
- col[:longest].scan(/./mu).each do |i|
274
- mdw_count +=1 if @magick_draw.get_type_metrics(i).width >= mdw
309
+ mdw_count, font_scale, mdw = 0, col[:sz]/11.0, 6.0
310
+ mdw_count = col[:longest].scan(/./mu).reduce(0) do | count, char |
311
+ count +=1 if @magick_draw.get_type_metrics(char).max_advance >= mdw
312
+ count
275
313
  end
276
- ((mdw_count * mdw + 5) / mdw * 256) / 256.0 * best_guess * font_scale
277
- end
314
+ ((mdw_count * mdw + 5) / mdw * 256) / 256.0 * font_scale
315
+ end
316
+
317
+ # Something to look into:
318
+ # width calculation actually needs to be done agains the formatted value for items that apply a
319
+ # format
320
+ # def excel_format(cell)
321
+ # # The most common case.
322
+ # return time.value.to_s if cell.style == 0
323
+ #
324
+ # # The second most common case
325
+ # num_fmt = workbook.styles.cellXfs[items.style].numFmtId
326
+ # return value.to_s if num_fmt == 0
327
+ #
328
+ # format_code = workbook.styles.numFmts[num_fmt]
329
+ # # need to find some exceptionally fast way of parsing value according to
330
+ # # an excel format_code
331
+ # item.value.to_s
332
+ # end
333
+
278
334
  end
279
335
  end