write_xlsx 1.12.2 → 1.13.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -0
  3. data/Changes +6 -0
  4. data/README.md +1 -1
  5. data/lib/write_xlsx/chartsheet.rb +4 -1
  6. data/lib/write_xlsx/object_positioning.rb +189 -0
  7. data/lib/write_xlsx/package/app.rb +2 -1
  8. data/lib/write_xlsx/page_setup.rb +190 -0
  9. data/lib/write_xlsx/sheets.rb +3 -3
  10. data/lib/write_xlsx/utility.rb +57 -9
  11. data/lib/write_xlsx/version.rb +1 -1
  12. data/lib/write_xlsx/workbook.rb +34 -34
  13. data/lib/write_xlsx/worksheet/asset_manager.rb +60 -0
  14. data/lib/write_xlsx/worksheet/autofilter.rb +388 -0
  15. data/lib/write_xlsx/worksheet/cell_data.rb +8 -5
  16. data/lib/write_xlsx/worksheet/cell_data_manager.rb +47 -0
  17. data/lib/write_xlsx/worksheet/cell_data_store.rb +61 -0
  18. data/lib/write_xlsx/worksheet/columns.rb +199 -0
  19. data/lib/write_xlsx/worksheet/comments_support.rb +61 -0
  20. data/lib/write_xlsx/worksheet/conditional_formats.rb +30 -0
  21. data/lib/write_xlsx/worksheet/data_writing.rb +990 -0
  22. data/lib/write_xlsx/worksheet/drawing_methods.rb +308 -0
  23. data/lib/write_xlsx/worksheet/drawing_preparation.rb +290 -0
  24. data/lib/write_xlsx/worksheet/drawing_relations.rb +76 -0
  25. data/lib/write_xlsx/worksheet/drawing_xml_writer.rb +50 -0
  26. data/lib/write_xlsx/worksheet/formatting.rb +416 -0
  27. data/lib/write_xlsx/worksheet/initialization.rb +146 -0
  28. data/lib/write_xlsx/worksheet/panes.rb +64 -0
  29. data/lib/write_xlsx/worksheet/print_options.rb +72 -0
  30. data/lib/write_xlsx/worksheet/protection.rb +65 -0
  31. data/lib/write_xlsx/worksheet/rich_text_helpers.rb +78 -0
  32. data/lib/write_xlsx/worksheet/row_col_sizing.rb +67 -0
  33. data/lib/write_xlsx/worksheet/rows.rb +84 -0
  34. data/lib/write_xlsx/worksheet/selection.rb +41 -0
  35. data/lib/write_xlsx/worksheet/xml_writer.rb +1241 -0
  36. data/lib/write_xlsx/worksheet.rb +359 -4530
  37. data/lib/write_xlsx/zip_file_utils.rb +1 -1
  38. data/write_xlsx.gemspec +1 -1
  39. metadata +34 -5
  40. data/lib/write_xlsx/worksheet/page_setup.rb +0 -192
@@ -0,0 +1,308 @@
1
+ # -*- coding: utf-8 -*-
2
+ # frozen_string_literal: true
3
+
4
+ module Writexlsx
5
+ class Worksheet
6
+ module DrawingMethods
7
+ ###############################################################################
8
+ #
9
+ # DrawingMethods
10
+ #
11
+ # Provides high-level Worksheet APIs for inserting drawing-related objects
12
+ # such as charts, images, shapes, tables, and sparklines.
13
+ #
14
+ # Responsibilities:
15
+ # - Public insertion entry points used by Worksheet users
16
+ # - Argument normalization and option extraction
17
+ # - Shape insertion workflow and helper utilities
18
+ #
19
+ # This module handles *what gets added to the sheet*.
20
+ # It does not manage relationships or XML output.
21
+ #
22
+ ###############################################################################
23
+ #
24
+ # This method can be used to insert a Chart object into a worksheet.
25
+ # The Chart must be created by the add_chart() Workbook method and
26
+ # it must have the embedded option set.
27
+ #
28
+ def insert_chart(row, col, chart = nil, *options)
29
+ normalized_row, normalized_col, normalized_chart, normalized_options =
30
+ normalize_row_col_args(row, col, chart, options)
31
+ raise WriteXLSXInsufficientArgumentError if [normalized_row, normalized_col, normalized_chart].include?(nil)
32
+
33
+ x_offset, y_offset, x_scale, y_scale, anchor, description, decorative =
34
+ extract_chart_options(normalized_options)
35
+
36
+ raise "Not a Chart object in insert_chart()" unless normalized_chart.is_a?(Chart) || normalized_chart.is_a?(Chartsheet)
37
+ raise "Not a embedded style Chart object in insert_chart()" if normalized_chart.respond_to?(:embedded) && normalized_chart.embedded == 0
38
+
39
+ if normalized_chart.already_inserted? || (normalized_chart.combined && normalized_chart.combined.already_inserted?)
40
+ raise "Chart cannot be inserted in a worksheet more than once"
41
+ else
42
+ normalized_chart.already_inserted = true
43
+ normalized_chart.combined.already_inserted = true if normalized_chart.combined
44
+ end
45
+
46
+ # Use the values set with chart.set_size, if any.
47
+ x_scale = normalized_chart.x_scale if normalized_chart.x_scale != 1
48
+ y_scale = normalized_chart.y_scale if normalized_chart.y_scale != 1
49
+ x_offset = normalized_chart.x_offset if ptrue?(normalized_chart.x_offset)
50
+ y_offset = normalized_chart.y_offset if ptrue?(normalized_chart.y_offset)
51
+
52
+ @assets.add_chart(
53
+ InsertedChart.new(
54
+ normalized_row, normalized_col, normalized_chart,
55
+ x_offset, y_offset, x_scale, y_scale, anchor, description, decorative
56
+ )
57
+ )
58
+ end
59
+
60
+ def insert_image(row, col, image = nil, *options)
61
+ normalized_row, normalized_col, normalized_image, normalized_options =
62
+ normalize_row_col_args(row, col, image, options)
63
+ raise WriteXLSXInsufficientArgumentError if [normalized_row, normalized_col, normalized_image].include?(nil)
64
+
65
+ x_offset, y_offset, x_scale, y_scale,
66
+ anchor, url, tip, description, decorative = extract_image_options(normalized_options)
67
+
68
+ @assets.add_image(
69
+ Image.new(
70
+ normalized_row, normalized_col, normalized_image, x_offset, y_offset,
71
+ x_scale, y_scale, url, tip, anchor, description, decorative
72
+ )
73
+ )
74
+ end
75
+
76
+ def embed_image(row, col, filename, options = nil)
77
+ normalize_row, normalize_col, image, normalize_options = normalize_row_col_args(row, col, filename, options)
78
+
79
+ raise WriteXLSXInsufficientArgumentError if [normalize_row, normalize_col, image].include?(nil)
80
+ raise "Couldn't locate #{image}" unless File.exist?(image)
81
+
82
+ # Check that row and col are valid and store max and min values
83
+ check_dimensions(normalize_row, normalize_col)
84
+ store_row_col_max_min_values(normalize_row, normalize_col)
85
+
86
+ if options
87
+ xf = options[:cell_format]
88
+ url = options[:url]
89
+ tip = options[:tip]
90
+ description = options[:description]
91
+ decorative = options[:decorative]
92
+ else
93
+ xf, url, tip, description, decorative = []
94
+ end
95
+
96
+ # Write the url without writing a string.
97
+ if url
98
+ xf ||= @default_url_format
99
+
100
+ write_url(row, col, url, xf, nil, tip, true)
101
+ end
102
+
103
+ # Get the image properties, mainly for the type and checksum.
104
+ image_property = ImageProperty.new(
105
+ image, description: description, decorative: decorative
106
+ )
107
+ @workbook.store_image_types(image_property.type)
108
+
109
+ # Check for duplicate images.
110
+ image_index = @embedded_image_indexes[image_property.md5]
111
+
112
+ unless ptrue?(image_index)
113
+ @workbook.embedded_images << image_property
114
+
115
+ image_index = @workbook.embedded_images.size
116
+ @embedded_image_indexes[image_property.md5] = image_index
117
+ end
118
+
119
+ # Write the cell placeholder.
120
+ store_data_to_table(EmbedImageCellData.new(image_index, xf), normalize_row, normalize_col)
121
+ @has_embedded_images = true
122
+ end
123
+
124
+ #
125
+ # :call-seq:
126
+ # insert_shape(row, col, shape [ , x, y, x_scale, y_scale ])
127
+ #
128
+ # Insert a shape into the worksheet.
129
+ #
130
+ def insert_shape(
131
+ row_start, column_start, shape = nil, x_offset = nil, y_offset = nil,
132
+ x_scale = nil, y_scale = nil, anchor = nil
133
+ )
134
+ row, col, normalized_shape, normalized_options =
135
+ normalize_shape_args(row_start, column_start, shape, x_offset, y_offset, x_scale, y_scale, anchor)
136
+ raise "Insufficient arguments in insert_shape()" if [row, col, normalized_shape].include?(nil)
137
+
138
+ set_shape_position(normalized_shape, row, col, normalized_options)
139
+ assign_shape_id(normalized_shape)
140
+
141
+ # Allow lookup of entry into shape array by shape ID.
142
+ @shape_hash[normalized_shape.id] = normalized_shape.element = shapes.size
143
+
144
+ inserted_shape = build_inserted_shape(normalized_shape)
145
+
146
+ # For connectors change x/y coords based on location of connected shapes.
147
+ auto_locate_shape_connectors(inserted_shape)
148
+
149
+ # Insert a link to the shape on the list of shapes. Connection to
150
+ # the parent shape is maintained.
151
+ @assets.add_shape(inserted_shape)
152
+ inserted_shape
153
+ end
154
+
155
+ #
156
+ # :call-seq:
157
+ # add_table(row1, col1, row2, col2, properties)
158
+ #
159
+ # Add an Excel table to a worksheet.
160
+ #
161
+ def add_table(*args)
162
+ # Table count is a member of Workbook, global to all Worksheet.
163
+ table = Package::Table.new(self, *args)
164
+ @assets.add_table(table)
165
+ table
166
+ end
167
+
168
+ #
169
+ # :call-seq:
170
+ # add_sparkline(properties)
171
+ #
172
+ # Add sparklines to the worksheet.
173
+ #
174
+ def add_sparkline(param)
175
+ @assets.add_sparkline(Sparkline.new(self, param, quote_sheetname(@name)))
176
+ end
177
+
178
+ private
179
+
180
+ def normalize_row_col_args(row, col, object, options)
181
+ if (row_col_array = row_col_notation(row))
182
+ normalized_row, normalized_col = row_col_array
183
+ normalized_object = col
184
+ normalized_options = [object] + options
185
+ else
186
+ normalized_row = row
187
+ normalized_col = col
188
+ normalized_object = object
189
+ normalized_options = options
190
+ end
191
+
192
+ [normalized_row, normalized_col, normalized_object, normalized_options]
193
+ end
194
+
195
+ def extract_chart_options(options)
196
+ if options.first.instance_of?(Hash)
197
+ params = options.first
198
+ [
199
+ params[:x_offset] || 0,
200
+ params[:y_offset] || 0,
201
+ params[:x_scale] || 1,
202
+ params[:y_scale] || 1,
203
+ params[:object_position] || 1,
204
+ params[:description],
205
+ params[:decorative]
206
+ ]
207
+ else
208
+ x_offset, y_offset, x_scale, y_scale, anchor = options
209
+ [x_offset || 0, y_offset || 0, x_scale || 1, y_scale || 1, anchor || 1, nil, nil]
210
+ end
211
+ end
212
+
213
+ def extract_image_options(options)
214
+ if options.first.instance_of?(Hash)
215
+ params = options.first
216
+ [
217
+ params[:x_offset] || 0,
218
+ params[:y_offset] || 0,
219
+ params[:x_scale] || 1,
220
+ params[:y_scale] || 1,
221
+ params[:object_position] || 2,
222
+ params[:url],
223
+ params[:tip],
224
+ params[:description],
225
+ params[:decorative]
226
+ ]
227
+ else
228
+ x_offset, y_offset, x_scale, y_scale, anchor = options
229
+ [x_offset || 0, y_offset || 0, x_scale || 1, y_scale || 1, anchor || 2, nil, nil, nil, nil]
230
+ end
231
+ end
232
+
233
+ def normalize_shape_args(row_start, column_start, shape,
234
+ x_offset, y_offset, x_scale, y_scale, anchor)
235
+ if (row_col_array = row_col_notation(row_start))
236
+ normalized_row, normalized_col = row_col_array
237
+ normalized_shape = column_start
238
+ normalized_x_offset = shape
239
+ normalized_y_offset = x_offset
240
+ normalized_x_scale = y_offset
241
+ normalized_y_scale = x_scale
242
+ normalized_anchor = y_scale
243
+ else
244
+ normalized_row = row_start
245
+ normalized_col = column_start
246
+ normalized_shape = shape
247
+ normalized_x_offset = x_offset
248
+ normalized_y_offset = y_offset
249
+ normalized_x_scale = x_scale
250
+ normalized_y_scale = y_scale
251
+ normalized_anchor = anchor
252
+ end
253
+
254
+ [
255
+ normalized_row,
256
+ normalized_col,
257
+ normalized_shape,
258
+ {
259
+ x_offset: normalized_x_offset,
260
+ y_offset: normalized_y_offset,
261
+ x_scale: normalized_x_scale,
262
+ y_scale: normalized_y_scale,
263
+ anchor: normalized_anchor
264
+ }
265
+ ]
266
+ end
267
+
268
+ def set_shape_position(shape, row, col, options)
269
+ shape.set_position(
270
+ row, col,
271
+ options[:x_offset],
272
+ options[:y_offset],
273
+ options[:x_scale],
274
+ options[:y_scale],
275
+ options[:anchor]
276
+ )
277
+ end
278
+
279
+ def assign_shape_id(shape)
280
+ loop do
281
+ id = shape.id || 0
282
+ used = @shape_hash[id]
283
+
284
+ if !used && id != 0
285
+ break
286
+ else
287
+ @last_shape_id += 1
288
+ shape.id = @last_shape_id
289
+ end
290
+ end
291
+
292
+ @shape_hash[shape.id] = shape.element = shapes.size
293
+ end
294
+
295
+ def build_inserted_shape(shape)
296
+ if ptrue?(shape.stencil)
297
+ shape.dup
298
+ else
299
+ shape
300
+ end
301
+ end
302
+
303
+ def auto_locate_shape_connectors(shape)
304
+ shape.auto_locate_connectors(shapes, @shape_hash)
305
+ end
306
+ end
307
+ end
308
+ end
@@ -0,0 +1,290 @@
1
+ # -*- coding: utf-8 -*-
2
+ # frozen_string_literal: true
3
+
4
+ module Writexlsx
5
+ class Worksheet
6
+ module DrawingPreparation
7
+ ###############################################################################
8
+ #
9
+ # DrawingPreparation
10
+ #
11
+ # Prepares drawing-related assets for XLSX output.
12
+ #
13
+ # Responsibilities:
14
+ # - Convert inserted charts, images, shapes, and media into Drawing objects
15
+ # - Register drawing assets with the Workbook and Drawings containers
16
+ # - Prepare background, header/footer images, and VML objects
17
+ # - Coordinate drawing setup before XML serialization
18
+ #
19
+ # This module handles *how drawing assets are assembled for output*.
20
+ # Relationship management and XML writing are handled elsewhere.
21
+ #
22
+ ###############################################################################
23
+
24
+ # Drawing preparation
25
+
26
+ def prepare_drawings(drawing_id, chart_ref_id, image_ref_id, image_ids, header_image_ids, background_ids)
27
+ has_drawings = false
28
+
29
+ # Check that some image or drawing needs to be processed.
30
+ unless some_image_or_drawing_to_be_processed?
31
+
32
+ # Don't increase the drawing_id header/footer images.
33
+ unless charts.empty? && images.empty? && shapes.empty?
34
+ drawing_id += 1
35
+ has_drawings = true
36
+ end
37
+
38
+ # Prepare the background images.
39
+ image_ref_id = prepare_background_image(background_ids, image_ref_id)
40
+
41
+ # Prepare the worksheet images.
42
+ images.each do |image|
43
+ image_ref_id = prepare_image(image, drawing_id, image_ids, image_ref_id)
44
+ end
45
+
46
+ # Prepare the worksheet charts.
47
+ charts.each_with_index do |_chart, index|
48
+ chart_ref_id += 1
49
+ prepare_chart(index, chart_ref_id, drawing_id)
50
+ end
51
+
52
+ # Prepare the worksheet shapes.
53
+ shapes.each_with_index do |_shape, index|
54
+ prepare_shape(index, drawing_id)
55
+ end
56
+
57
+ # Prepare the header and footer images.
58
+ [header_images, footer_images].each do |images|
59
+ images.each do |image|
60
+ image_ref_id = prepare_header_footer_image(
61
+ image, header_image_ids, image_ref_id
62
+ )
63
+ end
64
+ end
65
+
66
+ if has_drawings
67
+ @workbook.drawings << drawings
68
+ end
69
+ end
70
+
71
+ [drawing_id, chart_ref_id, image_ref_id]
72
+ end
73
+
74
+ #
75
+ # Set up chart/drawings.
76
+ #
77
+ def prepare_chart(index, chart_id, drawing_id) # :nodoc:
78
+ drawing_type = 1
79
+
80
+ inserted_chart = charts[index]
81
+ inserted_chart.chart.id = chart_id - 1
82
+
83
+ dimensions = position_object_emus(inserted_chart)
84
+
85
+ # Create a Drawing object to use with worksheet unless one already exists.
86
+ drawing = Drawing.new(
87
+ drawing_type, dimensions, 0, 0, nil, inserted_chart.anchor,
88
+ drawing_rel_index, 0, nil, inserted_chart.name,
89
+ inserted_chart.description, inserted_chart.decorative
90
+ )
91
+ if drawings?
92
+ @drawings.add_drawing_object(drawing)
93
+ else
94
+ @drawings = Drawings.new
95
+ @drawings.add_drawing_object(drawing)
96
+ @drawings.embedded = true
97
+
98
+ @external_drawing_links << ['/drawing', "../drawings/drawing#{drawing_id}.xml"]
99
+ end
100
+ @drawing_links << ['/chart', "../charts/chart#{chart_id}.xml"]
101
+ end
102
+
103
+ #
104
+ # Set up image/drawings.
105
+ #
106
+ def prepare_image(image, drawing_id, image_ids, image_ref_id) # :nodoc:
107
+ image_type = image.type
108
+ x_dpi = image.x_dpi || 96
109
+ y_dpi = image.y_dpi || 96
110
+ md5 = image.md5
111
+ drawing_type = 2
112
+
113
+ @workbook.store_image_types(image_type)
114
+
115
+ if image_ids[md5]
116
+ image_id = image_ids[md5]
117
+ else
118
+ image_ref_id += 1
119
+ image_ids[md5] = image_id = image_ref_id
120
+ @workbook.images << image
121
+ end
122
+
123
+ dimensions = position_object_emus(image)
124
+
125
+ # Create a Drawing object to use with worksheet unless one already exists.
126
+ drawing = Drawing.new(
127
+ drawing_type, dimensions, image.width_emus, image.height_emus,
128
+ nil, image.anchor, 0, 0, image.tip, image.name,
129
+ image.description || image.name, image.decorative
130
+ )
131
+ unless drawings?
132
+ @drawings = Drawings.new
133
+ @drawings.embedded = true
134
+
135
+ @external_drawing_links << ['/drawing', "../drawings/drawing#{drawing_id}.xml"]
136
+ end
137
+ @drawings.add_drawing_object(drawing)
138
+
139
+ if image.url
140
+ target_mode = 'External'
141
+ target = escape_url(image.url) if image.url =~ %r{^[fh]tt?ps?://} || image.url =~ /^mailto:/
142
+ if image.url =~ /^external:/
143
+ target = escape_url(image.url.sub(/^external:/, ''))
144
+
145
+ # Additional escape not required in worksheet hyperlinks
146
+ target = target.gsub("#", '%23')
147
+
148
+ # Prefix absolute paths (not relative) with file:///
149
+ target = if target =~ /^\w:/ || target =~ /^\\\\/
150
+ "file:///#{target}"
151
+ else
152
+ target.gsub("\\", '/')
153
+ end
154
+ end
155
+
156
+ if image.url =~ /^internal:/
157
+ target = image.url.sub(/^internal:/, '#')
158
+ target_mode = nil
159
+ end
160
+
161
+ if target.length > 255
162
+ raise <<"EOS"
163
+ Ignoring URL #{target} where link or anchor > 255 characters since it exceeds Excel's limit for URLS. See LIMITATIONS section of the WriteXLSX documentation.
164
+ EOS
165
+ end
166
+
167
+ @drawing_links << ['/hyperlink', target, target_mode] if target && !@drawing_rels[image.url]
168
+ drawing.url_rel_index = drawing_rel_index(image.url)
169
+ end
170
+
171
+ @drawing_links << ['/image', "../media/image#{image_id}.#{image_type}"] unless @drawing_rels[md5]
172
+
173
+ drawing.rel_index = drawing_rel_index(md5)
174
+
175
+ image_ref_id
176
+ end
177
+
178
+ #
179
+ # Set up drawing shapes
180
+ #
181
+ def prepare_shape(index, drawing_id)
182
+ shape = shapes[index]
183
+
184
+ # Create a Drawing object to use with worksheet unless one already exists.
185
+ unless drawings?
186
+ @drawings = Drawings.new
187
+ @drawings.embedded = true
188
+ @external_drawing_links << ['/drawing', "../drawings/drawing#{drawing_id}.xml"]
189
+ @has_shapes = true
190
+ end
191
+
192
+ # Validate the he shape against various rules.
193
+ shape.validate(index)
194
+ shape.calc_position_emus(self)
195
+
196
+ drawing_type = 3
197
+ drawing = Drawing.new(
198
+ drawing_type, shape.dimensions, shape.width_emu, shape.height_emu,
199
+ shape, shape.anchor, drawing_rel_index, 0, shape.name, nil, 0
200
+ )
201
+ drawings.add_drawing_object(drawing)
202
+ end
203
+
204
+ # Image preparation
205
+
206
+ #
207
+ # Set up an image without a drawing object for the background image.
208
+ #
209
+ def prepare_background(image_id, image_type)
210
+ @external_background_links <<
211
+ ['/image', "../media/image#{image_id}.#{image_type}"]
212
+ end
213
+
214
+ def prepare_background_image(background_ids, image_ref_id)
215
+ unless background_image.nil?
216
+ @workbook.store_image_types(background_image.type)
217
+
218
+ if background_ids[background_image.md5]
219
+ ref_id = background_ids[background_image.md5]
220
+ else
221
+ image_ref_id += 1
222
+ ref_id = image_ref_id
223
+ background_ids[background_image.md5] = ref_id
224
+ @workbook.images << background_image
225
+ end
226
+
227
+ prepare_background(ref_id, background_image.type)
228
+ end
229
+
230
+ image_ref_id
231
+ end
232
+
233
+ def prepare_header_image(image_id, image_property)
234
+ # Strip the extension from the filename.
235
+ body = image_property.name.dup
236
+ body[/\.[^.]+$/, 0] = ''
237
+ image_property.body = body
238
+
239
+ @vml_drawing_links << ['/image', "../media/image#{image_id}.#{image_property.type}"] unless @vml_drawing_rels[image_property.md5]
240
+
241
+ image_property.ref_id = get_vml_drawing_rel_index(image_property.md5)
242
+ @header_images_array << image_property
243
+ end
244
+
245
+ def prepare_header_footer_image(image, header_image_ids, image_ref_id)
246
+ @workbook.store_image_types(image.type)
247
+
248
+ if header_image_ids[image.md5]
249
+ ref_id = header_image_ids[image.md5]
250
+ else
251
+ image_ref_id += 1
252
+ header_image_ids[image.md5] = ref_id = image_ref_id
253
+ @workbook.images << image
254
+ end
255
+
256
+ prepare_header_image(ref_id, image)
257
+
258
+ image_ref_id
259
+ end
260
+
261
+ # VML preparation
262
+
263
+ #
264
+ # Turn the HoH that stores the comments into an array for easier handling
265
+ # and set the external links for comments and buttons.
266
+ #
267
+ def prepare_vml_objects(vml_data_id, vml_shape_id, vml_drawing_id, comment_id)
268
+ set_external_vml_links(vml_drawing_id)
269
+ set_external_comment_links(comment_id) if has_comments?
270
+
271
+ # The VML o:idmap data id contains a comma separated range when there is
272
+ # more than one 1024 block of comments, like this: data="1,2".
273
+ data = vml_data_id.to_s
274
+ (1..num_comments_block).each do |i|
275
+ data += ",#{vml_data_id + i}"
276
+ end
277
+ @vml_data_id = data
278
+ @vml_shape_id = vml_shape_id
279
+ end
280
+
281
+ #
282
+ # Setup external linkage for VML header/footer images.
283
+ #
284
+ def prepare_header_vml_objects(vml_header_id, vml_drawing_id)
285
+ @vml_header_id = vml_header_id
286
+ @external_vml_links << ['/vmlDrawing', "../drawings/vmlDrawing#{vml_drawing_id}.vml"]
287
+ end
288
+ end
289
+ end
290
+ end
@@ -0,0 +1,76 @@
1
+ # -*- coding: utf-8 -*-
2
+ # frozen_string_literal: true
3
+
4
+ module Writexlsx
5
+ class Worksheet
6
+ module DrawingRelations
7
+ ###############################################################################
8
+ #
9
+ # DrawingRelations
10
+ #
11
+ # Manages drawing relationships and external linkage information.
12
+ #
13
+ # Responsibilities:
14
+ # - Relationship ID allocation for drawings and VML objects
15
+ # - Tracking drawing and VML relationship mappings
16
+ # - Managing external links for drawings, comments, tables, and media
17
+ # - Providing relationship data for rels file generation
18
+ #
19
+ # This module handles *how drawing resources are linked together*.
20
+ # It does not prepare drawing objects or write XML output.
21
+ #
22
+ ###############################################################################
23
+ #
24
+ # Get the index used to address a drawing rel link.
25
+ #
26
+ def drawing_rel_index(target = nil)
27
+ if !target
28
+ # Undefined values for drawings like charts will always be unique.
29
+ @drawing_rels_id += 1
30
+ elsif ptrue?(@drawing_rels[target])
31
+ @drawing_rels[target]
32
+ else
33
+ @drawing_rels_id += 1
34
+ @drawing_rels[target] = @drawing_rels_id
35
+ end
36
+ end
37
+
38
+ #
39
+ # Get the index used to address a vml_drawing rel link.
40
+ #
41
+ def get_vml_drawing_rel_index(target)
42
+ if @vml_drawing_rels[target]
43
+ @vml_drawing_rels[target]
44
+ else
45
+ @vml_drawing_rels_id += 1
46
+ @vml_drawing_rels[target] = @vml_drawing_rels_id
47
+ end
48
+ end
49
+
50
+ def set_external_vml_links(vml_drawing_id) # :nodoc:
51
+ @external_vml_links <<
52
+ ['/vmlDrawing', "../drawings/vmlDrawing#{vml_drawing_id}.vml"]
53
+ end
54
+
55
+ def set_external_comment_links(comment_id) # :nodoc:
56
+ @external_comment_links <<
57
+ ['/comments', "../comments#{comment_id}.xml"]
58
+ end
59
+
60
+ def external_links
61
+ [
62
+ @external_hyper_links,
63
+ @external_drawing_links,
64
+ @external_vml_links,
65
+ @external_background_links,
66
+ @external_table_links,
67
+ @external_comment_links
68
+ ].reject(&:empty?)
69
+ end
70
+
71
+ def drawing_links
72
+ [@drawing_links]
73
+ end
74
+ end
75
+ end
76
+ end