write_xlsx 1.12.1 → 1.12.2

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +12 -0
  3. data/Changes +3 -0
  4. data/LICENSE.txt +1 -1
  5. data/examples/autofilter.rb +1 -2
  6. data/examples/colors.rb +4 -4
  7. data/examples/formats.rb +14 -14
  8. data/lib/write_xlsx/chart/area.rb +1 -1
  9. data/lib/write_xlsx/chart/axis.rb +4 -4
  10. data/lib/write_xlsx/chart/bar.rb +1 -1
  11. data/lib/write_xlsx/chart/caption.rb +8 -4
  12. data/lib/write_xlsx/chart/column.rb +1 -1
  13. data/lib/write_xlsx/chart/doughnut.rb +2 -2
  14. data/lib/write_xlsx/chart/line.rb +1 -1
  15. data/lib/write_xlsx/chart/pie.rb +2 -2
  16. data/lib/write_xlsx/chart/radar.rb +1 -1
  17. data/lib/write_xlsx/chart/scatter.rb +1 -1
  18. data/lib/write_xlsx/chart/series.rb +10 -20
  19. data/lib/write_xlsx/chart/stock.rb +1 -1
  20. data/lib/write_xlsx/chart.rb +14 -21
  21. data/lib/write_xlsx/chartsheet.rb +3 -3
  22. data/lib/write_xlsx/drawing.rb +108 -114
  23. data/lib/write_xlsx/format.rb +20 -24
  24. data/lib/write_xlsx/image.rb +89 -0
  25. data/lib/write_xlsx/image_property.rb +163 -0
  26. data/lib/write_xlsx/inserted_chart.rb +42 -0
  27. data/lib/write_xlsx/package/button.rb +58 -5
  28. data/lib/write_xlsx/package/conditional_format.rb +4 -4
  29. data/lib/write_xlsx/package/packager.rb +22 -27
  30. data/lib/write_xlsx/package/rich_value.rb +1 -1
  31. data/lib/write_xlsx/package/styles.rb +1 -1
  32. data/lib/write_xlsx/package/vml.rb +10 -19
  33. data/lib/write_xlsx/shape.rb +3 -2
  34. data/lib/write_xlsx/sparkline.rb +1 -1
  35. data/lib/write_xlsx/utility.rb +8 -203
  36. data/lib/write_xlsx/version.rb +1 -1
  37. data/lib/write_xlsx/workbook.rb +87 -175
  38. data/lib/write_xlsx/worksheet/data_validation.rb +1 -1
  39. data/lib/write_xlsx/worksheet/hyperlink.rb +2 -2
  40. data/lib/write_xlsx/worksheet.rb +478 -484
  41. data/lib/write_xlsx/zip_file_utils.rb +1 -1
  42. data/write_xlsx.gemspec +3 -3
  43. metadata +11 -11
@@ -1,15 +1,18 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'write_xlsx/package/xml_writer_simple'
5
- require 'write_xlsx/package/button'
6
4
  require 'write_xlsx/colors'
7
- require 'write_xlsx/format'
5
+ require 'write_xlsx/compatibility'
8
6
  require 'write_xlsx/drawing'
7
+ require 'write_xlsx/format'
8
+ require 'write_xlsx/image'
9
+ require 'write_xlsx/image_property'
10
+ require 'write_xlsx/inserted_chart'
11
+ require 'write_xlsx/package/button'
12
+ require 'write_xlsx/package/conditional_format'
13
+ require 'write_xlsx/package/xml_writer_simple'
9
14
  require 'write_xlsx/sparkline'
10
- require 'write_xlsx/compatibility'
11
15
  require 'write_xlsx/utility'
12
- require 'write_xlsx/package/conditional_format'
13
16
  require 'write_xlsx/worksheet/cell_data'
14
17
  require 'write_xlsx/worksheet/data_validation'
15
18
  require 'write_xlsx/worksheet/hyperlink'
@@ -21,24 +24,23 @@ module Writexlsx
21
24
  class Worksheet
22
25
  include Writexlsx::Utility
23
26
 
24
- MAX_DIGIT_WIDTH = 7 # For Calabri 11. # :nodoc:
25
- PADDING = 5 # :nodoc:
26
- COLINFO = Struct.new('ColInfo', :width, :format, :hidden, :level, :collapsed, :autofit)
27
+ COLINFO = Struct.new('ColInfo', :width, :format, :hidden, :level, :collapsed, :autofit)
27
28
 
28
- attr_reader :index # :nodoc:
29
+ attr_reader :index, :name # :nodoc:
29
30
  attr_reader :charts, :images, :tables, :shapes, :drawings # :nodoc:
30
31
  attr_reader :header_images, :footer_images, :background_image # :nodoc:
31
32
  attr_reader :vml_drawing_links # :nodoc:
32
33
  attr_reader :vml_data_id # :nodoc:
33
34
  attr_reader :vml_header_id # :nodoc:
34
35
  attr_reader :autofilter_area # :nodoc:
35
- attr_reader :writer, :set_rows, :col_info # :nodoc:
36
+ attr_reader :writer, :set_rows, :col_info, :row_sizes # :nodoc:
36
37
  attr_reader :vml_shape_id # :nodoc:
37
38
  attr_reader :comments, :comments_author # :nodoc:
38
39
  attr_accessor :data_bars_2010, :dxf_priority # :nodoc:
39
40
  attr_reader :vba_codename # :nodoc:
40
41
  attr_writer :excel_version # :nodoc:
41
42
  attr_reader :filter_cells # :nodoc:
43
+ attr_accessor :default_row_height # :nodoc:
42
44
 
43
45
  def initialize(workbook, index, name) # :nodoc:
44
46
  rowmax = 1_048_576
@@ -78,6 +80,7 @@ module Writexlsx
78
80
 
79
81
  @set_cols = {}
80
82
  @set_rows = {}
83
+ @col_size_changed = false
81
84
  @zoom = 100
82
85
  @zoom_scale_normal = true
83
86
  @right_to_left = false
@@ -94,7 +97,6 @@ module Writexlsx
94
97
 
95
98
  @last_shape_id = 1
96
99
  @rel_count = 0
97
- @hlink_count = 0
98
100
  @external_hyper_links = []
99
101
  @external_drawing_links = []
100
102
  @external_comment_links = []
@@ -116,27 +118,25 @@ module Writexlsx
116
118
  @has_dynamic_functions = false
117
119
  @has_embedded_images = false
118
120
 
119
- @use_future_functions = false
121
+ @use_future_functions = false
120
122
 
121
123
  @header_images = []
122
124
  @footer_images = []
123
- @background_image = ''
125
+ @background_image = nil
124
126
 
125
- @outline_row_level = 0
126
- @outline_col_level = 0
127
+ @outline_row_level = 0
128
+ @outline_col_level = 0
127
129
 
128
130
  @original_row_height = 15
129
131
  @default_row_height = 15
130
132
  @default_row_pixels = 20
131
133
  @default_col_width = 8.43
132
- @default_col_pixels = 64
133
134
  @default_row_rezoed = 0
134
135
  @default_date_pixels = 68
135
136
 
136
137
  @merge = []
137
138
 
138
- @has_vml = false
139
- @has_header_vml = false
139
+ @has_vml = false
140
140
  @comments = Package::Comments.new(self)
141
141
  @buttons_array = []
142
142
  @header_images_array = []
@@ -155,8 +155,8 @@ module Writexlsx
155
155
  @original_row_height = 12.75
156
156
  @default_row_height = 12.75
157
157
  @default_row_pixels = 17
158
- self.margins_left_right = 0.75
159
- self.margins_top_bottom = 1
158
+ self.margins_left_right = 0.75
159
+ self.margins_top_bottom = 1
160
160
  @page_setup.margin_header = 0.5
161
161
  @page_setup.margin_footer = 0.5
162
162
  @page_setup.header_footer_aligns = false
@@ -204,11 +204,6 @@ module Writexlsx
204
204
  end
205
205
  end
206
206
 
207
- #
208
- # The name method is used to retrieve the name of a worksheet.
209
- #
210
- attr_reader :name
211
-
212
207
  #
213
208
  # Set this worksheet as a selected worksheet, i.e. the worksheet has its tab
214
209
  # highlighted.
@@ -296,29 +291,6 @@ module Writexlsx
296
291
  @protected_ranges << [range, range_name, password]
297
292
  end
298
293
 
299
- def protect_default_settings # :nodoc:
300
- {
301
- sheet: true,
302
- content: false,
303
- objects: false,
304
- scenarios: false,
305
- format_cells: false,
306
- format_columns: false,
307
- format_rows: false,
308
- insert_columns: false,
309
- insert_rows: false,
310
- insert_hyperlinks: false,
311
- delete_columns: false,
312
- delete_rows: false,
313
- select_locked_cells: true,
314
- sort: false,
315
- autofilter: false,
316
- pivot_tables: false,
317
- select_unlocked_cells: true
318
- }
319
- end
320
- private :protect_default_settings
321
-
322
294
  #
323
295
  # :call-seq:
324
296
  # set_column(firstcol, lastcol, width, format, hidden, level, collapsed)
@@ -376,7 +348,7 @@ module Writexlsx
376
348
  end
377
349
 
378
350
  # Store the column change to allow optimisations.
379
- @col_size_changed = 1
351
+ @col_size_changed = true
380
352
  end
381
353
 
382
354
  #
@@ -398,12 +370,8 @@ module Writexlsx
398
370
  # Ensure at least $first_col, $last_col and $width
399
371
  return if data.size < 3
400
372
 
401
- first_col = data[0]
402
- last_col = data[1]
403
- pixels = data[2]
404
- format = data[3]
405
- hidden = data[4] || 0
406
- level = data[5]
373
+ first_col, last_col, pixels, format, hidden, level = data
374
+ hidden ||= 0
407
375
 
408
376
  width = pixels_to_width(pixels) if ptrue?(pixels)
409
377
 
@@ -671,7 +639,10 @@ module Writexlsx
671
639
  raise 'Header string must be less than 255 characters' if string.length > 255
672
640
 
673
641
  # Replace the Excel placeholder &[Picture] with the internal &G.
674
- @page_setup.header = string.gsub("&[Picture]", '&G')
642
+ header_footer_string = string.gsub("&[Picture]", '&G')
643
+ # placeholeder /&G/ の数
644
+ placeholder_count = header_footer_string.scan("&G").count
645
+ @page_setup.header = header_footer_string
675
646
 
676
647
  @page_setup.header_footer_aligns = options[:align_with_margins] if options[:align_with_margins]
677
648
 
@@ -683,17 +654,13 @@ module Writexlsx
683
654
  [
684
655
  [:image_left, 'LH'], [:image_center, 'CH'], [:image_right, 'RH']
685
656
  ].each do |p|
686
- @header_images << [options[p.first], p.last] if options[p.first]
657
+ @header_images << ImageProperty.new(options[p.first], position: p.last) if options[p.first]
687
658
  end
688
659
 
689
- # placeholeder /&G/ の数
690
- placeholder_count = @page_setup.header.scan("&G").count
691
-
692
- image_count = @header_images.count
660
+ # # placeholeder /&G/ の数
661
+ # placeholder_count = @page_setup.header.scan("&G").count
693
662
 
694
- raise "Number of header image (#{image_count}) doesn't match placeholder count (#{placeholder_count}) in string: #{@page_setup.header}" if image_count != placeholder_count
695
-
696
- @has_header_vml = true if image_count > 0
663
+ raise "Number of header image (#{@header_images.size}) doesn't match placeholder count (#{placeholder_count}) in string: #{@page_setup.header}" if @header_images.size != placeholder_count
697
664
 
698
665
  @page_setup.margin_header = margin || 0.3
699
666
  @page_setup.header_footer_changed = true
@@ -705,8 +672,6 @@ module Writexlsx
705
672
  def set_footer(string = '', margin = 0.3, options = {})
706
673
  raise 'Footer string must be less than 255 characters' if string.length > 255
707
674
 
708
- @page_setup.footer = string.dup
709
-
710
675
  # Replace the Excel placeholder &[Picture] with the internal &G.
711
676
  @page_setup.footer = string.gsub("&[Picture]", '&G')
712
677
 
@@ -720,17 +685,13 @@ module Writexlsx
720
685
  [
721
686
  [:image_left, 'LF'], [:image_center, 'CF'], [:image_right, 'RF']
722
687
  ].each do |p|
723
- @footer_images << [options[p.first], p.last] if options[p.first]
688
+ @footer_images << ImageProperty.new(options[p.first], position: p.last) if options[p.first]
724
689
  end
725
690
 
726
691
  # placeholeder /&G/ の数
727
692
  placeholder_count = @page_setup.footer.scan("&G").count
728
693
 
729
- image_count = @footer_images.count
730
-
731
- raise "Number of footer image (#{image_count}) doesn't match placeholder count (#{placeholder_count}) in string: #{@page_setup.footer}" if image_count != placeholder_count
732
-
733
- @has_header_vml = true if image_count > 0
694
+ raise "Number of footer image (#{@footer_images.size}) doesn't match placeholder count (#{placeholder_count}) in string: #{@page_setup.footer}" if @footer_images.size != placeholder_count
734
695
 
735
696
  @page_setup.margin_footer = margin
736
697
  @page_setup.header_footer_changed = true
@@ -1327,18 +1288,6 @@ module Writexlsx
1327
1288
  store_data_to_table(BlankCellData.new(_format), _row, _col)
1328
1289
  end
1329
1290
 
1330
- def expand_formula(formula, function, addition = '')
1331
- if formula =~ /\b(#{function})/
1332
- formula.gsub(
1333
- ::Regexp.last_match(1),
1334
- "_xlfn#{addition}.#{::Regexp.last_match(1)}"
1335
- )
1336
- else
1337
- formula
1338
- end
1339
- end
1340
- private :expand_formula
1341
-
1342
1291
  #
1343
1292
  # Utility method to strip equal sign and array braces from a formula
1344
1293
  # and also expand out future and dynamic array formulas.
@@ -1758,7 +1707,7 @@ module Writexlsx
1758
1707
  # The outline_settings() method is used to control the appearance of
1759
1708
  # outlines in Excel.
1760
1709
  #
1761
- def outline_settings(visible = 1, symbols_below = 1, symbols_right = 1, auto_style = 0)
1710
+ def outline_settings(visible = 1, symbols_below = 1, symbols_right = 1, auto_style = false)
1762
1711
  @outline_on = visible
1763
1712
  @outline_below = symbols_below
1764
1713
  @outline_right = symbols_right
@@ -1804,14 +1753,15 @@ module Writexlsx
1804
1753
  _tip = tip
1805
1754
  _ignore_write_string = ignore_write_string
1806
1755
  end
1807
- _format, _str = _str, _format if _str.respond_to?(:xf_index) || !_format.respond_to?(:xf_index)
1756
+
1757
+ _format, _str = _str, _format if _str.respond_to?(:xf_index) || (_format && !_format.respond_to?(:xf_index))
1808
1758
  raise WriteXLSXInsufficientArgumentError if [_row, _col, _url].include?(nil)
1809
1759
 
1810
1760
  # Check that row and col are valid and store max and min values
1811
1761
  check_dimensions(_row, _col)
1812
1762
  store_row_col_max_min_values(_row, _col)
1813
1763
 
1814
- hyperlink = Hyperlink.factory(_url, _str, _tip)
1764
+ hyperlink = Hyperlink.factory(_url, _str, _tip, @max_url_length)
1815
1765
  store_hyperlink(_row, _col, hyperlink)
1816
1766
 
1817
1767
  raise "URL '#{url}' added but URL exceeds Excel's limit of 65,530 URLs per worksheet." if hyperlinks_count > 65_530
@@ -1914,10 +1864,10 @@ module Writexlsx
1914
1864
  x_offset = _chart.x_offset if ptrue?(_chart.x_offset)
1915
1865
  y_offset = _chart.y_offset if ptrue?(_chart.y_offset)
1916
1866
 
1917
- @charts << [
1867
+ @charts << InsertedChart.new(
1918
1868
  _row, _col, _chart, x_offset, y_offset,
1919
1869
  x_scale, y_scale, anchor, description, decorative
1920
- ]
1870
+ )
1921
1871
  end
1922
1872
 
1923
1873
  #
@@ -1959,10 +1909,10 @@ module Writexlsx
1959
1909
  y_scale ||= 1
1960
1910
  anchor ||= 2
1961
1911
 
1962
- @images << [
1912
+ @images << Image.new(
1963
1913
  _row, _col, _image, x_offset, y_offset,
1964
1914
  x_scale, y_scale, url, tip, anchor, description, decorative
1965
- ]
1915
+ )
1966
1916
  end
1967
1917
 
1968
1918
  #
@@ -2005,18 +1955,19 @@ module Writexlsx
2005
1955
  end
2006
1956
 
2007
1957
  # Get the image properties, mainly for the type and checksum.
2008
- image_properties = get_image_properties(image)
2009
- type = image_properties[0]
2010
- md5 = image_properties[6]
1958
+ image_property = ImageProperty.new(
1959
+ image, description: description, decorative: decorative
1960
+ )
1961
+ @workbook.store_image_types(image_property.type)
2011
1962
 
2012
1963
  # Check for duplicate images.
2013
- image_index = @embedded_image_indexes[md5]
1964
+ image_index = @embedded_image_indexes[image_property.md5]
2014
1965
 
2015
1966
  unless ptrue?(image_index)
2016
- @workbook.embedded_images << [image, type, description, decorative]
1967
+ @workbook.embedded_images << image_property
2017
1968
 
2018
1969
  image_index = @workbook.embedded_images.size
2019
- @embedded_image_indexes[md5] = image_index
1970
+ @embedded_image_indexes[image_property.md5] = image_index
2020
1971
  end
2021
1972
 
2022
1973
  # Write the cell placeholder.
@@ -2024,6 +1975,76 @@ module Writexlsx
2024
1975
  @has_embedded_images = true
2025
1976
  end
2026
1977
 
1978
+ #
1979
+ # :call-seq:
1980
+ # insert_shape(row, col, shape [ , x, y, x_scale, y_scale ])
1981
+ #
1982
+ # Insert a shape into the worksheet.
1983
+ #
1984
+ def insert_shape(
1985
+ row_start, column_start, shape = nil, x_offset = nil, y_offset = nil,
1986
+ x_scale = nil, y_scale = nil, anchor = nil
1987
+ )
1988
+ # Check for a cell reference in A1 notation and substitute row and column.
1989
+ if (row_col_array = row_col_notation(row_start))
1990
+ _row_start, _column_start = row_col_array
1991
+ _shape = column_start
1992
+ _x_offset = shape
1993
+ _y_offset = x_offset
1994
+ _x_scale = y_offset
1995
+ _y_scale = x_scale
1996
+ _anchor = y_scale
1997
+ else
1998
+ _row_start = row_start
1999
+ _column_start = column_start
2000
+ _shape = shape
2001
+ _x_offset = x_offset
2002
+ _y_offset = y_offset
2003
+ _x_scale = x_scale
2004
+ _y_scale = y_scale
2005
+ _anchor = anchor
2006
+ end
2007
+ raise "Insufficient arguments in insert_shape()" if [_row_start, _column_start, _shape].include?(nil)
2008
+
2009
+ _shape.set_position(
2010
+ _row_start, _column_start, _x_offset, _y_offset,
2011
+ _x_scale, _y_scale, _anchor
2012
+ )
2013
+ # Assign a shape ID.
2014
+ while true
2015
+ id = _shape.id || 0
2016
+ used = @shape_hash[id]
2017
+
2018
+ # Test if shape ID is already used. Otherwise assign a new one.
2019
+ if !used && id != 0
2020
+ break
2021
+ else
2022
+ @last_shape_id += 1
2023
+ _shape.id = @last_shape_id
2024
+ end
2025
+ end
2026
+
2027
+ # Allow lookup of entry into shape array by shape ID.
2028
+ @shape_hash[_shape.id] = _shape.element = @shapes.size
2029
+
2030
+ insert = if ptrue?(_shape.stencil)
2031
+ # Insert a copy of the shape, not a reference so that the shape is
2032
+ # used as a stencil. Previously stamped copies don't get modified
2033
+ # if the stencil is modified.
2034
+ _shape.dup
2035
+ else
2036
+ _shape
2037
+ end
2038
+
2039
+ # For connectors change x/y coords based on location of connected shapes.
2040
+ insert.auto_locate_connectors(@shapes, @shape_hash)
2041
+
2042
+ # Insert a link to the shape on the list of shapes. Connection to
2043
+ # the parent shape is maintained.
2044
+ @shapes << insert
2045
+ insert
2046
+ end
2047
+
2027
2048
  #
2028
2049
  # :call-seq:
2029
2050
  # repeat_formula(row, column, formula [ , format ])
@@ -2087,9 +2108,6 @@ module Writexlsx
2087
2108
  level = args[4] || 0
2088
2109
  collapsed = args[5] || 0
2089
2110
 
2090
- # Get the default row height.
2091
- default_height = @default_row_height
2092
-
2093
2111
  # Use min col in check_dimensions. Default to 0 if undefined.
2094
2112
  min_col = @dim_colmin || 0
2095
2113
 
@@ -2097,12 +2115,12 @@ module Writexlsx
2097
2115
  check_dimensions(row, min_col)
2098
2116
  store_row_col_max_min_values(row, min_col)
2099
2117
 
2100
- height ||= default_height
2118
+ height ||= @default_row_height
2101
2119
 
2102
2120
  # If the height is 0 the row is hidden and the height is the default.
2103
2121
  if height == 0
2104
2122
  hidden = 1
2105
- height = default_height
2123
+ height = @default_row_height
2106
2124
  end
2107
2125
 
2108
2126
  # Set the limits for the outline levels (0 <= x <= 7).
@@ -2311,8 +2329,11 @@ module Writexlsx
2311
2329
  _col = col
2312
2330
  _properties = properties
2313
2331
  end
2314
- @buttons_array << button_params(_row, _col, _properties)
2315
- @has_vml = 1
2332
+
2333
+ @buttons_array << Writexlsx::Package::Button.new(
2334
+ self, _row, _col, _properties, @default_row_pixels, @buttons_array.size + 1
2335
+ )
2336
+ @has_vml = true
2316
2337
  end
2317
2338
 
2318
2339
  #
@@ -2415,7 +2436,7 @@ module Writexlsx
2415
2436
 
2416
2437
  tokens = extract_filter_tokens(expression)
2417
2438
 
2418
- raise "Incorrect number of tokens in expression '#{expression}'" unless tokens.size == 3 || tokens.size == 7
2439
+ raise "Incorrect number of tokens in expression '#{expression}'" unless [3, 7].include?(tokens.size)
2419
2440
 
2420
2441
  tokens = parse_filter_expression(expression, tokens)
2421
2442
 
@@ -2494,7 +2515,7 @@ module Writexlsx
2494
2515
  end
2495
2516
 
2496
2517
  def has_header_vml? # :nodoc:
2497
- @has_header_vml
2518
+ !(@header_images.empty? && @footer_images.empty?)
2498
2519
  end
2499
2520
 
2500
2521
  def has_comments? # :nodoc:
@@ -2509,49 +2530,29 @@ module Writexlsx
2509
2530
  !!@is_chartsheet
2510
2531
  end
2511
2532
 
2512
- def set_external_vml_links(vml_drawing_id) # :nodoc:
2513
- @external_vml_links <<
2514
- ['/vmlDrawing', "../drawings/vmlDrawing#{vml_drawing_id}.vml"]
2515
- end
2516
-
2517
- def set_external_comment_links(comment_id) # :nodoc:
2518
- @external_comment_links <<
2519
- ['/comments', "../comments#{comment_id}.xml"]
2520
- end
2521
-
2522
2533
  #
2523
2534
  # Set up chart/drawings.
2524
2535
  #
2525
2536
  def prepare_chart(index, chart_id, drawing_id) # :nodoc:
2526
2537
  drawing_type = 1
2527
2538
 
2528
- row, col, chart, x_offset, y_offset,
2529
- x_scale, y_scale, anchor, description, decorative = @charts[index]
2530
-
2531
- chart.id = chart_id - 1
2532
- x_scale ||= 0
2533
- y_scale ||= 0
2539
+ inserted_chart = @charts[index]
2540
+ inserted_chart.chart.id = chart_id - 1
2534
2541
 
2535
- # Use user specified dimensions, if any.
2536
- width = chart.width if ptrue?(chart.width)
2537
- height = chart.height if ptrue?(chart.height)
2538
-
2539
- width = (0.5 + (width * x_scale)).to_i
2540
- height = (0.5 + (height * y_scale)).to_i
2541
-
2542
- dimensions = position_object_emus(col, row, x_offset, y_offset, width, height, anchor)
2543
-
2544
- # Set the chart name for the embedded object if it has been specified.
2545
- name = chart.name
2542
+ dimensions = position_object_emus(inserted_chart)
2546
2543
 
2547
2544
  # Create a Drawing object to use with worksheet unless one already exists.
2548
- drawing = Drawing.new(drawing_type, dimensions, 0, 0, nil, anchor, drawing_rel_index, 0, nil, name, description, decorative)
2545
+ drawing = Drawing.new(
2546
+ drawing_type, dimensions, 0, 0, nil, inserted_chart.anchor,
2547
+ drawing_rel_index, 0, nil, inserted_chart.name,
2548
+ inserted_chart.description, inserted_chart.decorative
2549
+ )
2549
2550
  if drawings?
2550
2551
  @drawings.add_drawing_object(drawing)
2551
2552
  else
2552
2553
  @drawings = Drawings.new
2553
2554
  @drawings.add_drawing_object(drawing)
2554
- @drawings.embedded = 1
2555
+ @drawings.embedded = true
2555
2556
 
2556
2557
  @external_drawing_links << ['/drawing', "../drawings/drawing#{drawing_id}.xml"]
2557
2558
  end
@@ -2589,85 +2590,6 @@ module Writexlsx
2589
2590
  data
2590
2591
  end
2591
2592
 
2592
- #
2593
- # Calculate the vertices that define the position of a graphical object within
2594
- # the worksheet in pixels.
2595
- #
2596
- def position_object_pixels(col_start, row_start, x1, y1, width, height, anchor = nil) # :nodoc:
2597
- # Adjust start column for negative offsets.
2598
- while x1 < 0 && col_start > 0
2599
- x1 += size_col(col_start - 1)
2600
- col_start -= 1
2601
- end
2602
-
2603
- # Adjust start row for negative offsets.
2604
- while y1 < 0 && row_start > 0
2605
- y1 += size_row(row_start - 1)
2606
- row_start -= 1
2607
- end
2608
-
2609
- # Ensure that the image isn't shifted off the page at top left.
2610
- x1 = 0 if x1 < 0
2611
- y1 = 0 if y1 < 0
2612
-
2613
- # Calculate the absolute x offset of the top-left vertex.
2614
- x_abs = if @col_size_changed
2615
- (0..col_start - 1).inject(0) { |sum, col| sum += size_col(col, anchor) }
2616
- else
2617
- # Optimisation for when the column widths haven't changed.
2618
- @default_col_pixels * col_start
2619
- end
2620
- x_abs += x1
2621
-
2622
- # Calculate the absolute y offset of the top-left vertex.
2623
- # Store the column change to allow optimisations.
2624
- y_abs = if @row_size_changed
2625
- (0..row_start - 1).inject(0) { |sum, row| sum += size_row(row, anchor) }
2626
- else
2627
- # Optimisation for when the row heights haven't changed.
2628
- @default_row_pixels * row_start
2629
- end
2630
- y_abs += y1
2631
-
2632
- # Adjust start column for offsets that are greater than the col width.
2633
- while x1 >= size_col(col_start, anchor)
2634
- x1 -= size_col(col_start)
2635
- col_start += 1
2636
- end
2637
-
2638
- # Adjust start row for offsets that are greater than the row height.
2639
- while y1 >= size_row(row_start, anchor)
2640
- y1 -= size_row(row_start)
2641
- row_start += 1
2642
- end
2643
-
2644
- # Initialise end cell to the same as the start cell.
2645
- col_end = col_start
2646
- row_end = row_start
2647
-
2648
- # Only offset the image in the cell if the row/col isn't hidden.
2649
- width += x1 if size_col(col_start, anchor) > 0
2650
- height += y1 if size_row(row_start, anchor) > 0
2651
-
2652
- # Subtract the underlying cell widths to find the end cell of the object.
2653
- while width >= size_col(col_end, anchor)
2654
- width -= size_col(col_end, anchor)
2655
- col_end += 1
2656
- end
2657
-
2658
- # Subtract the underlying cell heights to find the end cell of the object.
2659
- while height >= size_row(row_end, anchor)
2660
- height -= size_row(row_end, anchor)
2661
- row_end += 1
2662
- end
2663
-
2664
- # The end vertices are whatever is left from the width and height.
2665
- x2 = width
2666
- y2 = height
2667
-
2668
- [col_start, row_start, x1, y1, col_end, row_end, x2, y2, x_abs, y_abs]
2669
- end
2670
-
2671
2593
  def comments_visible? # :nodoc:
2672
2594
  !!@comments_visible
2673
2595
  end
@@ -2725,7 +2647,7 @@ module Writexlsx
2725
2647
  if index.to_s =~ /^#([0-9A-F]{6})$/i
2726
2648
  "FF#{::Regexp.last_match(1).upcase}"
2727
2649
  else
2728
- "FF#{super(index)}"
2650
+ "FF#{palette_color_from_index(index)}"
2729
2651
  end
2730
2652
  end
2731
2653
 
@@ -2872,42 +2794,242 @@ module Writexlsx
2872
2794
  @has_embedded_images
2873
2795
  end
2874
2796
 
2875
- private
2797
+ # Check that some image or drawing needs to be processed.
2798
+ def some_image_or_drawing_to_be_processed?
2799
+ charts.size + images.size + shapes.size + header_images.size + footer_images.size + (background_image ? 1 : 0) == 0
2800
+ end
2876
2801
 
2877
- #
2878
- # Compare adjacent column information structures.
2879
- #
2880
- def compare_col_info(col_options, previous_options)
2881
- if !col_options.width.nil? != !previous_options.width.nil?
2882
- return nil
2883
- end
2884
- if col_options.width && previous_options.width &&
2885
- col_options.width != previous_options.width
2886
- return nil
2887
- end
2802
+ def prepare_drawings(drawing_id, chart_ref_id, image_ref_id, image_ids, header_image_ids, background_ids)
2803
+ has_drawings = false
2888
2804
 
2889
- if !col_options.format.nil? != !previous_options.format.nil?
2890
- return nil
2891
- end
2892
- if col_options.format && previous_options.format &&
2893
- col_options.format != previous_options.format
2894
- return nil
2895
- end
2805
+ # Check that some image or drawing needs to be processed.
2806
+ unless some_image_or_drawing_to_be_processed?
2896
2807
 
2897
- return nil if col_options.hidden != previous_options.hidden
2898
- return nil if col_options.level != previous_options.level
2899
- return nil if col_options.collapsed != previous_options.collapsed
2808
+ # Don't increase the drawing_id header/footer images.
2809
+ unless charts.empty? && images.empty? && shapes.empty?
2810
+ drawing_id += 1
2811
+ has_drawings = true
2812
+ end
2900
2813
 
2901
- true
2902
- end
2814
+ # Prepare the background images.
2815
+ image_ref_id = prepare_background_image(background_ids, image_ref_id)
2903
2816
 
2904
- #
2905
- # Get the index used to address a drawing rel link.
2906
- #
2907
- def drawing_rel_index(target = nil)
2908
- if !target
2909
- # Undefined values for drawings like charts will always be unique.
2910
- @drawing_rels_id += 1
2817
+ # Prepare the worksheet images.
2818
+ images.each do |image|
2819
+ image_ref_id = prepare_image(image, drawing_id, image_ids, image_ref_id)
2820
+ end
2821
+
2822
+ # Prepare the worksheet charts.
2823
+ charts.each_with_index do |_chart, index|
2824
+ chart_ref_id += 1
2825
+ prepare_chart(index, chart_ref_id, drawing_id)
2826
+ end
2827
+
2828
+ # Prepare the worksheet shapes.
2829
+ shapes.each_with_index do |_shape, index|
2830
+ prepare_shape(index, drawing_id)
2831
+ end
2832
+
2833
+ # Prepare the header and footer images.
2834
+ [header_images, footer_images].each do |images|
2835
+ images.each do |image|
2836
+ image_ref_id = prepare_header_footer_image(
2837
+ image, header_image_ids, image_ref_id
2838
+ )
2839
+ end
2840
+ end
2841
+
2842
+ if has_drawings
2843
+ @workbook.drawings << drawings
2844
+ end
2845
+ end
2846
+
2847
+ [drawing_id, chart_ref_id, image_ref_id]
2848
+ end
2849
+
2850
+ #
2851
+ # Set the background image for the worksheet.
2852
+ #
2853
+ def set_background(image)
2854
+ raise "Couldn't locate #{image}: $!" unless File.exist?(image)
2855
+
2856
+ @background_image = ImageProperty.new(image)
2857
+ end
2858
+
2859
+ #
2860
+ # Calculate the vertices that define the position of a graphical object
2861
+ # within the worksheet in pixels.
2862
+ #
2863
+ def position_object_pixels(col_start, row_start, x1, y1, width, height, anchor = nil) # :nodoc:
2864
+ # Adjust start column for negative offsets.
2865
+ while x1 < 0 && col_start > 0
2866
+ x1 += size_col(col_start - 1)
2867
+ col_start -= 1
2868
+ end
2869
+
2870
+ # Adjust start row for negative offsets.
2871
+ while y1 < 0 && row_start > 0
2872
+ y1 += size_row(row_start - 1)
2873
+ row_start -= 1
2874
+ end
2875
+
2876
+ # Ensure that the image isn't shifted off the page at top left.
2877
+ x1 = 0 if x1 < 0
2878
+ y1 = 0 if y1 < 0
2879
+
2880
+ # Calculate the absolute x offset of the top-left vertex.
2881
+ x_abs = if @col_size_changed
2882
+ (0..(col_start - 1)).inject(0) { |sum, col| sum += size_col(col, anchor) }
2883
+ else
2884
+ # Optimisation for when the column widths haven't changed.
2885
+ DEFAULT_COL_PIXELS * col_start
2886
+ end
2887
+ x_abs += x1
2888
+
2889
+ # Calculate the absolute y offset of the top-left vertex.
2890
+ # Store the column change to allow optimisations.
2891
+ y_abs = if @row_size_changed
2892
+ (0..(row_start - 1)).inject(0) { |sum, row| sum += size_row(row, anchor) }
2893
+ else
2894
+ # Optimisation for when the row heights haven't changed.
2895
+ @default_row_pixels * row_start
2896
+ end
2897
+ y_abs += y1
2898
+
2899
+ # Adjust start column for offsets that are greater than the col width.
2900
+ while x1 >= size_col(col_start, anchor)
2901
+ x1 -= size_col(col_start)
2902
+ col_start += 1
2903
+ end
2904
+
2905
+ # Adjust start row for offsets that are greater than the row height.
2906
+ while y1 >= size_row(row_start, anchor)
2907
+ y1 -= size_row(row_start)
2908
+ row_start += 1
2909
+ end
2910
+
2911
+ # Initialise end cell to the same as the start cell.
2912
+ col_end = col_start
2913
+ row_end = row_start
2914
+
2915
+ # Only offset the image in the cell if the row/col isn't hidden.
2916
+ width += x1 if size_col(col_start, anchor) > 0
2917
+ height += y1 if size_row(row_start, anchor) > 0
2918
+
2919
+ # Subtract the underlying cell widths to find the end cell of the object.
2920
+ while width >= size_col(col_end, anchor)
2921
+ width -= size_col(col_end, anchor)
2922
+ col_end += 1
2923
+ end
2924
+
2925
+ # Subtract the underlying cell heights to find the end cell of the object.
2926
+ while height >= size_row(row_end, anchor)
2927
+ height -= size_row(row_end, anchor)
2928
+ row_end += 1
2929
+ end
2930
+
2931
+ # The end vertices are whatever is left from the width and height.
2932
+ x2 = width
2933
+ y2 = height
2934
+
2935
+ [col_start, row_start, x1, y1, col_end, row_end, x2, y2, x_abs, y_abs]
2936
+ end
2937
+
2938
+ private
2939
+
2940
+ #
2941
+ # Convert the width of a cell from user's units to pixels. Excel rounds
2942
+ # the column width to the nearest pixel. If the width hasn't been set
2943
+ # by the user we use the default value. A hidden column is treated as
2944
+ # having a width of zero unless it has the special "object_position" of
2945
+ # 4 (size with cells).
2946
+ #
2947
+ def size_col(col, anchor = 0) # :nodoc:
2948
+ # Look up the cell value to see if it has been changed.
2949
+ if col_info[col]
2950
+ width = col_info[col].width || @default_col_width
2951
+ hidden = col_info[col].hidden
2952
+
2953
+ # Convert to pixels.
2954
+ pixels = if hidden == 1 && anchor != 4
2955
+ 0
2956
+ elsif width < 1
2957
+ ((width * (MAX_DIGIT_WIDTH + PADDING)) + 0.5).to_i
2958
+ else
2959
+ ((width * MAX_DIGIT_WIDTH) + 0.5).to_i + PADDING
2960
+ end
2961
+ else
2962
+ pixels = DEFAULT_COL_PIXELS
2963
+ end
2964
+ pixels
2965
+ end
2966
+
2967
+ #
2968
+ # Convert the height of a cell from user's units to pixels. If the height
2969
+ # hasn't been set by the user we use the default value. A hidden row is
2970
+ # treated as having a height of zero unless it has the special
2971
+ # "object_position" of 4 (size with cells).
2972
+ #
2973
+ def size_row(row, anchor = 0) # :nodoc:
2974
+ # Look up the cell value to see if it has been changed
2975
+ if row_sizes[row]
2976
+ height, hidden = row_sizes[row]
2977
+
2978
+ pixels = if hidden == 1 && anchor != 4
2979
+ 0
2980
+ else
2981
+ (4 / 3.0 * height).to_i
2982
+ end
2983
+ else
2984
+ pixels = (4 / 3.0 * default_row_height).to_i
2985
+ end
2986
+ pixels
2987
+ end
2988
+
2989
+ #
2990
+ # Compare adjacent column information structures.
2991
+ #
2992
+ def compare_col_info(col_options, previous_options)
2993
+ if !col_options.width.nil? != !previous_options.width.nil?
2994
+ return nil
2995
+ end
2996
+ if col_options.width && previous_options.width &&
2997
+ col_options.width != previous_options.width
2998
+ return nil
2999
+ end
3000
+
3001
+ if !col_options.format.nil? != !previous_options.format.nil?
3002
+ return nil
3003
+ end
3004
+ if col_options.format && previous_options.format &&
3005
+ col_options.format != previous_options.format
3006
+ return nil
3007
+ end
3008
+
3009
+ return nil if col_options.hidden != previous_options.hidden
3010
+ return nil if col_options.level != previous_options.level
3011
+ return nil if col_options.collapsed != previous_options.collapsed
3012
+
3013
+ true
3014
+ end
3015
+
3016
+ def set_external_vml_links(vml_drawing_id) # :nodoc:
3017
+ @external_vml_links <<
3018
+ ['/vmlDrawing', "../drawings/vmlDrawing#{vml_drawing_id}.vml"]
3019
+ end
3020
+
3021
+ def set_external_comment_links(comment_id) # :nodoc:
3022
+ @external_comment_links <<
3023
+ ['/comments', "../comments#{comment_id}.xml"]
3024
+ end
3025
+
3026
+ #
3027
+ # Get the index used to address a drawing rel link.
3028
+ #
3029
+ def drawing_rel_index(target = nil)
3030
+ if !target
3031
+ # Undefined values for drawings like charts will always be unique.
3032
+ @drawing_rels_id += 1
2911
3033
  elsif ptrue?(@drawing_rels[target])
2912
3034
  @drawing_rels[target]
2913
3035
  else
@@ -3185,12 +3307,13 @@ module Writexlsx
3185
3307
  end
3186
3308
 
3187
3309
  #
3188
- # Calculate the vertices that define the position of a graphical object within
3189
- # the worksheet in EMUs.
3310
+ # Calculate the vertices that define the position of a graphical object
3311
+ # within the worksheet in EMUs.
3190
3312
  #
3191
- def position_object_emus(col_start, row_start, x1, y1, width, height, anchor = nil) # :nodoc:
3313
+ def position_object_emus(graphical_object) # :nodoc:
3314
+ go = graphical_object
3192
3315
  col_start, row_start, x1, y1, col_end, row_end, x2, y2, x_abs, y_abs =
3193
- position_object_pixels(col_start, row_start, x1, y1, width, height, anchor)
3316
+ position_object_pixels(go.col, go.row, go.x_offset, go.y_offset, go.scaled_width, go.scaled_height, go.anchor)
3194
3317
 
3195
3318
  # Convert the pixel values to EMUs. See above.
3196
3319
  x1 = (0.5 + (9_525 * x1)).to_i
@@ -3203,54 +3326,6 @@ module Writexlsx
3203
3326
  [col_start, row_start, x1, y1, col_end, row_end, x2, y2, x_abs, y_abs]
3204
3327
  end
3205
3328
 
3206
- #
3207
- # Convert the width of a cell from user's units to pixels. Excel rounds the
3208
- # column width to the nearest pixel. If the width hasn't been set by the user
3209
- # we use the default value. A hidden column is treated as having a width of
3210
- # zero unless it has the special "object_position" of 4 (size with cells).
3211
- #
3212
- def size_col(col, anchor = 0) # :nodoc:
3213
- # Look up the cell value to see if it has been changed.
3214
- if @col_info[col]
3215
- width = @col_info[col].width || @default_col_width
3216
- hidden = @col_info[col].hidden
3217
-
3218
- # Convert to pixels.
3219
- pixels = if hidden == 1 && anchor != 4
3220
- 0
3221
- elsif width < 1
3222
- ((width * (MAX_DIGIT_WIDTH + PADDING)) + 0.5).to_i
3223
- else
3224
- ((width * MAX_DIGIT_WIDTH) + 0.5).to_i + PADDING
3225
- end
3226
- else
3227
- pixels = @default_col_pixels
3228
- end
3229
- pixels
3230
- end
3231
-
3232
- #
3233
- # Convert the height of a cell from user's units to pixels. If the height
3234
- # hasn't been set by the user we use the default value. A hidden row is
3235
- # treated as having a height of zero unless it has the special
3236
- # "object_position" of 4 (size with cells).
3237
- #
3238
- def size_row(row, anchor = 0) # :nodoc:
3239
- # Look up the cell value to see if it has been changed
3240
- if @row_sizes[row]
3241
- height, hidden = @row_sizes[row]
3242
-
3243
- pixels = if hidden == 1 && anchor != 4
3244
- 0
3245
- else
3246
- (4 / 3.0 * height).to_i
3247
- end
3248
- else
3249
- pixels = (4 / 3.0 * @default_row_height).to_i
3250
- end
3251
- pixels
3252
- end
3253
-
3254
3329
  #
3255
3330
  # Convert the width of a cell from pixels to character units.
3256
3331
  #
@@ -3277,48 +3352,44 @@ module Writexlsx
3277
3352
  #
3278
3353
  # Set up image/drawings.
3279
3354
  #
3280
- def prepare_image(index, image_id, drawing_id, width, height, name, image_type, x_dpi = 96, y_dpi = 96, md5 = nil) # :nodoc:
3281
- x_dpi ||= 96
3282
- y_dpi ||= 96
3355
+ def prepare_image(image, drawing_id, image_ids, image_ref_id) # :nodoc:
3356
+ image_type = image.type
3357
+ x_dpi = image.x_dpi || 96
3358
+ y_dpi = image.y_dpi || 96
3359
+ md5 = image.md5
3283
3360
  drawing_type = 2
3284
3361
 
3285
- row, col, _image, x_offset, y_offset,
3286
- x_scale, y_scale, url, tip, anchor, description, decorative = @images[index]
3362
+ @workbook.store_image_types(image_type)
3287
3363
 
3288
- width *= x_scale
3289
- height *= y_scale
3290
-
3291
- width *= 96.0 / x_dpi
3292
- height *= 96.0 / y_dpi
3293
-
3294
- dimensions = position_object_emus(col, row, x_offset, y_offset, width, height, anchor)
3364
+ if image_ids[md5]
3365
+ image_id = image_ids[md5]
3366
+ else
3367
+ image_ref_id += 1
3368
+ image_ids[md5] = image_id = image_ref_id
3369
+ @workbook.images << image
3370
+ end
3295
3371
 
3296
- # Convert from pixels to emus.
3297
- width = (0.5 + (width * 9_525)).to_i
3298
- height = (0.5 + (height * 9_525)).to_i
3372
+ dimensions = position_object_emus(image)
3299
3373
 
3300
3374
  # Create a Drawing object to use with worksheet unless one already exists.
3301
- drawing = Drawing.new(drawing_type, dimensions, width, height, nil, anchor, 0, 0, tip, name, description, decorative)
3302
- if drawings?
3303
- drawings = @drawings
3304
- else
3305
- drawings = Drawings.new
3306
- drawings.embedded = 1
3307
-
3308
- @drawings = drawings
3375
+ drawing = Drawing.new(
3376
+ drawing_type, dimensions, image.width_emus, image.height_emus,
3377
+ nil, image.anchor, 0, 0, image.tip, image.name,
3378
+ image.description || image.name, image.decorative
3379
+ )
3380
+ unless drawings?
3381
+ @drawings = Drawings.new
3382
+ @drawings.embedded = true
3309
3383
 
3310
3384
  @external_drawing_links << ['/drawing', "../drawings/drawing#{drawing_id}.xml"]
3311
3385
  end
3312
- drawings.add_drawing_object(drawing)
3313
-
3314
- drawing.description = name unless description
3386
+ @drawings.add_drawing_object(drawing)
3315
3387
 
3316
- if url
3317
- rel_type = '/hyperlink'
3388
+ if image.url
3318
3389
  target_mode = 'External'
3319
- target = escape_url(url) if url =~ %r{^[fh]tt?ps?://} || url =~ /^mailto:/
3320
- if url =~ /^external:/
3321
- target = escape_url(url.sub(/^external:/, ''))
3390
+ target = escape_url(image.url) if image.url =~ %r{^[fh]tt?ps?://} || image.url =~ /^mailto:/
3391
+ if image.url =~ /^external:/
3392
+ target = escape_url(image.url.sub(/^external:/, ''))
3322
3393
 
3323
3394
  # Additional escape not required in worksheet hyperlinks
3324
3395
  target = target.gsub("#", '%23')
@@ -3331,8 +3402,8 @@ module Writexlsx
3331
3402
  end
3332
3403
  end
3333
3404
 
3334
- if url =~ /^internal:/
3335
- target = url.sub(/^internal:/, '#')
3405
+ if image.url =~ /^internal:/
3406
+ target = image.url.sub(/^internal:/, '#')
3336
3407
  target_mode = nil
3337
3408
  end
3338
3409
 
@@ -3342,37 +3413,28 @@ Ignoring URL #{target} where link or anchor > 255 characters since it exceeds Ex
3342
3413
  EOS
3343
3414
  end
3344
3415
 
3345
- @drawing_links << [rel_type, target, target_mode] if target && !@drawing_rels[url]
3346
- drawing.url_rel_index = drawing_rel_index(url)
3416
+ @drawing_links << ['/hyperlink', target, target_mode] if target && !@drawing_rels[image.url]
3417
+ drawing.url_rel_index = drawing_rel_index(image.url)
3347
3418
  end
3348
3419
 
3349
3420
  @drawing_links << ['/image', "../media/image#{image_id}.#{image_type}"] unless @drawing_rels[md5]
3350
3421
 
3351
3422
  drawing.rel_index = drawing_rel_index(md5)
3423
+
3424
+ image_ref_id
3352
3425
  end
3353
- public :prepare_image
3354
3426
 
3355
- def prepare_header_image(image_id, width, height, name, image_type, position, x_dpi, y_dpi, md5)
3427
+ def prepare_header_image(image_id, image_property)
3356
3428
  # Strip the extension from the filename.
3357
- body = name.dup
3429
+ body = image_property.name.dup
3358
3430
  body[/\.[^.]+$/, 0] = ''
3431
+ image_property.body = body
3359
3432
 
3360
- @vml_drawing_links << ['/image', "../media/image#{image_id}.#{image_type}"] unless @vml_drawing_rels[md5]
3433
+ @vml_drawing_links << ['/image', "../media/image#{image_id}.#{image_property.type}"] unless @vml_drawing_rels[image_property.md5]
3361
3434
 
3362
- ref_id = get_vml_drawing_rel_index(md5)
3363
- @header_images_array << [width, height, body, position, x_dpi, y_dpi, ref_id]
3435
+ image_property.ref_id = get_vml_drawing_rel_index(image_property.md5)
3436
+ @header_images_array << image_property
3364
3437
  end
3365
- public :prepare_header_image
3366
-
3367
- #
3368
- # Set the background image for the worksheet.
3369
- #
3370
- def set_background(image)
3371
- raise "Couldn't locate #{image}: $!" unless File.exist?(image)
3372
-
3373
- @background_image = image
3374
- end
3375
- public :set_background
3376
3438
 
3377
3439
  #
3378
3440
  # Set up an image without a drawing object for the background image.
@@ -3381,78 +3443,25 @@ EOS
3381
3443
  @external_background_links <<
3382
3444
  ['/image', "../media/image#{image_id}.#{image_type}"]
3383
3445
  end
3384
- public :prepare_background
3385
3446
 
3386
- #
3387
- # :call-seq:
3388
- # insert_shape(row, col, shape [ , x, y, x_scale, y_scale ])
3389
- #
3390
- # Insert a shape into the worksheet.
3391
- #
3392
- def insert_shape(
3393
- row_start, column_start, shape = nil, x_offset = nil, y_offset = nil,
3394
- x_scale = nil, y_scale = nil, anchor = nil
3395
- )
3396
- # Check for a cell reference in A1 notation and substitute row and column.
3397
- if (row_col_array = row_col_notation(row_start))
3398
- _row_start, _column_start = row_col_array
3399
- _shape = column_start
3400
- _x_offset = shape
3401
- _y_offset = x_offset
3402
- _x_scale = y_offset
3403
- _y_scale = x_scale
3404
- _anchor = y_scale
3405
- else
3406
- _row_start = row_start
3407
- _column_start = column_start
3408
- _shape = shape
3409
- _x_offset = x_offset
3410
- _y_offset = y_offset
3411
- _x_scale = x_scale
3412
- _y_scale = y_scale
3413
- _anchor = anchor
3414
- end
3415
- raise "Insufficient arguments in insert_shape()" if [_row_start, _column_start, _shape].include?(nil)
3447
+ def prepare_background_image(background_ids, image_ref_id)
3448
+ unless background_image.nil?
3449
+ @workbook.store_image_types(background_image.type)
3416
3450
 
3417
- _shape.set_position(
3418
- _row_start, _column_start, _x_offset, _y_offset,
3419
- _x_scale, _y_scale, _anchor
3420
- )
3421
- # Assign a shape ID.
3422
- while true
3423
- id = _shape.id || 0
3424
- used = @shape_hash[id]
3425
-
3426
- # Test if shape ID is already used. Otherwise assign a new one.
3427
- if !used && id != 0
3428
- break
3451
+ if background_ids[background_image.md5]
3452
+ ref_id = background_ids[background_image.md5]
3429
3453
  else
3430
- @last_shape_id += 1
3431
- _shape.id = @last_shape_id
3454
+ image_ref_id += 1
3455
+ ref_id = image_ref_id
3456
+ background_ids[background_image.md5] = ref_id
3457
+ @workbook.images << background_image
3432
3458
  end
3433
- end
3434
-
3435
- # Allow lookup of entry into shape array by shape ID.
3436
- @shape_hash[_shape.id] = _shape.element = @shapes.size
3437
3459
 
3438
- insert = if ptrue?(_shape.stencil)
3439
- # Insert a copy of the shape, not a reference so that the shape is
3440
- # used as a stencil. Previously stamped copies don't get modified
3441
- # if the stencil is modified.
3442
- _shape.dup
3443
- else
3444
- _shape
3445
- end
3446
-
3447
- # For connectors change x/y coords based on location of connected shapes.
3448
- insert.auto_locate_connectors(@shapes, @shape_hash)
3460
+ prepare_background(ref_id, background_image.type)
3461
+ end
3449
3462
 
3450
- # Insert a link to the shape on the list of shapes. Connection to
3451
- # the parent shape is maintained.
3452
- @shapes << insert
3453
- insert
3463
+ image_ref_id
3454
3464
  end
3455
- public :insert_shape
3456
3465
 
3457
3466
  #
3458
3467
  # Set up drawing shapes
@@ -3463,7 +3472,7 @@ EOS
3463
3472
  # Create a Drawing object to use with worksheet unless one already exists.
3464
3473
  unless drawings?
3465
3474
  @drawings = Drawings.new
3466
- @drawings.embedded = 1
3475
+ @drawings.embedded = true
3467
3476
  @external_drawing_links << ['/drawing', "../drawings/drawing#{drawing_id}.xml"]
3468
3477
  @has_shapes = true
3469
3478
  end
@@ -3479,70 +3488,6 @@ EOS
3479
3488
  )
3480
3489
  drawings.add_drawing_object(drawing)
3481
3490
  end
3482
- public :prepare_shape
3483
-
3484
- #
3485
- # This method handles the parameters passed to insert_button as well as
3486
- # calculating the button object position and vertices.
3487
- #
3488
- def button_params(row, col, params)
3489
- button = Writexlsx::Package::Button.new
3490
-
3491
- button_number = 1 + @buttons_array.size
3492
-
3493
- # Set the button caption.
3494
- caption = params[:caption] || "Button #{button_number}"
3495
-
3496
- button.font = { _caption: caption }
3497
-
3498
- # Set the macro name.
3499
- button.macro = if params[:macro]
3500
- "[0]!#{params[:macro]}"
3501
- else
3502
- "[0]!Button#{button_number}_Click"
3503
- end
3504
-
3505
- # Set the alt text for the button.
3506
- button.description = params[:description]
3507
-
3508
- # Ensure that a width and height have been set.
3509
- default_width = @default_col_pixels
3510
- default_height = @default_row_pixels
3511
- params[:width] = default_width unless params[:width]
3512
- params[:height] = default_height unless params[:height]
3513
-
3514
- # Set the x/y offsets.
3515
- params[:x_offset] = 0 unless params[:x_offset]
3516
- params[:y_offset] = 0 unless params[:y_offset]
3517
-
3518
- # Scale the size of the button box if required.
3519
- params[:width] = params[:width] * params[:x_scale] if params[:x_scale]
3520
- params[:height] = params[:height] * params[:y_scale] if params[:y_scale]
3521
-
3522
- # Round the dimensions to the nearest pixel.
3523
- params[:width] = (0.5 + params[:width]).to_i
3524
- params[:height] = (0.5 + params[:height]).to_i
3525
-
3526
- params[:start_row] = row
3527
- params[:start_col] = col
3528
-
3529
- # Calculate the positions of button object.
3530
- vertices = position_object_pixels(
3531
- params[:start_col],
3532
- params[:start_row],
3533
- params[:x_offset],
3534
- params[:y_offset],
3535
- params[:width],
3536
- params[:height]
3537
- )
3538
-
3539
- # Add the width and height for VML.
3540
- vertices << [params[:width], params[:height]]
3541
-
3542
- button.vertices = vertices
3543
-
3544
- button
3545
- end
3546
3491
 
3547
3492
  #
3548
3493
  # Hash a worksheet password. Based on the algorithm in ECMA-376-4:2016,
@@ -3868,7 +3813,7 @@ EOS
3868
3813
  end
3869
3814
  else
3870
3815
  # Row attributes only.
3871
- write_empty_row(row_num, span, *(@set_rows[row_num]))
3816
+ write_empty_row(row_num, span, *@set_rows[row_num])
3872
3817
  end
3873
3818
  end
3874
3819
  end
@@ -4374,7 +4319,7 @@ EOS
4374
4319
  return unless outline_changed?
4375
4320
 
4376
4321
  attributes = []
4377
- attributes << ["applyStyles", 1] if @outline_style != 0
4322
+ attributes << ["applyStyles", 1] if @outline_style
4378
4323
  attributes << ["summaryBelow", 0] if @outline_below == 0
4379
4324
  attributes << ["summaryRight", 0] if @outline_right == 0
4380
4325
  attributes << ["showOutlineSymbols", 0] if @outline_on == 0
@@ -4471,7 +4416,7 @@ EOS
4471
4416
  # Write the <picture> element.
4472
4417
  #
4473
4418
  def write_picture
4474
- return unless ptrue?(@background_image)
4419
+ return unless @background_image
4475
4420
 
4476
4421
  # Increment the relationship id.
4477
4422
  @rel_count += 1
@@ -4704,7 +4649,7 @@ EOS
4704
4649
  def write_sparklines(sparkline)
4705
4650
  # Write the sparkline elements.
4706
4651
  @writer.tag_elements('x14:sparklines') do
4707
- (0..sparkline[:count] - 1).each do |i|
4652
+ (0..(sparkline[:count] - 1)).each do |i|
4708
4653
  range = sparkline[:ranges][i]
4709
4654
  location = sparkline[:locations][i]
4710
4655
 
@@ -4984,5 +4929,54 @@ EOS
4984
4929
 
4985
4930
  @writer.empty_tag('ignoredError', attributes)
4986
4931
  end
4932
+
4933
+ def prepare_header_footer_image(image, header_image_ids, image_ref_id)
4934
+ @workbook.store_image_types(image.type)
4935
+
4936
+ if header_image_ids[image.md5]
4937
+ ref_id = header_image_ids[image.md5]
4938
+ else
4939
+ image_ref_id += 1
4940
+ header_image_ids[image.md5] = ref_id = image_ref_id
4941
+ @workbook.images << image
4942
+ end
4943
+
4944
+ prepare_header_image(ref_id, image)
4945
+
4946
+ image_ref_id
4947
+ end
4948
+
4949
+ def protect_default_settings # :nodoc:
4950
+ {
4951
+ sheet: true,
4952
+ content: false,
4953
+ objects: false,
4954
+ scenarios: false,
4955
+ format_cells: false,
4956
+ format_columns: false,
4957
+ format_rows: false,
4958
+ insert_columns: false,
4959
+ insert_rows: false,
4960
+ insert_hyperlinks: false,
4961
+ delete_columns: false,
4962
+ delete_rows: false,
4963
+ select_locked_cells: true,
4964
+ sort: false,
4965
+ autofilter: false,
4966
+ pivot_tables: false,
4967
+ select_unlocked_cells: true
4968
+ }
4969
+ end
4970
+
4971
+ def expand_formula(formula, function, addition = '')
4972
+ if formula =~ /\b(#{function})/
4973
+ formula.gsub(
4974
+ ::Regexp.last_match(1),
4975
+ "_xlfn#{addition}.#{::Regexp.last_match(1)}"
4976
+ )
4977
+ else
4978
+ formula
4979
+ end
4980
+ end
4987
4981
  end
4988
4982
  end