xlsxrb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.devcontainer/Dockerfile +64 -0
  3. data/.devcontainer/devcontainer.json +17 -0
  4. data/CHANGELOG.md +12 -0
  5. data/CODE_OF_CONDUCT.md +10 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +384 -0
  8. data/Rakefile +290 -0
  9. data/benchmark.rb +390 -0
  10. data/docs/ARCHITECTURE.md +488 -0
  11. data/docs/SPEC_SOURCES.md +42 -0
  12. data/docs/visual/VisualGallery.md +4684 -0
  13. data/docs/wasm/wasm_doc_helper.css +189 -0
  14. data/docs/wasm/wasm_doc_helper.js +320 -0
  15. data/lib/xlsxrb/elements/cell.rb +77 -0
  16. data/lib/xlsxrb/elements/column.rb +28 -0
  17. data/lib/xlsxrb/elements/row.rb +47 -0
  18. data/lib/xlsxrb/elements/types.rb +36 -0
  19. data/lib/xlsxrb/elements/workbook.rb +47 -0
  20. data/lib/xlsxrb/elements/worksheet.rb +50 -0
  21. data/lib/xlsxrb/elements.rb +15 -0
  22. data/lib/xlsxrb/ooxml/reader.rb +8048 -0
  23. data/lib/xlsxrb/ooxml/shared_strings_parser.rb +75 -0
  24. data/lib/xlsxrb/ooxml/styles_parser.rb +194 -0
  25. data/lib/xlsxrb/ooxml/utils.rb +122 -0
  26. data/lib/xlsxrb/ooxml/workbook_parser.rb +78 -0
  27. data/lib/xlsxrb/ooxml/workbook_writer.rb +1000 -0
  28. data/lib/xlsxrb/ooxml/worksheet_parser.rb +455 -0
  29. data/lib/xlsxrb/ooxml/worksheet_writer.rb +1008 -0
  30. data/lib/xlsxrb/ooxml/writer.rb +5450 -0
  31. data/lib/xlsxrb/ooxml/xml_builder.rb +113 -0
  32. data/lib/xlsxrb/ooxml/xml_parser.rb +68 -0
  33. data/lib/xlsxrb/ooxml/zip_generator.rb +162 -0
  34. data/lib/xlsxrb/ooxml/zip_reader.rb +158 -0
  35. data/lib/xlsxrb/ooxml/zip_writer.rb +213 -0
  36. data/lib/xlsxrb/ooxml.rb +22 -0
  37. data/lib/xlsxrb/style_builder.rb +241 -0
  38. data/lib/xlsxrb/version.rb +5 -0
  39. data/lib/xlsxrb.rb +1427 -0
  40. data/measure_memory.rb +42 -0
  41. data/sig/xlsxrb.rbs +23 -0
  42. data/vendor/sdk_runner/Program.cs +91 -0
  43. data/vendor/sdk_runner/sdk_runner.csproj +13 -0
  44. metadata +144 -0
@@ -0,0 +1,4684 @@
1
+ # Visual Examples Gallery
2
+
3
+ This gallery showcases `xlsxrb` DSL usage side-by-side with the visual rendering in LibreOffice Calc.
4
+
5
+ [◄ Back to README](../../README.md)
6
+
7
+ ## Capability Overview
8
+
9
+ <table>
10
+ <thead>
11
+ <tr>
12
+ <th align="left">Feature</th>
13
+ <th align="center">Visual Preview</th>
14
+ <th align="center">Download XLSX</th>
15
+ <th align="center">Link to Detail</th>
16
+ </tr>
17
+ </thead>
18
+ <tbody>
19
+ <tr>
20
+ <td><strong>Align Horizontal</strong></td>
21
+ <td align="center"><img src="../../test/visual/baselines/align_horizontal/page-1.png" width="160" alt="Align Horizontal"/></td>
22
+ <td align="center"><a href="files/align_horizontal.xlsx">Download</a></td>
23
+ <td align="center"><a href="#align-horizontal">View Code &amp; Detail</a></td>
24
+ </tr>
25
+ <tr>
26
+ <td><strong>Align Horizontal Fill</strong></td>
27
+ <td align="center"><img src="../../test/visual/baselines/align_horizontal_fill/page-1.png" width="160" alt="Align Horizontal Fill"/></td>
28
+ <td align="center"><a href="files/align_horizontal_fill.xlsx">Download</a></td>
29
+ <td align="center"><a href="#align-horizontal-fill">View Code &amp; Detail</a></td>
30
+ </tr>
31
+ <tr>
32
+ <td><strong>Align Horizontal Justify</strong></td>
33
+ <td align="center"><img src="../../test/visual/baselines/align_horizontal_justify/page-1.png" width="160" alt="Align Horizontal Justify"/></td>
34
+ <td align="center"><a href="files/align_horizontal_justify.xlsx">Download</a></td>
35
+ <td align="center"><a href="#align-horizontal-justify">View Code &amp; Detail</a></td>
36
+ </tr>
37
+ <tr>
38
+ <td><strong>Align Indent</strong></td>
39
+ <td align="center"><img src="../../test/visual/baselines/align_indent/page-1.png" width="160" alt="Align Indent"/></td>
40
+ <td align="center"><a href="files/align_indent.xlsx">Download</a></td>
41
+ <td align="center"><a href="#align-indent">View Code &amp; Detail</a></td>
42
+ </tr>
43
+ <tr>
44
+ <td><strong>Align Text Rotation</strong></td>
45
+ <td align="center"><img src="../../test/visual/baselines/align_text_rotation/page-1.png" width="160" alt="Align Text Rotation"/></td>
46
+ <td align="center"><a href="files/align_text_rotation.xlsx">Download</a></td>
47
+ <td align="center"><a href="#align-text-rotation">View Code &amp; Detail</a></td>
48
+ </tr>
49
+ <tr>
50
+ <td><strong>Align Text Wrap</strong></td>
51
+ <td align="center"><img src="../../test/visual/baselines/align_text_wrap/page-1.png" width="160" alt="Align Text Wrap"/></td>
52
+ <td align="center"><a href="files/align_text_wrap.xlsx">Download</a></td>
53
+ <td align="center"><a href="#align-text-wrap">View Code &amp; Detail</a></td>
54
+ </tr>
55
+ <tr>
56
+ <td><strong>Align Vertical</strong></td>
57
+ <td align="center"><img src="../../test/visual/baselines/align_vertical/page-1.png" width="160" alt="Align Vertical"/></td>
58
+ <td align="center"><a href="files/align_vertical.xlsx">Download</a></td>
59
+ <td align="center"><a href="#align-vertical">View Code &amp; Detail</a></td>
60
+ </tr>
61
+ <tr>
62
+ <td><strong>Basic Data</strong></td>
63
+ <td align="center"><img src="../../test/visual/baselines/basic_data/page-1.png" width="160" alt="Basic Data"/></td>
64
+ <td align="center"><a href="files/basic_data.xlsx">Download</a></td>
65
+ <td align="center"><a href="#basic-data">View Code &amp; Detail</a></td>
66
+ </tr>
67
+ <tr>
68
+ <td><strong>Borders</strong></td>
69
+ <td align="center"><img src="../../test/visual/baselines/borders/page-1.png" width="160" alt="Borders"/></td>
70
+ <td align="center"><a href="files/borders.xlsx">Download</a></td>
71
+ <td align="center"><a href="#borders">View Code &amp; Detail</a></td>
72
+ </tr>
73
+ <tr>
74
+ <td><strong>Cell Booleans</strong></td>
75
+ <td align="center"><img src="../../test/visual/baselines/cell_booleans/page-1.png" width="160" alt="Cell Booleans"/></td>
76
+ <td align="center"><a href="files/cell_booleans.xlsx">Download</a></td>
77
+ <td align="center"><a href="#cell-booleans">View Code &amp; Detail</a></td>
78
+ </tr>
79
+ <tr>
80
+ <td><strong>Cell Dates</strong></td>
81
+ <td align="center"><img src="../../test/visual/baselines/cell_dates/page-1.png" width="160" alt="Cell Dates"/></td>
82
+ <td align="center"><a href="files/cell_dates.xlsx">Download</a></td>
83
+ <td align="center"><a href="#cell-dates">View Code &amp; Detail</a></td>
84
+ </tr>
85
+ <tr>
86
+ <td><strong>Cell Formulas</strong></td>
87
+ <td align="center"><img src="../../test/visual/baselines/cell_formulas/page-1.png" width="160" alt="Cell Formulas"/></td>
88
+ <td align="center"><a href="files/cell_formulas.xlsx">Download</a></td>
89
+ <td align="center"><a href="#cell-formulas">View Code &amp; Detail</a></td>
90
+ </tr>
91
+ <tr>
92
+ <td><strong>Cell Num Currency Jpy</strong></td>
93
+ <td align="center"><img src="../../test/visual/baselines/cell_num_currency_jpy/page-1.png" width="160" alt="Cell Num Currency Jpy"/></td>
94
+ <td align="center"><a href="files/cell_num_currency_jpy.xlsx">Download</a></td>
95
+ <td align="center"><a href="#cell-num-currency-jpy">View Code &amp; Detail</a></td>
96
+ </tr>
97
+ <tr>
98
+ <td><strong>Cell Num Custom Colors</strong></td>
99
+ <td align="center"><img src="../../test/visual/baselines/cell_num_custom_colors/page-1.png" width="160" alt="Cell Num Custom Colors"/></td>
100
+ <td align="center"><a href="files/cell_num_custom_colors.xlsx">Download</a></td>
101
+ <td align="center"><a href="#cell-num-custom-colors">View Code &amp; Detail</a></td>
102
+ </tr>
103
+ <tr>
104
+ <td><strong>Cell Num Fractions</strong></td>
105
+ <td align="center"><img src="../../test/visual/baselines/cell_num_fractions/page-1.png" width="160" alt="Cell Num Fractions"/></td>
106
+ <td align="center"><a href="files/cell_num_fractions.xlsx">Download</a></td>
107
+ <td align="center"><a href="#cell-num-fractions">View Code &amp; Detail</a></td>
108
+ </tr>
109
+ <tr>
110
+ <td><strong>Cell Num Percent Decimals</strong></td>
111
+ <td align="center"><img src="../../test/visual/baselines/cell_num_percent_decimals/page-1.png" width="160" alt="Cell Num Percent Decimals"/></td>
112
+ <td align="center"><a href="files/cell_num_percent_decimals.xlsx">Download</a></td>
113
+ <td align="center"><a href="#cell-num-percent-decimals">View Code &amp; Detail</a></td>
114
+ </tr>
115
+ <tr>
116
+ <td><strong>Cell Num Scientific</strong></td>
117
+ <td align="center"><img src="../../test/visual/baselines/cell_num_scientific/page-1.png" width="160" alt="Cell Num Scientific"/></td>
118
+ <td align="center"><a href="files/cell_num_scientific.xlsx">Download</a></td>
119
+ <td align="center"><a href="#cell-num-scientific">View Code &amp; Detail</a></td>
120
+ </tr>
121
+ <tr>
122
+ <td><strong>Cell Numbers</strong></td>
123
+ <td align="center"><img src="../../test/visual/baselines/cell_numbers/page-1.png" width="160" alt="Cell Numbers"/></td>
124
+ <td align="center"><a href="files/cell_numbers.xlsx">Download</a></td>
125
+ <td align="center"><a href="#cell-numbers">View Code &amp; Detail</a></td>
126
+ </tr>
127
+ <tr>
128
+ <td><strong>Cell Rich Text</strong></td>
129
+ <td align="center"><img src="../../test/visual/baselines/cell_rich_text/page-1.png" width="160" alt="Cell Rich Text"/></td>
130
+ <td align="center"><a href="files/cell_rich_text.xlsx">Download</a></td>
131
+ <td align="center"><a href="#cell-rich-text">View Code &amp; Detail</a></td>
132
+ </tr>
133
+ <tr>
134
+ <td><strong>Cell Times</strong></td>
135
+ <td align="center"><img src="../../test/visual/baselines/cell_times/page-1.png" width="160" alt="Cell Times"/></td>
136
+ <td align="center"><a href="files/cell_times.xlsx">Download</a></td>
137
+ <td align="center"><a href="#cell-times">View Code &amp; Detail</a></td>
138
+ </tr>
139
+ <tr>
140
+ <td><strong>Cf Begins With</strong></td>
141
+ <td align="center"><img src="../../test/visual/baselines/cf_begins_with/page-1.png" width="160" alt="Cf Begins With"/></td>
142
+ <td align="center"><a href="files/cf_begins_with.xlsx">Download</a></td>
143
+ <td align="center"><a href="#cf-begins-with">View Code &amp; Detail</a></td>
144
+ </tr>
145
+ <tr>
146
+ <td><strong>Cf Cell Between</strong></td>
147
+ <td align="center"><img src="../../test/visual/baselines/cf_cell_between/page-1.png" width="160" alt="Cf Cell Between"/></td>
148
+ <td align="center"><a href="files/cf_cell_between.xlsx">Download</a></td>
149
+ <td align="center"><a href="#cf-cell-between">View Code &amp; Detail</a></td>
150
+ </tr>
151
+ <tr>
152
+ <td><strong>Cf Cell Equal To</strong></td>
153
+ <td align="center"><img src="../../test/visual/baselines/cf_cell_equal_to/page-1.png" width="160" alt="Cf Cell Equal To"/></td>
154
+ <td align="center"><a href="files/cf_cell_equal_to.xlsx">Download</a></td>
155
+ <td align="center"><a href="#cf-cell-equal-to">View Code &amp; Detail</a></td>
156
+ </tr>
157
+ <tr>
158
+ <td><strong>Cf Cell Greater Equal</strong></td>
159
+ <td align="center"><img src="../../test/visual/baselines/cf_cell_greater_equal/page-1.png" width="160" alt="Cf Cell Greater Equal"/></td>
160
+ <td align="center"><a href="files/cf_cell_greater_equal.xlsx">Download</a></td>
161
+ <td align="center"><a href="#cf-cell-greater-equal">View Code &amp; Detail</a></td>
162
+ </tr>
163
+ <tr>
164
+ <td><strong>Cf Cell Greater Than</strong></td>
165
+ <td align="center"><img src="../../test/visual/baselines/cf_cell_greater_than/page-1.png" width="160" alt="Cf Cell Greater Than"/></td>
166
+ <td align="center"><a href="files/cf_cell_greater_than.xlsx">Download</a></td>
167
+ <td align="center"><a href="#cf-cell-greater-than">View Code &amp; Detail</a></td>
168
+ </tr>
169
+ <tr>
170
+ <td><strong>Cf Cell Less Than</strong></td>
171
+ <td align="center"><img src="../../test/visual/baselines/cf_cell_less_than/page-1.png" width="160" alt="Cf Cell Less Than"/></td>
172
+ <td align="center"><a href="files/cf_cell_less_than.xlsx">Download</a></td>
173
+ <td align="center"><a href="#cf-cell-less-than">View Code &amp; Detail</a></td>
174
+ </tr>
175
+ <tr>
176
+ <td><strong>Cf Color Scale</strong></td>
177
+ <td align="center"><img src="../../test/visual/baselines/cf_color_scale/page-1.png" width="160" alt="Cf Color Scale"/></td>
178
+ <td align="center"><a href="files/cf_color_scale.xlsx">Download</a></td>
179
+ <td align="center"><a href="#cf-color-scale">View Code &amp; Detail</a></td>
180
+ </tr>
181
+ <tr>
182
+ <td><strong>Cf Contains Text</strong></td>
183
+ <td align="center"><img src="../../test/visual/baselines/cf_contains_text/page-1.png" width="160" alt="Cf Contains Text"/></td>
184
+ <td align="center"><a href="files/cf_contains_text.xlsx">Download</a></td>
185
+ <td align="center"><a href="#cf-contains-text">View Code &amp; Detail</a></td>
186
+ </tr>
187
+ <tr>
188
+ <td><strong>Cf Data Bar</strong></td>
189
+ <td align="center"><img src="../../test/visual/baselines/cf_data_bar/page-1.png" width="160" alt="Cf Data Bar"/></td>
190
+ <td align="center"><a href="files/cf_data_bar.xlsx">Download</a></td>
191
+ <td align="center"><a href="#cf-data-bar">View Code &amp; Detail</a></td>
192
+ </tr>
193
+ <tr>
194
+ <td><strong>Cf Ends With</strong></td>
195
+ <td align="center"><img src="../../test/visual/baselines/cf_ends_with/page-1.png" width="160" alt="Cf Ends With"/></td>
196
+ <td align="center"><a href="files/cf_ends_with.xlsx">Download</a></td>
197
+ <td align="center"><a href="#cf-ends-with">View Code &amp; Detail</a></td>
198
+ </tr>
199
+ <tr>
200
+ <td><strong>Cf Expression Formula</strong></td>
201
+ <td align="center"><img src="../../test/visual/baselines/cf_expression_formula/page-1.png" width="160" alt="Cf Expression Formula"/></td>
202
+ <td align="center"><a href="files/cf_expression_formula.xlsx">Download</a></td>
203
+ <td align="center"><a href="#cf-expression-formula">View Code &amp; Detail</a></td>
204
+ </tr>
205
+ <tr>
206
+ <td><strong>Cf Icon Set</strong></td>
207
+ <td align="center"><img src="../../test/visual/baselines/cf_icon_set/page-1.png" width="160" alt="Cf Icon Set"/></td>
208
+ <td align="center"><a href="files/cf_icon_set.xlsx">Download</a></td>
209
+ <td align="center"><a href="#cf-icon-set">View Code &amp; Detail</a></td>
210
+ </tr>
211
+ <tr>
212
+ <td><strong>Chart Area</strong></td>
213
+ <td align="center"><img src="../../test/visual/baselines/chart_area/page-1.png" width="160" alt="Chart Area"/></td>
214
+ <td align="center"><a href="files/chart_area.xlsx">Download</a></td>
215
+ <td align="center"><a href="#chart-area">View Code &amp; Detail</a></td>
216
+ </tr>
217
+ <tr>
218
+ <td><strong>Chart Area Stacked</strong></td>
219
+ <td align="center"><img src="../../test/visual/baselines/chart_area_stacked/page-1.png" width="160" alt="Chart Area Stacked"/></td>
220
+ <td align="center"><a href="files/chart_area_stacked.xlsx">Download</a></td>
221
+ <td align="center"><a href="#chart-area-stacked">View Code &amp; Detail</a></td>
222
+ </tr>
223
+ <tr>
224
+ <td><strong>Chart Bar</strong></td>
225
+ <td align="center"><img src="../../test/visual/baselines/chart_bar/page-1.png" width="160" alt="Chart Bar"/></td>
226
+ <td align="center"><a href="files/chart_bar.xlsx">Download</a></td>
227
+ <td align="center"><a href="#chart-bar">View Code &amp; Detail</a></td>
228
+ </tr>
229
+ <tr>
230
+ <td><strong>Chart Bar Percent Stacked</strong></td>
231
+ <td align="center"><img src="../../test/visual/baselines/chart_bar_percent_stacked/page-1.png" width="160" alt="Chart Bar Percent Stacked"/></td>
232
+ <td align="center"><a href="files/chart_bar_percent_stacked.xlsx">Download</a></td>
233
+ <td align="center"><a href="#chart-bar-percent-stacked">View Code &amp; Detail</a></td>
234
+ </tr>
235
+ <tr>
236
+ <td><strong>Chart Bar Stacked</strong></td>
237
+ <td align="center"><img src="../../test/visual/baselines/chart_bar_stacked/page-1.png" width="160" alt="Chart Bar Stacked"/></td>
238
+ <td align="center"><a href="files/chart_bar_stacked.xlsx">Download</a></td>
239
+ <td align="center"><a href="#chart-bar-stacked">View Code &amp; Detail</a></td>
240
+ </tr>
241
+ <tr>
242
+ <td><strong>Chart Doughnut</strong></td>
243
+ <td align="center"><img src="../../test/visual/baselines/chart_doughnut/page-1.png" width="160" alt="Chart Doughnut"/></td>
244
+ <td align="center"><a href="files/chart_doughnut.xlsx">Download</a></td>
245
+ <td align="center"><a href="#chart-doughnut">View Code &amp; Detail</a></td>
246
+ </tr>
247
+ <tr>
248
+ <td><strong>Chart Line</strong></td>
249
+ <td align="center"><img src="../../test/visual/baselines/chart_line/page-1.png" width="160" alt="Chart Line"/></td>
250
+ <td align="center"><a href="files/chart_line.xlsx">Download</a></td>
251
+ <td align="center"><a href="#chart-line">View Code &amp; Detail</a></td>
252
+ </tr>
253
+ <tr>
254
+ <td><strong>Chart Line Stacked</strong></td>
255
+ <td align="center"><img src="../../test/visual/baselines/chart_line_stacked/page-1.png" width="160" alt="Chart Line Stacked"/></td>
256
+ <td align="center"><a href="files/chart_line_stacked.xlsx">Download</a></td>
257
+ <td align="center"><a href="#chart-line-stacked">View Code &amp; Detail</a></td>
258
+ </tr>
259
+ <tr>
260
+ <td><strong>Chart Pie</strong></td>
261
+ <td align="center"><img src="../../test/visual/baselines/chart_pie/page-1.png" width="160" alt="Chart Pie"/></td>
262
+ <td align="center"><a href="files/chart_pie.xlsx">Download</a></td>
263
+ <td align="center"><a href="#chart-pie">View Code &amp; Detail</a></td>
264
+ </tr>
265
+ <tr>
266
+ <td><strong>Chart Radar</strong></td>
267
+ <td align="center"><img src="../../test/visual/baselines/chart_radar/page-1.png" width="160" alt="Chart Radar"/></td>
268
+ <td align="center"><a href="files/chart_radar.xlsx">Download</a></td>
269
+ <td align="center"><a href="#chart-radar">View Code &amp; Detail</a></td>
270
+ </tr>
271
+ <tr>
272
+ <td><strong>Chart Scatter</strong></td>
273
+ <td align="center"><img src="../../test/visual/baselines/chart_scatter/page-1.png" width="160" alt="Chart Scatter"/></td>
274
+ <td align="center"><a href="files/chart_scatter.xlsx">Download</a></td>
275
+ <td align="center"><a href="#chart-scatter">View Code &amp; Detail</a></td>
276
+ </tr>
277
+ <tr>
278
+ <td><strong>Col Grouping</strong></td>
279
+ <td align="center"><img src="../../test/visual/baselines/col_grouping/page-1.png" width="160" alt="Col Grouping"/></td>
280
+ <td align="center"><a href="files/col_grouping.xlsx">Download</a></td>
281
+ <td align="center"><a href="#col-grouping">View Code &amp; Detail</a></td>
282
+ </tr>
283
+ <tr>
284
+ <td><strong>Col Width Tall</strong></td>
285
+ <td align="center"><img src="../../test/visual/baselines/col_width_tall/page-1.png" width="160" alt="Col Width Tall"/></td>
286
+ <td align="center"><a href="files/col_width_tall.xlsx">Download</a></td>
287
+ <td align="center"><a href="#col-width-tall">View Code &amp; Detail</a></td>
288
+ </tr>
289
+ <tr>
290
+ <td><strong>Col Widths</strong></td>
291
+ <td align="center"><img src="../../test/visual/baselines/col_widths/page-1.png" width="160" alt="Col Widths"/></td>
292
+ <td align="center"><a href="files/col_widths.xlsx">Download</a></td>
293
+ <td align="center"><a href="#col-widths">View Code &amp; Detail</a></td>
294
+ </tr>
295
+ <tr>
296
+ <td><strong>Conditional Formatting</strong></td>
297
+ <td align="center"><img src="../../test/visual/baselines/conditional_formatting/page-1.png" width="160" alt="Conditional Formatting"/></td>
298
+ <td align="center"><a href="files/conditional_formatting.xlsx">Download</a></td>
299
+ <td align="center"><a href="#conditional-formatting">View Code &amp; Detail</a></td>
300
+ </tr>
301
+ <tr>
302
+ <td><strong>Embedded Images</strong></td>
303
+ <td align="center"><img src="../../test/visual/baselines/embedded_images/page-1.png" width="160" alt="Embedded Images"/></td>
304
+ <td align="center"><a href="files/embedded_images.xlsx">Download</a></td>
305
+ <td align="center"><a href="#embedded-images">View Code &amp; Detail</a></td>
306
+ </tr>
307
+ <tr>
308
+ <td><strong>Fill Gradients</strong></td>
309
+ <td align="center"><img src="../../test/visual/baselines/fill_gradients/page-1.png" width="160" alt="Fill Gradients"/></td>
310
+ <td align="center"><a href="files/fill_gradients.xlsx">Download</a></td>
311
+ <td align="center"><a href="#fill-gradients">View Code &amp; Detail</a></td>
312
+ </tr>
313
+ <tr>
314
+ <td><strong>Fill Patterns</strong></td>
315
+ <td align="center"><img src="../../test/visual/baselines/fill_patterns/page-1.png" width="160" alt="Fill Patterns"/></td>
316
+ <td align="center"><a href="files/fill_patterns.xlsx">Download</a></td>
317
+ <td align="center"><a href="#fill-patterns">View Code &amp; Detail</a></td>
318
+ </tr>
319
+ <tr>
320
+ <td><strong>Fill Solid Colors</strong></td>
321
+ <td align="center"><img src="../../test/visual/baselines/fill_solid_colors/page-1.png" width="160" alt="Fill Solid Colors"/></td>
322
+ <td align="center"><a href="files/fill_solid_colors.xlsx">Download</a></td>
323
+ <td align="center"><a href="#fill-solid-colors">View Code &amp; Detail</a></td>
324
+ </tr>
325
+ <tr>
326
+ <td><strong>Fonts</strong></td>
327
+ <td align="center"><img src="../../test/visual/baselines/fonts/page-1.png" width="160" alt="Fonts"/></td>
328
+ <td align="center"><a href="files/fonts.xlsx">Download</a></td>
329
+ <td align="center"><a href="#fonts">View Code &amp; Detail</a></td>
330
+ </tr>
331
+ <tr>
332
+ <td><strong>Interactive Autofilter</strong></td>
333
+ <td align="center"><img src="../../test/visual/baselines/interactive_autofilter/page-1.png" width="160" alt="Interactive Autofilter"/></td>
334
+ <td align="center"><a href="files/interactive_autofilter.xlsx">Download</a></td>
335
+ <td align="center"><a href="#interactive-autofilter">View Code &amp; Detail</a></td>
336
+ </tr>
337
+ <tr>
338
+ <td><strong>Interactive Comments</strong></td>
339
+ <td align="center"><img src="../../test/visual/baselines/interactive_comments/page-1.png" width="160" alt="Interactive Comments"/></td>
340
+ <td align="center"><a href="files/interactive_comments.xlsx">Download</a></td>
341
+ <td align="center"><a href="#interactive-comments">View Code &amp; Detail</a></td>
342
+ </tr>
343
+ <tr>
344
+ <td><strong>Interactive Validation Custom</strong></td>
345
+ <td align="center"><img src="../../test/visual/baselines/interactive_validation_custom/page-1.png" width="160" alt="Interactive Validation Custom"/></td>
346
+ <td align="center"><a href="files/interactive_validation_custom.xlsx">Download</a></td>
347
+ <td align="center"><a href="#interactive-validation-custom">View Code &amp; Detail</a></td>
348
+ </tr>
349
+ <tr>
350
+ <td><strong>Interactive Validation Date</strong></td>
351
+ <td align="center"><img src="../../test/visual/baselines/interactive_validation_date/page-1.png" width="160" alt="Interactive Validation Date"/></td>
352
+ <td align="center"><a href="files/interactive_validation_date.xlsx">Download</a></td>
353
+ <td align="center"><a href="#interactive-validation-date">View Code &amp; Detail</a></td>
354
+ </tr>
355
+ <tr>
356
+ <td><strong>Interactive Validation List</strong></td>
357
+ <td align="center"><img src="../../test/visual/baselines/interactive_validation_list/page-1.png" width="160" alt="Interactive Validation List"/></td>
358
+ <td align="center"><a href="files/interactive_validation_list.xlsx">Download</a></td>
359
+ <td align="center"><a href="#interactive-validation-list">View Code &amp; Detail</a></td>
360
+ </tr>
361
+ <tr>
362
+ <td><strong>Interactive Validation Range</strong></td>
363
+ <td align="center"><img src="../../test/visual/baselines/interactive_validation_range/page-1.png" width="160" alt="Interactive Validation Range"/></td>
364
+ <td align="center"><a href="files/interactive_validation_range.xlsx">Download</a></td>
365
+ <td align="center"><a href="#interactive-validation-range">View Code &amp; Detail</a></td>
366
+ </tr>
367
+ <tr>
368
+ <td><strong>Interactive Validation Text Length</strong></td>
369
+ <td align="center"><img src="../../test/visual/baselines/interactive_validation_text_length/page-1.png" width="160" alt="Interactive Validation Text Length"/></td>
370
+ <td align="center"><a href="files/interactive_validation_text_length.xlsx">Download</a></td>
371
+ <td align="center"><a href="#interactive-validation-text-length">View Code &amp; Detail</a></td>
372
+ </tr>
373
+ <tr>
374
+ <td><strong>Interactive Validation Time</strong></td>
375
+ <td align="center"><img src="../../test/visual/baselines/interactive_validation_time/page-1.png" width="160" alt="Interactive Validation Time"/></td>
376
+ <td align="center"><a href="files/interactive_validation_time.xlsx">Download</a></td>
377
+ <td align="center"><a href="#interactive-validation-time">View Code &amp; Detail</a></td>
378
+ </tr>
379
+ <tr>
380
+ <td><strong>Japanese Text</strong></td>
381
+ <td align="center"><img src="../../test/visual/baselines/japanese_text/page-1.png" width="160" alt="Japanese Text"/></td>
382
+ <td align="center"><a href="files/japanese_text.xlsx">Download</a></td>
383
+ <td align="center"><a href="#japanese-text">View Code &amp; Detail</a></td>
384
+ </tr>
385
+ <tr>
386
+ <td><strong>Merge Freeze</strong></td>
387
+ <td align="center"><img src="../../test/visual/baselines/merge_freeze/page-1.png" width="160" alt="Merge Freeze"/></td>
388
+ <td align="center"><a href="files/merge_freeze.xlsx">Download</a></td>
389
+ <td align="center"><a href="#merge-freeze">View Code &amp; Detail</a></td>
390
+ </tr>
391
+ <tr>
392
+ <td><strong>Page Grid Lines Print</strong></td>
393
+ <td align="center"><img src="../../test/visual/baselines/page_grid_lines_print/page-1.png" width="160" alt="Page Grid Lines Print"/></td>
394
+ <td align="center"><a href="files/page_grid_lines_print.xlsx">Download</a></td>
395
+ <td align="center"><a href="#page-grid-lines-print">View Code &amp; Detail</a></td>
396
+ </tr>
397
+ <tr>
398
+ <td><strong>Page Header Footer</strong></td>
399
+ <td align="center"><img src="../../test/visual/baselines/page_header_footer/page-1.png" width="160" alt="Page Header Footer"/></td>
400
+ <td align="center"><a href="files/page_header_footer.xlsx">Download</a></td>
401
+ <td align="center"><a href="#page-header-footer">View Code &amp; Detail</a></td>
402
+ </tr>
403
+ <tr>
404
+ <td><strong>Page Headings Print</strong></td>
405
+ <td align="center"><img src="../../test/visual/baselines/page_headings_print/page-1.png" width="160" alt="Page Headings Print"/></td>
406
+ <td align="center"><a href="files/page_headings_print.xlsx">Download</a></td>
407
+ <td align="center"><a href="#page-headings-print">View Code &amp; Detail</a></td>
408
+ </tr>
409
+ <tr>
410
+ <td><strong>Page Margins Narrow</strong></td>
411
+ <td align="center"><img src="../../test/visual/baselines/page_margins_narrow/page-1.png" width="160" alt="Page Margins Narrow"/></td>
412
+ <td align="center"><a href="files/page_margins_narrow.xlsx">Download</a></td>
413
+ <td align="center"><a href="#page-margins-narrow">View Code &amp; Detail</a></td>
414
+ </tr>
415
+ <tr>
416
+ <td><strong>Page Margins Wide</strong></td>
417
+ <td align="center"><img src="../../test/visual/baselines/page_margins_wide/page-1.png" width="160" alt="Page Margins Wide"/></td>
418
+ <td align="center"><a href="files/page_margins_wide.xlsx">Download</a></td>
419
+ <td align="center"><a href="#page-margins-wide">View Code &amp; Detail</a></td>
420
+ </tr>
421
+ <tr>
422
+ <td><strong>Page Orientation Landscape</strong></td>
423
+ <td align="center"><img src="../../test/visual/baselines/page_orientation_landscape/page-1.png" width="160" alt="Page Orientation Landscape"/></td>
424
+ <td align="center"><a href="files/page_orientation_landscape.xlsx">Download</a></td>
425
+ <td align="center"><a href="#page-orientation-landscape">View Code &amp; Detail</a></td>
426
+ </tr>
427
+ <tr>
428
+ <td><strong>Page Paper Size A3</strong></td>
429
+ <td align="center"><img src="../../test/visual/baselines/page_paper_size_a3/page-1.png" width="160" alt="Page Paper Size A3"/></td>
430
+ <td align="center"><a href="files/page_paper_size_a3.xlsx">Download</a></td>
431
+ <td align="center"><a href="#page-paper-size-a3">View Code &amp; Detail</a></td>
432
+ </tr>
433
+ <tr>
434
+ <td><strong>Row Grouping</strong></td>
435
+ <td align="center"><img src="../../test/visual/baselines/row_grouping/page-1.png" width="160" alt="Row Grouping"/></td>
436
+ <td align="center"><a href="files/row_grouping.xlsx">Download</a></td>
437
+ <td align="center"><a href="#row-grouping">View Code &amp; Detail</a></td>
438
+ </tr>
439
+ <tr>
440
+ <td><strong>Row Height Tall</strong></td>
441
+ <td align="center"><img src="../../test/visual/baselines/row_height_tall/page-1.png" width="160" alt="Row Height Tall"/></td>
442
+ <td align="center"><a href="files/row_height_tall.xlsx">Download</a></td>
443
+ <td align="center"><a href="#row-height-tall">View Code &amp; Detail</a></td>
444
+ </tr>
445
+ <tr>
446
+ <td><strong>Row Heights</strong></td>
447
+ <td align="center"><img src="../../test/visual/baselines/row_heights/page-1.png" width="160" alt="Row Heights"/></td>
448
+ <td align="center"><a href="files/row_heights.xlsx">Download</a></td>
449
+ <td align="center"><a href="#row-heights">View Code &amp; Detail</a></td>
450
+ </tr>
451
+ <tr>
452
+ <td><strong>Sheet Tab Colors</strong></td>
453
+ <td align="center"><img src="../../test/visual/baselines/sheet_tab_colors/page-1.png" width="160" alt="Sheet Tab Colors"/></td>
454
+ <td align="center"><a href="files/sheet_tab_colors.xlsx">Download</a></td>
455
+ <td align="center"><a href="#sheet-tab-colors">View Code &amp; Detail</a></td>
456
+ </tr>
457
+ <tr>
458
+ <td><strong>Sparkline Column</strong></td>
459
+ <td align="center"><img src="../../test/visual/baselines/sparkline_column/page-1.png" width="160" alt="Sparkline Column"/></td>
460
+ <td align="center"><a href="files/sparkline_column.xlsx">Download</a></td>
461
+ <td align="center"><a href="#sparkline-column">View Code &amp; Detail</a></td>
462
+ </tr>
463
+ <tr>
464
+ <td><strong>Sparkline Line</strong></td>
465
+ <td align="center"><img src="../../test/visual/baselines/sparkline_line/page-1.png" width="160" alt="Sparkline Line"/></td>
466
+ <td align="center"><a href="files/sparkline_line.xlsx">Download</a></td>
467
+ <td align="center"><a href="#sparkline-line">View Code &amp; Detail</a></td>
468
+ </tr>
469
+ <tr>
470
+ <td><strong>Styles Fonts Fills</strong></td>
471
+ <td align="center"><img src="../../test/visual/baselines/styles_fonts_fills/page-1.png" width="160" alt="Styles Fonts Fills"/></td>
472
+ <td align="center"><a href="files/styles_fonts_fills.xlsx">Download</a></td>
473
+ <td align="center"><a href="#styles-fonts-fills">View Code &amp; Detail</a></td>
474
+ </tr>
475
+ <tr>
476
+ <td><strong>View Show Grid Lines</strong></td>
477
+ <td align="center"><img src="../../test/visual/baselines/view_show_grid_lines/page-1.png" width="160" alt="View Show Grid Lines"/></td>
478
+ <td align="center"><a href="files/view_show_grid_lines.xlsx">Download</a></td>
479
+ <td align="center"><a href="#view-show-grid-lines">View Code &amp; Detail</a></td>
480
+ </tr>
481
+ <tr>
482
+ <td><strong>View Zoom Scale</strong></td>
483
+ <td align="center"><img src="../../test/visual/baselines/view_zoom_scale/page-1.png" width="160" alt="View Zoom Scale"/></td>
484
+ <td align="center"><a href="files/view_zoom_scale.xlsx">Download</a></td>
485
+ <td align="center"><a href="#view-zoom-scale">View Code &amp; Detail</a></td>
486
+ </tr>
487
+ <tr>
488
+ <td><strong>Workbook Three Sheets</strong></td>
489
+ <td align="center"><img src="../../test/visual/baselines/workbook_three_sheets/page-1.png" width="160" alt="Workbook Three Sheets"/></td>
490
+ <td align="center"><a href="files/workbook_three_sheets.xlsx">Download</a></td>
491
+ <td align="center"><a href="#workbook-three-sheets">View Code &amp; Detail</a></td>
492
+ </tr>
493
+ </tbody>
494
+ </table>
495
+
496
+
497
+ ---
498
+
499
+ ## Align Horizontal
500
+
501
+ [Download .xlsx](files/align_horizontal.xlsx)
502
+
503
+ Demonstrates horizontal text alignment (left, center, right).
504
+
505
+ ### Rendered Output (LibreOffice Calc)
506
+
507
+ <div><img src="../../test/visual/baselines/align_horizontal/page-1.png" width="100%" alt="Preview"/></div>
508
+
509
+ ### DSL Code
510
+
511
+ ```ruby
512
+ # frozen_string_literal: true
513
+ require "xlsxrb"
514
+ output_path = ARGV[0] || "align_horizontal.xlsx"
515
+ Xlsxrb.generate(output_path) do |w|
516
+ w.add_style("left") { |s| s.align_horizontal("left") }
517
+ w.add_style("center") { |s| s.align_horizontal("center") }
518
+ w.add_style("right") { |s| s.align_horizontal("right") }
519
+ w.add_sheet("Alignment") do |s|
520
+ s.set_print_option(:grid_lines, true)
521
+ s.set_column(0, width: 20)
522
+ s.set_column(1, width: 20)
523
+ s.set_column(2, width: 20)
524
+ s.add_row(["Left", "Center", "Right"], styles: { 0 => "left", 1 => "center", 2 => "right" })
525
+ end
526
+ end
527
+
528
+ # 2. Read the generated sheet and print the parsed alignments
529
+ puts "=== Read Alignment (Xlsxrb.read) ==="
530
+ workbook = Xlsxrb.read(output_path)
531
+ sheet = workbook.sheets.first
532
+ row = sheet.rows.first
533
+
534
+ row.cells.each do |c|
535
+ xf = workbook.styles[:cell_xfs][c.style_index] if c.style_index
536
+ align_h = xf ? xf.dig(:alignment, :horizontal) : nil
537
+ puts "Cell #{c.ref} ('#{c.value}'): align_horizontal = #{align_h.inspect}"
538
+ end
539
+ ```
540
+
541
+ ### Console Output
542
+
543
+ ```text
544
+ === Read Alignment (Xlsxrb.read) ===
545
+ Cell A1 ('Left'): align_horizontal = "left"
546
+ Cell B1 ('Center'): align_horizontal = "center"
547
+ Cell C1 ('Right'): align_horizontal = "right"
548
+ ```
549
+
550
+ <hr/>
551
+
552
+ ## Align Horizontal Fill
553
+
554
+ [Download .xlsx](files/align_horizontal_fill.xlsx)
555
+
556
+ Demonstrates horizontal fill alignment (repeats value to fill cell width).
557
+
558
+ ### Rendered Output (LibreOffice Calc)
559
+
560
+ <div><img src="../../test/visual/baselines/align_horizontal_fill/page-1.png" width="100%" alt="Preview"/></div>
561
+
562
+ ### DSL Code
563
+
564
+ ```ruby
565
+ # frozen_string_literal: true
566
+ require "xlsxrb"
567
+ output_path = ARGV[0] || "align_horizontal_fill.xlsx"
568
+ Xlsxrb.generate(output_path) do |w|
569
+ w.add_style("fill_align") { |s| s.align_horizontal("fill") }
570
+ w.add_sheet("Alignment") do |s|
571
+ s.set_column(0, width: 30)
572
+ s.add_row(["X "], styles: { 0 => "fill_align" })
573
+ end
574
+ end
575
+
576
+ # 2. Read the generated sheet and print the parsed alignments
577
+ puts "=== Read Validation ==="
578
+ workbook = Xlsxrb.read(output_path)
579
+ sheet = workbook.sheets.first
580
+ row = sheet.rows.first
581
+ row.cells.each do |c|
582
+ xf = workbook.styles[:cell_xfs][c.style_index] if c.style_index
583
+ align_h = xf ? xf.dig(:alignment, :horizontal) : nil
584
+ align_v = xf ? xf.dig(:alignment, :vertical) : nil
585
+ wrap = xf ? xf.dig(:alignment, :wrap_text) : nil
586
+ indent = xf ? xf.dig(:alignment, :indent) : nil
587
+ rot = xf ? xf.dig(:alignment, :text_rotation) : nil
588
+ shrink = xf ? xf.dig(:alignment, :shrink_to_fit) : nil
589
+ puts "Cell #{c.ref} ('#{c.value}'): align_h=#{align_h.inspect}, align_v=#{align_v.inspect}, wrap=#{wrap.inspect}, indent=#{indent.inspect}, rotation=#{rot.inspect}, shrink=#{shrink.inspect}"
590
+ end
591
+ ```
592
+
593
+ ### Console Output
594
+
595
+ ```text
596
+ === Read Validation ===
597
+ Cell A1 ('X '): align_h="fill", align_v=nil, wrap=nil, indent=nil, rotation=nil, shrink=nil
598
+ ```
599
+
600
+ <hr/>
601
+
602
+ ## Align Horizontal Justify
603
+
604
+ [Download .xlsx](files/align_horizontal_justify.xlsx)
605
+
606
+ Demonstrates horizontal justify text alignment.
607
+
608
+ ### Rendered Output (LibreOffice Calc)
609
+
610
+ <div><img src="../../test/visual/baselines/align_horizontal_justify/page-1.png" width="100%" alt="Preview"/></div>
611
+
612
+ ### DSL Code
613
+
614
+ ```ruby
615
+ # frozen_string_literal: true
616
+ require "xlsxrb"
617
+ output_path = ARGV[0] || "align_horizontal_justify.xlsx"
618
+ Xlsxrb.generate(output_path) do |w|
619
+ w.add_style("justify_align") do |s|
620
+ s.align_horizontal("justify")
621
+ s.wrap_text(true)
622
+ end
623
+ w.add_sheet("Alignment") do |s|
624
+ s.set_column(0, width: 25)
625
+ s.add_row(["Justified alignment wraps and distributes text evenly."], styles: { 0 => "justify_align" })
626
+ end
627
+ end
628
+
629
+ # 2. Read the generated sheet and print the parsed alignments
630
+ puts "=== Read Validation ==="
631
+ workbook = Xlsxrb.read(output_path)
632
+ sheet = workbook.sheets.first
633
+ row = sheet.rows.first
634
+ row.cells.each do |c|
635
+ xf = workbook.styles[:cell_xfs][c.style_index] if c.style_index
636
+ align_h = xf ? xf.dig(:alignment, :horizontal) : nil
637
+ align_v = xf ? xf.dig(:alignment, :vertical) : nil
638
+ wrap = xf ? xf.dig(:alignment, :wrap_text) : nil
639
+ indent = xf ? xf.dig(:alignment, :indent) : nil
640
+ rot = xf ? xf.dig(:alignment, :text_rotation) : nil
641
+ shrink = xf ? xf.dig(:alignment, :shrink_to_fit) : nil
642
+ puts "Cell #{c.ref} ('#{c.value}'): align_h=#{align_h.inspect}, align_v=#{align_v.inspect}, wrap=#{wrap.inspect}, indent=#{indent.inspect}, rotation=#{rot.inspect}, shrink=#{shrink.inspect}"
643
+ end
644
+ ```
645
+
646
+ ### Console Output
647
+
648
+ ```text
649
+ === Read Validation ===
650
+ Cell A1 ('Justified alignment wraps and distributes text evenly.'): align_h="justify", align_v=nil, wrap=true, indent=nil, rotation=nil, shrink=nil
651
+ ```
652
+
653
+ <hr/>
654
+
655
+ ## Align Indent
656
+
657
+ [Download .xlsx](files/align_indent.xlsx)
658
+
659
+ Demonstrates text indentation inside cells.
660
+
661
+ ### Rendered Output (LibreOffice Calc)
662
+
663
+ <div><img src="../../test/visual/baselines/align_indent/page-1.png" width="100%" alt="Preview"/></div>
664
+
665
+ ### DSL Code
666
+
667
+ ```ruby
668
+ # frozen_string_literal: true
669
+ require "xlsxrb"
670
+ output_path = ARGV[0] || "align_indent.xlsx"
671
+ Xlsxrb.generate(output_path) do |w|
672
+ w.add_style("indent_1") { |s| s.align_horizontal("left").indent(1) }
673
+ w.add_style("indent_3") { |s| s.align_horizontal("left").indent(3) }
674
+ w.add_sheet("Indent") do |s|
675
+ s.set_column(0, width: 25)
676
+ s.set_column(1, width: 25)
677
+ s.set_print_option(:grid_lines, true)
678
+ s.add_row(["No Indent"])
679
+ s.add_row(["Indent 1"], styles: { 0 => "indent_1" })
680
+ s.add_row(["Indent 3"], styles: { 0 => "indent_3" })
681
+ end
682
+ end
683
+
684
+ # 2. Read the generated sheet and print the parsed alignments
685
+ puts "=== Read Validation ==="
686
+ workbook = Xlsxrb.read(output_path)
687
+ sheet = workbook.sheets.first
688
+ row = sheet.rows.first
689
+ row.cells.each do |c|
690
+ xf = workbook.styles[:cell_xfs][c.style_index] if c.style_index
691
+ align_h = xf ? xf.dig(:alignment, :horizontal) : nil
692
+ align_v = xf ? xf.dig(:alignment, :vertical) : nil
693
+ wrap = xf ? xf.dig(:alignment, :wrap_text) : nil
694
+ indent = xf ? xf.dig(:alignment, :indent) : nil
695
+ rot = xf ? xf.dig(:alignment, :text_rotation) : nil
696
+ shrink = xf ? xf.dig(:alignment, :shrink_to_fit) : nil
697
+ puts "Cell #{c.ref} ('#{c.value}'): align_h=#{align_h.inspect}, align_v=#{align_v.inspect}, wrap=#{wrap.inspect}, indent=#{indent.inspect}, rotation=#{rot.inspect}, shrink=#{shrink.inspect}"
698
+ end
699
+ ```
700
+
701
+ ### Console Output
702
+
703
+ ```text
704
+ === Read Validation ===
705
+ Cell A1 ('No Indent'): align_h=nil, align_v=nil, wrap=nil, indent=nil, rotation=nil, shrink=nil
706
+ ```
707
+
708
+ <hr/>
709
+
710
+ ## Align Text Rotation
711
+
712
+ [Download .xlsx](files/align_text_rotation.xlsx)
713
+
714
+ Demonstrates text rotated by specific angles (45, 90 degrees).
715
+
716
+ ### Rendered Output (LibreOffice Calc)
717
+
718
+ <div><img src="../../test/visual/baselines/align_text_rotation/page-1.png" width="100%" alt="Preview"/></div>
719
+
720
+ ### DSL Code
721
+
722
+ ```ruby
723
+ # frozen_string_literal: true
724
+ require "xlsxrb"
725
+ output_path = ARGV[0] || "align_text_rotation.xlsx"
726
+ Xlsxrb.generate(output_path) do |w|
727
+ w.add_style("rot_45") { |s| s.text_rotation(45) }
728
+ w.add_style("rot_90") { |s| s.text_rotation(90) }
729
+ w.add_sheet("Rotation") do |s|
730
+ s.set_column(0, width: 25)
731
+ s.set_column(1, width: 25)
732
+ s.set_print_option(:grid_lines, true)
733
+ s.add_row(["Rotated 45", "Rotated 90"], styles: { 0 => "rot_45", 1 => "rot_90" }, height: 50)
734
+ end
735
+ end
736
+
737
+ # 2. Read the generated sheet and print the parsed alignments
738
+ puts "=== Read Validation ==="
739
+ workbook = Xlsxrb.read(output_path)
740
+ sheet = workbook.sheets.first
741
+ row = sheet.rows.first
742
+ row.cells.each do |c|
743
+ xf = workbook.styles[:cell_xfs][c.style_index] if c.style_index
744
+ align_h = xf ? xf.dig(:alignment, :horizontal) : nil
745
+ align_v = xf ? xf.dig(:alignment, :vertical) : nil
746
+ wrap = xf ? xf.dig(:alignment, :wrap_text) : nil
747
+ indent = xf ? xf.dig(:alignment, :indent) : nil
748
+ rot = xf ? xf.dig(:alignment, :text_rotation) : nil
749
+ shrink = xf ? xf.dig(:alignment, :shrink_to_fit) : nil
750
+ puts "Cell #{c.ref} ('#{c.value}'): align_h=#{align_h.inspect}, align_v=#{align_v.inspect}, wrap=#{wrap.inspect}, indent=#{indent.inspect}, rotation=#{rot.inspect}, shrink=#{shrink.inspect}"
751
+ end
752
+ ```
753
+
754
+ ### Console Output
755
+
756
+ ```text
757
+ === Read Validation ===
758
+ Cell A1 ('Rotated 45'): align_h=nil, align_v=nil, wrap=nil, indent=nil, rotation=45, shrink=nil
759
+ Cell B1 ('Rotated 90'): align_h=nil, align_v=nil, wrap=nil, indent=nil, rotation=90, shrink=nil
760
+ ```
761
+
762
+ <hr/>
763
+
764
+ ## Align Text Wrap
765
+
766
+ [Download .xlsx](files/align_text_wrap.xlsx)
767
+
768
+ Demonstrates auto-wrapping multi-line text inside narrow cells.
769
+
770
+ ### Rendered Output (LibreOffice Calc)
771
+
772
+ <div><img src="../../test/visual/baselines/align_text_wrap/page-1.png" width="100%" alt="Preview"/></div>
773
+
774
+ ### DSL Code
775
+
776
+ ```ruby
777
+ # frozen_string_literal: true
778
+ require "xlsxrb"
779
+ output_path = ARGV[0] || "align_text_wrap.xlsx"
780
+ Xlsxrb.generate(output_path) do |w|
781
+ w.add_style("wrap") { |s| s.wrap_text }
782
+ w.add_sheet("Text Wrap") do |s|
783
+ s.set_print_option(:grid_lines, true)
784
+ s.set_column(0, width: 15)
785
+ s.add_row(["This is a long sentence that wraps inside the cell."], styles: { 0 => "wrap" })
786
+ end
787
+ end
788
+
789
+ # 2. Read the generated sheet and print the parsed alignments
790
+ puts "=== Read Validation ==="
791
+ workbook = Xlsxrb.read(output_path)
792
+ sheet = workbook.sheets.first
793
+ row = sheet.rows.first
794
+ row.cells.each do |c|
795
+ xf = workbook.styles[:cell_xfs][c.style_index] if c.style_index
796
+ align_h = xf ? xf.dig(:alignment, :horizontal) : nil
797
+ align_v = xf ? xf.dig(:alignment, :vertical) : nil
798
+ wrap = xf ? xf.dig(:alignment, :wrap_text) : nil
799
+ indent = xf ? xf.dig(:alignment, :indent) : nil
800
+ rot = xf ? xf.dig(:alignment, :text_rotation) : nil
801
+ shrink = xf ? xf.dig(:alignment, :shrink_to_fit) : nil
802
+ puts "Cell #{c.ref} ('#{c.value}'): align_h=#{align_h.inspect}, align_v=#{align_v.inspect}, wrap=#{wrap.inspect}, indent=#{indent.inspect}, rotation=#{rot.inspect}, shrink=#{shrink.inspect}"
803
+ end
804
+ ```
805
+
806
+ ### Console Output
807
+
808
+ ```text
809
+ === Read Validation ===
810
+ Cell A1 ('This is a long sentence that wraps inside the cell.'): align_h=nil, align_v=nil, wrap=true, indent=nil, rotation=nil, shrink=nil
811
+ ```
812
+
813
+ <hr/>
814
+
815
+ ## Align Vertical
816
+
817
+ [Download .xlsx](files/align_vertical.xlsx)
818
+
819
+ Demonstrates vertical text alignment (top, center, bottom).
820
+
821
+ ### Rendered Output (LibreOffice Calc)
822
+
823
+ <div><img src="../../test/visual/baselines/align_vertical/page-1.png" width="100%" alt="Preview"/></div>
824
+
825
+ ### DSL Code
826
+
827
+ ```ruby
828
+ # frozen_string_literal: true
829
+ require "xlsxrb"
830
+ output_path = ARGV[0] || "align_vertical.xlsx"
831
+ Xlsxrb.generate(output_path) do |w|
832
+ w.add_style("top") { |s| s.align_vertical("top") }
833
+ w.add_style("center") { |s| s.align_vertical("center") }
834
+ w.add_style("bottom") { |s| s.align_vertical("bottom") }
835
+ w.add_sheet("Vertical Alignment") do |s|
836
+ s.set_column(0, width: 25)
837
+ s.set_column(1, width: 25)
838
+ s.set_print_option(:grid_lines, true)
839
+ s.add_row(["Top", "Center", "Bottom"], styles: { 0 => "top", 1 => "center", 2 => "bottom" }, height: 40)
840
+ end
841
+ end
842
+
843
+ # 2. Read the generated sheet and print the parsed alignments
844
+ puts "=== Read Validation ==="
845
+ workbook = Xlsxrb.read(output_path)
846
+ sheet = workbook.sheets.first
847
+ row = sheet.rows.first
848
+ row.cells.each do |c|
849
+ xf = workbook.styles[:cell_xfs][c.style_index] if c.style_index
850
+ align_h = xf ? xf.dig(:alignment, :horizontal) : nil
851
+ align_v = xf ? xf.dig(:alignment, :vertical) : nil
852
+ wrap = xf ? xf.dig(:alignment, :wrap_text) : nil
853
+ indent = xf ? xf.dig(:alignment, :indent) : nil
854
+ rot = xf ? xf.dig(:alignment, :text_rotation) : nil
855
+ shrink = xf ? xf.dig(:alignment, :shrink_to_fit) : nil
856
+ puts "Cell #{c.ref} ('#{c.value}'): align_h=#{align_h.inspect}, align_v=#{align_v.inspect}, wrap=#{wrap.inspect}, indent=#{indent.inspect}, rotation=#{rot.inspect}, shrink=#{shrink.inspect}"
857
+ end
858
+ ```
859
+
860
+ ### Console Output
861
+
862
+ ```text
863
+ === Read Validation ===
864
+ Cell A1 ('Top'): align_h=nil, align_v="top", wrap=nil, indent=nil, rotation=nil, shrink=nil
865
+ Cell B1 ('Center'): align_h=nil, align_v="center", wrap=nil, indent=nil, rotation=nil, shrink=nil
866
+ Cell C1 ('Bottom'): align_h=nil, align_v="bottom", wrap=nil, indent=nil, rotation=nil, shrink=nil
867
+ ```
868
+
869
+ <hr/>
870
+
871
+ ## Basic Data
872
+
873
+ [Download .xlsx](files/basic_data.xlsx)
874
+
875
+ Demonstrates simple tabular data writing with basic Ruby types (Strings, Numbers, Dates, Booleans).
876
+
877
+ ### Rendered Output (LibreOffice Calc)
878
+
879
+ <div><img src="../../test/visual/baselines/basic_data/page-1.png" width="100%" alt="Preview"/></div>
880
+
881
+ ### DSL Code
882
+
883
+ ```ruby
884
+ # frozen_string_literal: true
885
+
886
+ require "xlsxrb"
887
+ require "date"
888
+
889
+ output_path = ARGV[0] || "basic_data.xlsx"
890
+
891
+ Xlsxrb.generate(output_path) do |w|
892
+ w.add_style("currency") { |style| style.number_format("$#,##0.00") }
893
+ w.add_style("date") { |style| style.number_format("yyyy-mm-dd") }
894
+ w.add_sheet("Basic Data") do
895
+ w.set_sheet_property(:fit_to_page, true)
896
+ w.set_page_setup(fit_to_width: 1, fit_to_height: 1)
897
+ w.set_column(0, width: 25)
898
+ w.set_column(1, width: 25)
899
+ w.add_row(["Product", "Qty", "Price", "Date", "Active"])
900
+ w.add_row(["Gadget A", 10, 99.99, Date.new(2026, 1, 15), true], styles: { 2 => "currency", 3 => "date" })
901
+ w.add_row(["Widget B", 5, 49.50, Date.new(2026, 2, 20), false], styles: { 2 => "currency", 3 => "date" })
902
+ end
903
+ end
904
+
905
+ # 2. Read the generated sheet and print parsed cell values and Ruby classes
906
+ puts "=== Read Validation ==="
907
+ workbook = Xlsxrb.read(output_path)
908
+ sheet = workbook.sheets.first
909
+ sheet.rows.each do |row|
910
+ row_cells = row.cells.map do |c|
911
+ "#{c.ref}: #{c.value.inspect} (#{c.value.class})"
912
+ end
913
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
914
+ end
915
+ ```
916
+
917
+ ### Console Output
918
+
919
+ ```text
920
+ === Read Validation ===
921
+ Row 0: A1: "Product" (String), B1: "Qty" (String), C1: "Price" (String), D1: "Date" (String), E1: "Active" (String)
922
+ Row 1: A2: "Gadget A" (String), B2: 10 (Integer), C2: 99.99 (Float), D2: 46037 (Integer), E2: true (TrueClass)
923
+ Row 2: A3: "Widget B" (String), B3: 5 (Integer), C3: 49.5 (Float), D3: 46073 (Integer), E3: false (FalseClass)
924
+ ```
925
+
926
+ <hr/>
927
+
928
+ ## Borders
929
+
930
+ [Download .xlsx](files/borders.xlsx)
931
+
932
+ Demonstrates border styles (thin, medium, thick, hair, dashed, medium dashed, dotted, double, dash-dot, medium dash-dot, dash-dot-dot, slanted, and diagonal cross borders) applied to cell ranges.
933
+
934
+ ### Rendered Output (LibreOffice Calc)
935
+
936
+ <div><img src="../../test/visual/baselines/borders/page-1.png" width="100%" alt="Preview"/></div>
937
+
938
+ ### DSL Code
939
+
940
+ ```ruby
941
+ # frozen_string_literal: true
942
+
943
+ require "xlsxrb"
944
+
945
+ output_path = ARGV[0] || "borders.xlsx"
946
+
947
+ Xlsxrb.generate(output_path) do |w|
948
+ w.add_style("thin") { |s| s.border_all(style: "thin", color: "FF000000") }
949
+ w.add_style("medium") { |s| s.border_all(style: "medium", color: "FF000000") }
950
+ w.add_style("thick") { |s| s.border_all(style: "thick", color: "FF000000") }
951
+ w.add_style("hair") { |s| s.border_all(style: "hair", color: "FF000000") }
952
+ w.add_style("dashed") { |s| s.border_all(style: "dashed", color: "FF000000") }
953
+ w.add_style("medium_dashed") { |s| s.border_all(style: "mediumDashed", color: "FF000000") }
954
+ w.add_style("dotted") { |s| s.border_all(style: "dotted", color: "FF000000") }
955
+ w.add_style("double") { |s| s.border_all(style: "double", color: "FF000000") }
956
+ w.add_style("dash_dot") { |s| s.border_all(style: "dashDot", color: "FF000000") }
957
+ w.add_style("medium_dash_dot") { |s| s.border_all(style: "mediumDashDot", color: "FF000000") }
958
+ w.add_style("dash_dot_dot") { |s| s.border_all(style: "dashDotDot", color: "FF000000") }
959
+ w.add_style("slanted") { |s| s.border_all(style: "slantedDashDot", color: "FF000000") }
960
+ w.add_style("diagonal") { |s| s.border_diagonal(style: "thin", color: "FF000000", up: true, down: true) }
961
+
962
+ w.add_sheet("Borders") do |s|
963
+ s.set_column(0, width: 25)
964
+ s.set_column(1, width: 25)
965
+ s.set_print_option(:grid_lines, true)
966
+
967
+ s.add_row(["Border Style", "Cell Preview"])
968
+ s.add_row(["Thin", "Thin Border"], styles: { 1 => "thin" })
969
+ s.add_row(["Medium", "Medium Border"], styles: { 1 => "medium" })
970
+ s.add_row(["Thick", "Thick Border"], styles: { 1 => "thick" })
971
+ s.add_row(["Hair", "Hair Border"], styles: { 1 => "hair" })
972
+ s.add_row(["Dashed", "Dashed Border"], styles: { 1 => "dashed" })
973
+ s.add_row(["Medium Dashed", "Medium Dashed"], styles: { 1 => "medium_dashed" })
974
+ s.add_row(["Dotted", "Dotted Border"], styles: { 1 => "dotted" })
975
+ s.add_row(["Double", "Double Border"], styles: { 1 => "double" })
976
+ s.add_row(["Dash-Dot", "Dash-Dot Border"], styles: { 1 => "dash_dot" })
977
+ s.add_row(["Medium Dash-Dot", "Medium Dash-Dot"], styles: { 1 => "medium_dash_dot" })
978
+ s.add_row(["Dash-Dot-Dot", "Dash-Dot-Dot"], styles: { 1 => "dash_dot_dot" })
979
+ s.add_row(["Slanted Dash-Dot", "Slanted Border"], styles: { 1 => "slanted" })
980
+ s.add_row(["Diagonal (Cross)", "Diagonal Border"], styles: { 1 => "diagonal" })
981
+ end
982
+ end
983
+
984
+ # Read check
985
+ puts "=== Read Validation ==="
986
+ workbook = Xlsxrb.read(output_path)
987
+ sheet = workbook.sheets.first
988
+ sheet.rows.each do |row|
989
+ row_cells = row.cells.map do |c|
990
+ xf = workbook.styles[:cell_xfs][c.style_index] if c.style_index
991
+ border_id = xf ? xf[:border_id] : 0
992
+ "#{c.ref}: #{c.value.inspect} (border_id: #{border_id})"
993
+ end
994
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
995
+ end
996
+ ```
997
+
998
+ ### Console Output
999
+
1000
+ ```text
1001
+ === Read Validation ===
1002
+ Row 0: A1: "Border Style" (border_id: 0), B1: "Cell Preview" (border_id: 0)
1003
+ Row 1: A2: "Thin" (border_id: 0), B2: "Thin Border" (border_id: 1)
1004
+ Row 2: A3: "Medium" (border_id: 0), B3: "Medium Border" (border_id: 2)
1005
+ Row 3: A4: "Thick" (border_id: 0), B4: "Thick Border" (border_id: 3)
1006
+ Row 4: A5: "Hair" (border_id: 0), B5: "Hair Border" (border_id: 4)
1007
+ Row 5: A6: "Dashed" (border_id: 0), B6: "Dashed Border" (border_id: 5)
1008
+ Row 6: A7: "Medium Dashed" (border_id: 0), B7: "Medium Dashed" (border_id: 6)
1009
+ Row 7: A8: "Dotted" (border_id: 0), B8: "Dotted Border" (border_id: 7)
1010
+ Row 8: A9: "Double" (border_id: 0), B9: "Double Border" (border_id: 8)
1011
+ Row 9: A10: "Dash-Dot" (border_id: 0), B10: "Dash-Dot Border" (border_id: 9)
1012
+ Row 10: A11: "Medium Dash-Dot" (border_id: 0), B11: "Medium Dash-Dot" (border_id: 10)
1013
+ Row 11: A12: "Dash-Dot-Dot" (border_id: 0), B12: "Dash-Dot-Dot" (border_id: 11)
1014
+ Row 12: A13: "Slanted Dash-Dot" (border_id: 0), B13: "Slanted Border" (border_id: 12)
1015
+ Row 13: A14: "Diagonal (Cross)" (border_id: 0), B14: "Diagonal Border" (border_id: 13)
1016
+ ```
1017
+
1018
+ <hr/>
1019
+
1020
+ ## Cell Booleans
1021
+
1022
+ [Download .xlsx](files/cell_booleans.xlsx)
1023
+
1024
+ Demonstrates boolean values serialized and rendered.
1025
+
1026
+ ### Rendered Output (LibreOffice Calc)
1027
+
1028
+ <div><img src="../../test/visual/baselines/cell_booleans/page-1.png" width="100%" alt="Preview"/></div>
1029
+
1030
+ ### DSL Code
1031
+
1032
+ ```ruby
1033
+ # frozen_string_literal: true
1034
+ require "xlsxrb"
1035
+ output_path = ARGV[0] || "cell_booleans.xlsx"
1036
+ Xlsxrb.generate(output_path) do |w|
1037
+ w.add_sheet("Booleans") do |s|
1038
+ s.set_column(0, width: 25)
1039
+ s.set_column(1, width: 25)
1040
+ s.add_row(["Label", "Boolean Value"])
1041
+ s.add_row(["Is Active", true])
1042
+ s.add_row(["Is Pending", false])
1043
+ end
1044
+ end
1045
+
1046
+ # 2. Read the generated sheet and print parsed cell values and Ruby classes
1047
+ puts "=== Read Validation ==="
1048
+ workbook = Xlsxrb.read(output_path)
1049
+ sheet = workbook.sheets.first
1050
+ sheet.rows.each do |row|
1051
+ row_cells = row.cells.map do |c|
1052
+ "#{c.ref}: #{c.value.inspect} (#{c.value.class})"
1053
+ end
1054
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
1055
+ end
1056
+ ```
1057
+
1058
+ ### Console Output
1059
+
1060
+ ```text
1061
+ === Read Validation ===
1062
+ Row 0: A1: "Label" (String), B1: "Boolean Value" (String)
1063
+ Row 1: A2: "Is Active" (String), B2: true (TrueClass)
1064
+ Row 2: A3: "Is Pending" (String), B3: false (FalseClass)
1065
+ ```
1066
+
1067
+ <hr/>
1068
+
1069
+ ## Cell Dates
1070
+
1071
+ [Download .xlsx](files/cell_dates.xlsx)
1072
+
1073
+ Demonstrates dates serialized natively and formatted with standard or custom format strings.
1074
+
1075
+ ### Rendered Output (LibreOffice Calc)
1076
+
1077
+ <div><img src="../../test/visual/baselines/cell_dates/page-1.png" width="100%" alt="Preview"/></div>
1078
+
1079
+ ### DSL Code
1080
+
1081
+ ```ruby
1082
+ # frozen_string_literal: true
1083
+ require "xlsxrb"
1084
+ require "date"
1085
+ output_path = ARGV[0] || "cell_dates.xlsx"
1086
+ Xlsxrb.generate(output_path) do |w|
1087
+ w.add_style("custom_date") { |s| s.num_fmt("yyyy-mm-dd") }
1088
+ w.add_sheet("Dates") do |s|
1089
+ s.set_column(0, width: 25)
1090
+ s.set_column(1, width: 25)
1091
+ s.add_row(["Format", "Date Value"])
1092
+ s.add_row(["Default Date", Date.new(2026, 7, 1)])
1093
+ s.add_row(["Formatted Date", Date.new(2026, 12, 25)], styles: { 1 => "custom_date" })
1094
+ end
1095
+ end
1096
+
1097
+ # 2. Read the generated sheet and print parsed cell values and Ruby classes
1098
+ puts "=== Read Validation ==="
1099
+ workbook = Xlsxrb.read(output_path)
1100
+ sheet = workbook.sheets.first
1101
+ sheet.rows.each do |row|
1102
+ row_cells = row.cells.map do |c|
1103
+ "#{c.ref}: #{c.value.inspect} (#{c.value.class})"
1104
+ end
1105
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
1106
+ end
1107
+ ```
1108
+
1109
+ ### Console Output
1110
+
1111
+ ```text
1112
+ === Read Validation ===
1113
+ Row 0: A1: "Format" (String), B1: "Date Value" (String)
1114
+ Row 1: A2: "Default Date" (String), B2: 46204 (Integer)
1115
+ Row 2: A3: "Formatted Date" (String), B3: 46381 (Integer)
1116
+ ```
1117
+
1118
+ <hr/>
1119
+
1120
+ ## Cell Formulas
1121
+
1122
+ [Download .xlsx](files/cell_formulas.xlsx)
1123
+
1124
+ Demonstrates standard spreadsheet calculations and formulas (SUM, AVERAGE).
1125
+
1126
+ ### Rendered Output (LibreOffice Calc)
1127
+
1128
+ <div><img src="../../test/visual/baselines/cell_formulas/page-1.png" width="100%" alt="Preview"/></div>
1129
+
1130
+ ### DSL Code
1131
+
1132
+ ```ruby
1133
+ # frozen_string_literal: true
1134
+ require "xlsxrb"
1135
+ output_path = ARGV[0] || "cell_formulas.xlsx"
1136
+ Xlsxrb.generate(output_path) do |w|
1137
+ w.add_sheet("Formulas") do |s|
1138
+ s.set_column(0, width: 25)
1139
+ s.set_column(1, width: 25)
1140
+ s.add_row(["Item", "Value"])
1141
+ s.add_row(["A", 10])
1142
+ s.add_row(["B", 20])
1143
+ s.add_row(["SUM", Xlsxrb::Elements::Formula.new(expression: "SUM(B2:B3)", cached_value: 30)])
1144
+ s.add_row(["AVERAGE", Xlsxrb::Elements::Formula.new(expression: "AVERAGE(B2:B3)", cached_value: 15)])
1145
+ end
1146
+ end
1147
+
1148
+ # 2. Read the generated sheet and print parsed cell values and Ruby classes
1149
+ puts "=== Read Validation ==="
1150
+ workbook = Xlsxrb.read(output_path)
1151
+ sheet = workbook.sheets.first
1152
+ sheet.rows.each do |row|
1153
+ row_cells = row.cells.map do |c|
1154
+ "#{c.ref}: #{c.value.inspect} (#{c.value.class})"
1155
+ end
1156
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
1157
+ end
1158
+ ```
1159
+
1160
+ ### Console Output
1161
+
1162
+ ```text
1163
+ === Read Validation ===
1164
+ Row 0: A1: "Item" (String), B1: "Value" (String)
1165
+ Row 1: A2: "A" (String), B2: 10 (Integer)
1166
+ Row 2: A3: "B" (String), B3: 20 (Integer)
1167
+ Row 3: A4: "SUM" (String), B4: 30 (Integer)
1168
+ Row 4: A5: "AVERAGE" (String), B5: 15 (Integer)
1169
+ ```
1170
+
1171
+ <hr/>
1172
+
1173
+ ## Cell Num Currency Jpy
1174
+
1175
+ [Download .xlsx](files/cell_num_currency_jpy.xlsx)
1176
+
1177
+ Demonstrates Yen Currency format code formatting.
1178
+
1179
+ ### Rendered Output (LibreOffice Calc)
1180
+
1181
+ <div><img src="../../test/visual/baselines/cell_num_currency_jpy/page-1.png" width="100%" alt="Preview"/></div>
1182
+
1183
+ ### DSL Code
1184
+
1185
+ ```ruby
1186
+ # frozen_string_literal: true
1187
+ require "xlsxrb"
1188
+ output_path = ARGV[0] || "cell_num_currency_jpy.xlsx"
1189
+ Xlsxrb.generate(output_path) do |w|
1190
+ w.add_style("jpy") { |s| s.num_fmt("¥#,##0;[Red]¥-#,##0") }
1191
+ w.add_sheet("JPY Currency") do |s|
1192
+ s.set_column(0, width: 25)
1193
+ s.set_column(1, width: 25)
1194
+ s.add_row(["Format", "Value"])
1195
+ s.add_row(["Positive Yen", 12500], styles: { 1 => "jpy" })
1196
+ s.add_row(["Negative Yen", -8000], styles: { 1 => "jpy" })
1197
+ end
1198
+ end
1199
+
1200
+ # 2. Read the generated sheet and print parsed cell numbers and format codes
1201
+ puts "=== Read Validation ==="
1202
+ workbook = Xlsxrb.read(output_path)
1203
+ sheet = workbook.sheets.first
1204
+ sheet.rows.each do |row|
1205
+ row_cells = row.cells.map do |c|
1206
+ xf = workbook.styles[:cell_xfs][c.style_index] if c.style_index
1207
+ num_fmt = xf ? workbook.styles[:num_fmts][xf[:num_fmt_id]] : nil
1208
+ "#{c.ref}: #{c.value.inspect} (Format ID: #{xf&.[](:num_fmt_id)}, Code: #{num_fmt.inspect})"
1209
+ end
1210
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
1211
+ end
1212
+ ```
1213
+
1214
+ ### Console Output
1215
+
1216
+ ```text
1217
+ === Read Validation ===
1218
+ Row 0: A1: "Format" (Format ID: , Code: nil), B1: "Value" (Format ID: , Code: nil)
1219
+ Row 1: A2: "Positive Yen" (Format ID: , Code: nil), B2: 12500 (Format ID: 164, Code: "¥#,##0;[Red]¥-#,##0")
1220
+ Row 2: A3: "Negative Yen" (Format ID: , Code: nil), B3: -8000 (Format ID: 164, Code: "¥#,##0;[Red]¥-#,##0")
1221
+ ```
1222
+
1223
+ <hr/>
1224
+
1225
+ ## Cell Num Custom Colors
1226
+
1227
+ [Download .xlsx](files/cell_num_custom_colors.xlsx)
1228
+
1229
+ Demonstrates custom colored formats for positive and negative numbers.
1230
+
1231
+ ### Rendered Output (LibreOffice Calc)
1232
+
1233
+ <div><img src="../../test/visual/baselines/cell_num_custom_colors/page-1.png" width="100%" alt="Preview"/></div>
1234
+
1235
+ ### DSL Code
1236
+
1237
+ ```ruby
1238
+ # frozen_string_literal: true
1239
+ require "xlsxrb"
1240
+ output_path = ARGV[0] || "cell_num_custom_colors.xlsx"
1241
+ Xlsxrb.generate(output_path) do |w|
1242
+ w.add_style("custom_color") { |s| s.num_fmt("[Green]#,##0;[Red]-#,##0") }
1243
+ w.add_sheet("Custom Colors") do |s|
1244
+ s.set_column(0, width: 25)
1245
+ s.set_column(1, width: 25)
1246
+ s.add_row(["Format", "Value"])
1247
+ s.add_row(["Positive (Green)", 5000], styles: { 1 => "custom_color" })
1248
+ s.add_row(["Negative (Red)", -2500], styles: { 1 => "custom_color" })
1249
+ end
1250
+ end
1251
+
1252
+ # 2. Read the generated sheet and print parsed cell numbers and format codes
1253
+ puts "=== Read Validation ==="
1254
+ workbook = Xlsxrb.read(output_path)
1255
+ sheet = workbook.sheets.first
1256
+ sheet.rows.each do |row|
1257
+ row_cells = row.cells.map do |c|
1258
+ xf = workbook.styles[:cell_xfs][c.style_index] if c.style_index
1259
+ num_fmt = xf ? workbook.styles[:num_fmts][xf[:num_fmt_id]] : nil
1260
+ "#{c.ref}: #{c.value.inspect} (Format ID: #{xf&.[](:num_fmt_id)}, Code: #{num_fmt.inspect})"
1261
+ end
1262
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
1263
+ end
1264
+ ```
1265
+
1266
+ ### Console Output
1267
+
1268
+ ```text
1269
+ === Read Validation ===
1270
+ Row 0: A1: "Format" (Format ID: , Code: nil), B1: "Value" (Format ID: , Code: nil)
1271
+ Row 1: A2: "Positive (Green)" (Format ID: , Code: nil), B2: 5000 (Format ID: 164, Code: "[Green]#,##0;[Red]-#,##0")
1272
+ Row 2: A3: "Negative (Red)" (Format ID: , Code: nil), B3: -2500 (Format ID: 164, Code: "[Green]#,##0;[Red]-#,##0")
1273
+ ```
1274
+
1275
+ <hr/>
1276
+
1277
+ ## Cell Num Fractions
1278
+
1279
+ [Download .xlsx](files/cell_num_fractions.xlsx)
1280
+
1281
+ Demonstrates fraction number formats (# ?/?).
1282
+
1283
+ ### Rendered Output (LibreOffice Calc)
1284
+
1285
+ <div><img src="../../test/visual/baselines/cell_num_fractions/page-1.png" width="100%" alt="Preview"/></div>
1286
+
1287
+ ### DSL Code
1288
+
1289
+ ```ruby
1290
+ # frozen_string_literal: true
1291
+ require "xlsxrb"
1292
+ output_path = ARGV[0] || "cell_num_fractions.xlsx"
1293
+ Xlsxrb.generate(output_path) do |w|
1294
+ w.add_style("frac") { |s| s.num_fmt("# ?/?") }
1295
+ w.add_sheet("Fractions") do |s|
1296
+ s.set_column(0, width: 25)
1297
+ s.set_column(1, width: 25)
1298
+ s.add_row(["Format", "Value"])
1299
+ s.add_row(["Half", 0.5], styles: { 1 => "frac" })
1300
+ s.add_row(["Third", 0.3333], styles: { 1 => "frac" })
1301
+ s.add_row(["Quarter", 0.75], styles: { 1 => "frac" })
1302
+ end
1303
+ end
1304
+
1305
+ # 2. Read the generated sheet and print parsed cell numbers and format codes
1306
+ puts "=== Read Validation ==="
1307
+ workbook = Xlsxrb.read(output_path)
1308
+ sheet = workbook.sheets.first
1309
+ sheet.rows.each do |row|
1310
+ row_cells = row.cells.map do |c|
1311
+ xf = workbook.styles[:cell_xfs][c.style_index] if c.style_index
1312
+ num_fmt = xf ? workbook.styles[:num_fmts][xf[:num_fmt_id]] : nil
1313
+ "#{c.ref}: #{c.value.inspect} (Format ID: #{xf&.[](:num_fmt_id)}, Code: #{num_fmt.inspect})"
1314
+ end
1315
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
1316
+ end
1317
+ ```
1318
+
1319
+ ### Console Output
1320
+
1321
+ ```text
1322
+ === Read Validation ===
1323
+ Row 0: A1: "Format" (Format ID: , Code: nil), B1: "Value" (Format ID: , Code: nil)
1324
+ Row 1: A2: "Half" (Format ID: , Code: nil), B2: 0.5 (Format ID: 164, Code: "# ?/?")
1325
+ Row 2: A3: "Third" (Format ID: , Code: nil), B3: 0.3333 (Format ID: 164, Code: "# ?/?")
1326
+ Row 3: A4: "Quarter" (Format ID: , Code: nil), B4: 0.75 (Format ID: 164, Code: "# ?/?")
1327
+ ```
1328
+
1329
+ <hr/>
1330
+
1331
+ ## Cell Num Percent Decimals
1332
+
1333
+ [Download .xlsx](files/cell_num_percent_decimals.xlsx)
1334
+
1335
+ Demonstrates percentages with two decimal places (0.00%).
1336
+
1337
+ ### Rendered Output (LibreOffice Calc)
1338
+
1339
+ <div><img src="../../test/visual/baselines/cell_num_percent_decimals/page-1.png" width="100%" alt="Preview"/></div>
1340
+
1341
+ ### DSL Code
1342
+
1343
+ ```ruby
1344
+ # frozen_string_literal: true
1345
+ require "xlsxrb"
1346
+ output_path = ARGV[0] || "cell_num_percent_decimals.xlsx"
1347
+ Xlsxrb.generate(output_path) do |w|
1348
+ w.add_style("pct2") { |s| s.num_fmt("0.00%") }
1349
+ w.add_sheet("Percents") do |s|
1350
+ s.set_column(0, width: 25)
1351
+ s.set_column(1, width: 25)
1352
+ s.add_row(["Format", "Value"])
1353
+ s.add_row(["Percent with 2 Decimals", 0.12345], styles: { 1 => "pct2" })
1354
+ end
1355
+ end
1356
+
1357
+ # 2. Read the generated sheet and print parsed cell numbers and format codes
1358
+ puts "=== Read Validation ==="
1359
+ workbook = Xlsxrb.read(output_path)
1360
+ sheet = workbook.sheets.first
1361
+ sheet.rows.each do |row|
1362
+ row_cells = row.cells.map do |c|
1363
+ xf = workbook.styles[:cell_xfs][c.style_index] if c.style_index
1364
+ num_fmt = xf ? workbook.styles[:num_fmts][xf[:num_fmt_id]] : nil
1365
+ "#{c.ref}: #{c.value.inspect} (Format ID: #{xf&.[](:num_fmt_id)}, Code: #{num_fmt.inspect})"
1366
+ end
1367
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
1368
+ end
1369
+ ```
1370
+
1371
+ ### Console Output
1372
+
1373
+ ```text
1374
+ === Read Validation ===
1375
+ Row 0: A1: "Format" (Format ID: , Code: nil), B1: "Value" (Format ID: , Code: nil)
1376
+ Row 1: A2: "Percent with 2 Decimals" (Format ID: , Code: nil), B2: 0.12345 (Format ID: 164, Code: "0.00%")
1377
+ ```
1378
+
1379
+ <hr/>
1380
+
1381
+ ## Cell Num Scientific
1382
+
1383
+ [Download .xlsx](files/cell_num_scientific.xlsx)
1384
+
1385
+ Demonstrates scientific number formats (0.00E+00).
1386
+
1387
+ ### Rendered Output (LibreOffice Calc)
1388
+
1389
+ <div><img src="../../test/visual/baselines/cell_num_scientific/page-1.png" width="100%" alt="Preview"/></div>
1390
+
1391
+ ### DSL Code
1392
+
1393
+ ```ruby
1394
+ # frozen_string_literal: true
1395
+ require "xlsxrb"
1396
+ output_path = ARGV[0] || "cell_num_scientific.xlsx"
1397
+ Xlsxrb.generate(output_path) do |w|
1398
+ w.add_style("sci") { |s| s.num_fmt("0.00E+00") }
1399
+ w.add_sheet("Scientific") do |s|
1400
+ s.set_column(0, width: 25)
1401
+ s.set_column(1, width: 25)
1402
+ s.add_row(["Format", "Value"])
1403
+ s.add_row(["Scientific", 123456789.0], styles: { 1 => "sci" })
1404
+ s.add_row(["Small Scientific", 0.00001234], styles: { 1 => "sci" })
1405
+ end
1406
+ end
1407
+
1408
+ # 2. Read the generated sheet and print parsed cell numbers and format codes
1409
+ puts "=== Read Validation ==="
1410
+ workbook = Xlsxrb.read(output_path)
1411
+ sheet = workbook.sheets.first
1412
+ sheet.rows.each do |row|
1413
+ row_cells = row.cells.map do |c|
1414
+ xf = workbook.styles[:cell_xfs][c.style_index] if c.style_index
1415
+ num_fmt = xf ? workbook.styles[:num_fmts][xf[:num_fmt_id]] : nil
1416
+ "#{c.ref}: #{c.value.inspect} (Format ID: #{xf&.[](:num_fmt_id)}, Code: #{num_fmt.inspect})"
1417
+ end
1418
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
1419
+ end
1420
+ ```
1421
+
1422
+ ### Console Output
1423
+
1424
+ ```text
1425
+ === Read Validation ===
1426
+ Row 0: A1: "Format" (Format ID: , Code: nil), B1: "Value" (Format ID: , Code: nil)
1427
+ Row 1: A2: "Scientific" (Format ID: , Code: nil), B2: 123456789.0 (Format ID: 164, Code: "0.00E+00")
1428
+ Row 2: A3: "Small Scientific" (Format ID: , Code: nil), B3: 1.234e-05 (Format ID: 164, Code: "0.00E+00")
1429
+ ```
1430
+
1431
+ <hr/>
1432
+
1433
+ ## Cell Numbers
1434
+
1435
+ [Download .xlsx](files/cell_numbers.xlsx)
1436
+
1437
+ Demonstrates custom formatting for integers, floating point numbers, currencies, and percentages.
1438
+
1439
+ ### Rendered Output (LibreOffice Calc)
1440
+
1441
+ <div><img src="../../test/visual/baselines/cell_numbers/page-1.png" width="100%" alt="Preview"/></div>
1442
+
1443
+ ### DSL Code
1444
+
1445
+ ```ruby
1446
+ # frozen_string_literal: true
1447
+ require "xlsxrb"
1448
+ output_path = ARGV[0] || "cell_numbers.xlsx"
1449
+ Xlsxrb.generate(output_path) do |w|
1450
+ w.add_style("currency") { |s| s.num_fmt("$#,##0.00") }
1451
+ w.add_style("percent") { |s| s.num_fmt("0.0%") }
1452
+ w.add_sheet("Numbers") do |s|
1453
+ s.set_column(0, width: 25)
1454
+ s.set_column(1, width: 25)
1455
+ s.add_row(["Format", "Value"])
1456
+ s.add_row(["Integer", 12345])
1457
+ s.add_row(["Float", 123.456])
1458
+ s.add_row(["Currency", 1234.5], styles: { 1 => "currency" })
1459
+ s.add_row(["Percentage", 0.85], styles: { 1 => "percent" })
1460
+ end
1461
+ end
1462
+
1463
+ # 2. Read the generated sheet and print parsed cell values and Ruby classes
1464
+ puts "=== Read Validation ==="
1465
+ workbook = Xlsxrb.read(output_path)
1466
+ sheet = workbook.sheets.first
1467
+ sheet.rows.each do |row|
1468
+ row_cells = row.cells.map do |c|
1469
+ "#{c.ref}: #{c.value.inspect} (#{c.value.class})"
1470
+ end
1471
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
1472
+ end
1473
+ ```
1474
+
1475
+ ### Console Output
1476
+
1477
+ ```text
1478
+ === Read Validation ===
1479
+ Row 0: A1: "Format" (String), B1: "Value" (String)
1480
+ Row 1: A2: "Integer" (String), B2: 12345 (Integer)
1481
+ Row 2: A3: "Float" (String), B3: 123.456 (Float)
1482
+ Row 3: A4: "Currency" (String), B4: 1234.5 (Float)
1483
+ Row 4: A5: "Percentage" (String), B5: 0.85 (Float)
1484
+ ```
1485
+
1486
+ <hr/>
1487
+
1488
+ ## Cell Rich Text
1489
+
1490
+ [Download .xlsx](files/cell_rich_text.xlsx)
1491
+
1492
+ Demonstrates Rich Text cells with multiple font weights, styles, and colors in a single cell.
1493
+
1494
+ ### Rendered Output (LibreOffice Calc)
1495
+
1496
+ <div><img src="../../test/visual/baselines/cell_rich_text/page-1.png" width="100%" alt="Preview"/></div>
1497
+
1498
+ ### DSL Code
1499
+
1500
+ ```ruby
1501
+ # frozen_string_literal: true
1502
+ require "xlsxrb"
1503
+ output_path = ARGV[0] || "cell_rich_text.xlsx"
1504
+ Xlsxrb.generate(output_path) do |w|
1505
+ rt = Xlsxrb::Elements::RichText.new(runs: [
1506
+ { text: "Normal " },
1507
+ { text: "BOLD RED ", font: { bold: true, color: "FFC00000", sz: 16 } },
1508
+ { text: "ITALIC BLUE", font: { italic: true, color: "FF0000FF", sz: 20 } }
1509
+ ])
1510
+ w.add_sheet("Rich Text") do |s|
1511
+ s.set_column(0, width: 25)
1512
+ s.set_column(1, width: 25)
1513
+ s.add_row(["Format", "Value"])
1514
+ s.add_row(["Rich Text Cell", rt])
1515
+ end
1516
+ end
1517
+
1518
+ # 2. Read the generated sheet and print parsed cell values and Ruby classes
1519
+ puts "=== Read Validation ==="
1520
+ workbook = Xlsxrb.read(output_path)
1521
+ sheet = workbook.sheets.first
1522
+ sheet.rows.each do |row|
1523
+ row_cells = row.cells.map do |c|
1524
+ "#{c.ref}: #{c.value.inspect} (#{c.value.class})"
1525
+ end
1526
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
1527
+ end
1528
+ ```
1529
+
1530
+ ### Console Output
1531
+
1532
+ ```text
1533
+ === Read Validation ===
1534
+ Row 0: A1: "Format" (String), B1: "Value" (String)
1535
+ Row 1: A2: "Rich Text Cell" (String), B2: "Normal BOLD RED ITALIC BLUE" (String)
1536
+ ```
1537
+
1538
+ <hr/>
1539
+
1540
+ ## Cell Times
1541
+
1542
+ [Download .xlsx](files/cell_times.xlsx)
1543
+
1544
+ Demonstrates timestamp values serialized natively and formatted showing hours, minutes, and seconds.
1545
+
1546
+ ### Rendered Output (LibreOffice Calc)
1547
+
1548
+ <div><img src="../../test/visual/baselines/cell_times/page-1.png" width="100%" alt="Preview"/></div>
1549
+
1550
+ ### DSL Code
1551
+
1552
+ ```ruby
1553
+ # frozen_string_literal: true
1554
+ require "xlsxrb"
1555
+ require "time"
1556
+ output_path = ARGV[0] || "cell_times.xlsx"
1557
+ Xlsxrb.generate(output_path) do |w|
1558
+ w.add_style("time_fmt") { |s| s.num_fmt("hh:mm:ss") }
1559
+ w.add_sheet("Times") do |s|
1560
+ s.set_column(0, width: 25)
1561
+ s.set_column(1, width: 25)
1562
+ s.add_row(["Format", "Time Value"])
1563
+ s.add_row(["DateTime", Time.new(2026, 7, 1, 12, 34, 56)])
1564
+ s.add_row(["Time Only", Time.new(2026, 7, 1, 9, 15, 0)], styles: { 1 => "time_fmt" })
1565
+ end
1566
+ end
1567
+
1568
+ # 2. Read the generated sheet and print parsed cell values and Ruby classes
1569
+ puts "=== Read Validation ==="
1570
+ workbook = Xlsxrb.read(output_path)
1571
+ sheet = workbook.sheets.first
1572
+ sheet.rows.each do |row|
1573
+ row_cells = row.cells.map do |c|
1574
+ "#{c.ref}: #{c.value.inspect} (#{c.value.class})"
1575
+ end
1576
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
1577
+ end
1578
+ ```
1579
+
1580
+ ### Console Output
1581
+
1582
+ ```text
1583
+ === Read Validation ===
1584
+ Row 0: A1: "Format" (String), B1: "Time Value" (String)
1585
+ Row 1: A2: "DateTime" (String), B2: 46204.52425925926 (Float)
1586
+ Row 2: A3: "Time Only" (String), B3: 46204.385416666664 (Float)
1587
+ ```
1588
+
1589
+ <hr/>
1590
+
1591
+ ## Cf Begins With
1592
+
1593
+ [Download .xlsx](files/cf_begins_with.xlsx)
1594
+
1595
+ Demonstrates conditional formatting highlighting cells starting with specific text.
1596
+
1597
+ ### Rendered Output (LibreOffice Calc)
1598
+
1599
+ <div><img src="../../test/visual/baselines/cf_begins_with/page-1.png" width="100%" alt="Preview"/></div>
1600
+
1601
+ ### DSL Code
1602
+
1603
+ ```ruby
1604
+ # frozen_string_literal: true
1605
+ require "xlsxrb"
1606
+ output_path = ARGV[0] || "cf_begins_with.xlsx"
1607
+ Xlsxrb.generate(output_path) do |w|
1608
+ w.add_sheet("CF Begins") do |s|
1609
+ s.set_column(0, width: 25)
1610
+ s.set_column(1, width: 25)
1611
+ s.add_row(["Code"])
1612
+ s.add_row(["A-100"])
1613
+ s.add_row(["B-200"])
1614
+ s.add_row(["A-300"])
1615
+ s.add_conditional_format("A2:A4", type: "beginsWith", operator: "beginsWith", text: "A", formula: 'LEFT(A2,1)="A"', fill_color: "FFFFC7CE")
1616
+ end
1617
+ end
1618
+
1619
+ # 2. Read the generated sheet and print cell values
1620
+ puts "=== Read Validation ==="
1621
+ workbook = Xlsxrb.read(output_path)
1622
+ sheet = workbook.sheets.first
1623
+ sheet.rows.first(5).each do |row|
1624
+ row_cells = row.cells.map { |c| "#{c.ref}: #{c.value.inspect}" }
1625
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
1626
+ end
1627
+ ```
1628
+
1629
+ ### Console Output
1630
+
1631
+ ```text
1632
+ === Read Validation ===
1633
+ Row 0: A1: "Code"
1634
+ Row 1: A2: "A-100"
1635
+ Row 2: A3: "B-200"
1636
+ Row 3: A4: "A-300"
1637
+ ```
1638
+
1639
+ <hr/>
1640
+
1641
+ ## Cf Cell Between
1642
+
1643
+ [Download .xlsx](files/cf_cell_between.xlsx)
1644
+
1645
+ Demonstrates conditional formatting highlighting cells within a range.
1646
+
1647
+ ### Rendered Output (LibreOffice Calc)
1648
+
1649
+ <div><img src="../../test/visual/baselines/cf_cell_between/page-1.png" width="100%" alt="Preview"/></div>
1650
+
1651
+ ### DSL Code
1652
+
1653
+ ```ruby
1654
+ # frozen_string_literal: true
1655
+ require "xlsxrb"
1656
+ output_path = ARGV[0] || "cf_cell_between.xlsx"
1657
+ Xlsxrb.generate(output_path) do |w|
1658
+ w.add_style("center") { |style| style.align_horizontal("center") }
1659
+ w.add_sheet("CF Between") do |s|
1660
+ s.set_column(0, width: 25)
1661
+ s.set_column(1, width: 25)
1662
+ s.add_row(["Values"])
1663
+ s.add_row([15], styles: ["center"])
1664
+ s.add_row([25], styles: ["center"])
1665
+ s.add_row([5], styles: ["center"])
1666
+ s.add_conditional_format("A2:A4", type: "cellIs", operator: "between", formulas: ["10", "20"], fill_color: "FF00FF00")
1667
+ end
1668
+ end
1669
+
1670
+ # 2. Read the generated sheet and print cell values
1671
+ puts "=== Read Validation ==="
1672
+ workbook = Xlsxrb.read(output_path)
1673
+ sheet = workbook.sheets.first
1674
+ sheet.rows.first(5).each do |row|
1675
+ row_cells = row.cells.map { |c| "#{c.ref}: #{c.value.inspect}" }
1676
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
1677
+ end
1678
+ ```
1679
+
1680
+ ### Console Output
1681
+
1682
+ ```text
1683
+ === Read Validation ===
1684
+ Row 0: A1: "Values"
1685
+ Row 1: A2: 15
1686
+ Row 2: A3: 25
1687
+ Row 3: A4: 5
1688
+ ```
1689
+
1690
+ <hr/>
1691
+
1692
+ ## Cf Cell Equal To
1693
+
1694
+ [Download .xlsx](files/cf_cell_equal_to.xlsx)
1695
+
1696
+ Demonstrates conditional formatting highlighting cells equal to a target value.
1697
+
1698
+ ### Rendered Output (LibreOffice Calc)
1699
+
1700
+ <div><img src="../../test/visual/baselines/cf_cell_equal_to/page-1.png" width="100%" alt="Preview"/></div>
1701
+
1702
+ ### DSL Code
1703
+
1704
+ ```ruby
1705
+ # frozen_string_literal: true
1706
+ require "xlsxrb"
1707
+ output_path = ARGV[0] || "cf_cell_equal_to.xlsx"
1708
+ Xlsxrb.generate(output_path) do |w|
1709
+ w.add_style("center") { |style| style.align_horizontal("center") }
1710
+ w.add_sheet("CF Equal") do |s|
1711
+ s.set_column(0, width: 25)
1712
+ s.set_column(1, width: 25)
1713
+ s.add_row(["Values"])
1714
+ s.add_row([10], styles: ["center"])
1715
+ s.add_row([20], styles: ["center"])
1716
+ s.add_row([10], styles: ["center"])
1717
+ s.add_conditional_format("A2:A4", type: "cellIs", operator: "equal", formula: "10", fill_color: "FFFFC7CE")
1718
+ end
1719
+ end
1720
+
1721
+ # 2. Read the generated sheet and print cell values
1722
+ puts "=== Read Validation ==="
1723
+ workbook = Xlsxrb.read(output_path)
1724
+ sheet = workbook.sheets.first
1725
+ sheet.rows.first(5).each do |row|
1726
+ row_cells = row.cells.map { |c| "#{c.ref}: #{c.value.inspect}" }
1727
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
1728
+ end
1729
+ ```
1730
+
1731
+ ### Console Output
1732
+
1733
+ ```text
1734
+ === Read Validation ===
1735
+ Row 0: A1: "Values"
1736
+ Row 1: A2: 10
1737
+ Row 2: A3: 20
1738
+ Row 3: A4: 10
1739
+ ```
1740
+
1741
+ <hr/>
1742
+
1743
+ ## Cf Cell Greater Equal
1744
+
1745
+ [Download .xlsx](files/cf_cell_greater_equal.xlsx)
1746
+
1747
+ Demonstrates conditional formatting highlighting cells greater than or equal to a threshold.
1748
+
1749
+ ### Rendered Output (LibreOffice Calc)
1750
+
1751
+ <div><img src="../../test/visual/baselines/cf_cell_greater_equal/page-1.png" width="100%" alt="Preview"/></div>
1752
+
1753
+ ### DSL Code
1754
+
1755
+ ```ruby
1756
+ # frozen_string_literal: true
1757
+ require "xlsxrb"
1758
+ output_path = ARGV[0] || "cf_cell_greater_equal.xlsx"
1759
+ Xlsxrb.generate(output_path) do |w|
1760
+ w.add_style("center") { |style| style.align_horizontal("center") }
1761
+ w.add_sheet("CF Greater Equal") do |s|
1762
+ s.set_column(0, width: 25)
1763
+ s.set_column(1, width: 25)
1764
+ s.add_row(["Values"])
1765
+ s.add_row([10], styles: ["center"])
1766
+ s.add_row([50], styles: ["center"])
1767
+ s.add_row([100], styles: ["center"])
1768
+ s.add_conditional_format("A2:A4", type: "cellIs", operator: "greaterThanOrEqual", formula: "50", fill_color: "FFFFC7CE")
1769
+ end
1770
+ end
1771
+
1772
+ # 2. Read the generated sheet and print cell values
1773
+ puts "=== Read Validation ==="
1774
+ workbook = Xlsxrb.read(output_path)
1775
+ sheet = workbook.sheets.first
1776
+ sheet.rows.first(5).each do |row|
1777
+ row_cells = row.cells.map { |c| "#{c.ref}: #{c.value.inspect}" }
1778
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
1779
+ end
1780
+ ```
1781
+
1782
+ ### Console Output
1783
+
1784
+ ```text
1785
+ === Read Validation ===
1786
+ Row 0: A1: "Values"
1787
+ Row 1: A2: 10
1788
+ Row 2: A3: 50
1789
+ Row 3: A4: 100
1790
+ ```
1791
+
1792
+ <hr/>
1793
+
1794
+ ## Cf Cell Greater Than
1795
+
1796
+ [Download .xlsx](files/cf_cell_greater_than.xlsx)
1797
+
1798
+ Demonstrates conditional formatting highlighting cells greater than a threshold.
1799
+
1800
+ ### Rendered Output (LibreOffice Calc)
1801
+
1802
+ <div><img src="../../test/visual/baselines/cf_cell_greater_than/page-1.png" width="100%" alt="Preview"/></div>
1803
+
1804
+ ### DSL Code
1805
+
1806
+ ```ruby
1807
+ # frozen_string_literal: true
1808
+ require "xlsxrb"
1809
+ output_path = ARGV[0] || "cf_cell_greater_than.xlsx"
1810
+ Xlsxrb.generate(output_path) do |w|
1811
+ w.add_style("center") { |style| style.align_horizontal("center") }
1812
+ w.add_sheet("CF Greater") do |s|
1813
+ s.set_column(0, width: 25)
1814
+ s.set_column(1, width: 25)
1815
+ s.add_row(["Values"])
1816
+ s.add_row([25], styles: ["center"])
1817
+ s.add_row([75], styles: ["center"])
1818
+ s.add_row([10], styles: ["center"])
1819
+ s.add_conditional_format("A2:A4", type: "cellIs", operator: "greaterThan", formula: "50", fill_color: "FFFFC7CE")
1820
+ end
1821
+ end
1822
+
1823
+ # 2. Read the generated sheet and print cell values
1824
+ puts "=== Read Validation ==="
1825
+ workbook = Xlsxrb.read(output_path)
1826
+ sheet = workbook.sheets.first
1827
+ sheet.rows.first(5).each do |row|
1828
+ row_cells = row.cells.map { |c| "#{c.ref}: #{c.value.inspect}" }
1829
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
1830
+ end
1831
+ ```
1832
+
1833
+ ### Console Output
1834
+
1835
+ ```text
1836
+ === Read Validation ===
1837
+ Row 0: A1: "Values"
1838
+ Row 1: A2: 25
1839
+ Row 2: A3: 75
1840
+ Row 3: A4: 10
1841
+ ```
1842
+
1843
+ <hr/>
1844
+
1845
+ ## Cf Cell Less Than
1846
+
1847
+ [Download .xlsx](files/cf_cell_less_than.xlsx)
1848
+
1849
+ Demonstrates conditional formatting highlighting cells less than a threshold.
1850
+
1851
+ ### Rendered Output (LibreOffice Calc)
1852
+
1853
+ <div><img src="../../test/visual/baselines/cf_cell_less_than/page-1.png" width="100%" alt="Preview"/></div>
1854
+
1855
+ ### DSL Code
1856
+
1857
+ ```ruby
1858
+ # frozen_string_literal: true
1859
+ require "xlsxrb"
1860
+ output_path = ARGV[0] || "cf_cell_less_than.xlsx"
1861
+ Xlsxrb.generate(output_path) do |w|
1862
+ w.add_style("center") { |style| style.align_horizontal("center") }
1863
+ w.add_sheet("CF Less") do |s|
1864
+ s.set_column(0, width: 25)
1865
+ s.set_column(1, width: 25)
1866
+ s.add_row(["Values"])
1867
+ s.add_row([25], styles: ["center"])
1868
+ s.add_row([75], styles: ["center"])
1869
+ s.add_row([10], styles: ["center"])
1870
+ s.add_conditional_format("A2:A4", type: "cellIs", operator: "lessThan", formula: "20", fill_color: "FFFFC7CE")
1871
+ end
1872
+ end
1873
+
1874
+ # 2. Read the generated sheet and print cell values
1875
+ puts "=== Read Validation ==="
1876
+ workbook = Xlsxrb.read(output_path)
1877
+ sheet = workbook.sheets.first
1878
+ sheet.rows.first(5).each do |row|
1879
+ row_cells = row.cells.map { |c| "#{c.ref}: #{c.value.inspect}" }
1880
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
1881
+ end
1882
+ ```
1883
+
1884
+ ### Console Output
1885
+
1886
+ ```text
1887
+ === Read Validation ===
1888
+ Row 0: A1: "Values"
1889
+ Row 1: A2: 25
1890
+ Row 2: A3: 75
1891
+ Row 3: A4: 10
1892
+ ```
1893
+
1894
+ <hr/>
1895
+
1896
+ ## Cf Color Scale
1897
+
1898
+ [Download .xlsx](files/cf_color_scale.xlsx)
1899
+
1900
+ Demonstrates color scale/heatmap conditional formatting.
1901
+
1902
+ ### Rendered Output (LibreOffice Calc)
1903
+
1904
+ <div><img src="../../test/visual/baselines/cf_color_scale/page-1.png" width="100%" alt="Preview"/></div>
1905
+
1906
+ ### DSL Code
1907
+
1908
+ ```ruby
1909
+ # frozen_string_literal: true
1910
+ require "xlsxrb"
1911
+ output_path = ARGV[0] || "cf_color_scale.xlsx"
1912
+ Xlsxrb.generate(output_path) do |w|
1913
+ w.add_style("center") { |style| style.align_horizontal("center") }
1914
+ w.add_sheet("Colors") do |s|
1915
+ s.set_column(0, width: 25)
1916
+ s.set_column(1, width: 25)
1917
+ s.add_row([10], styles: ["center"])
1918
+ s.add_row([50], styles: ["center"])
1919
+ s.add_row([90], styles: ["center"])
1920
+ s.add_conditional_format("A1:A3", type: :colorScale, priority: 1)
1921
+ end
1922
+ end
1923
+
1924
+ # 2. Read the generated sheet and print cell values
1925
+ puts "=== Read Validation ==="
1926
+ workbook = Xlsxrb.read(output_path)
1927
+ sheet = workbook.sheets.first
1928
+ sheet.rows.first(5).each do |row|
1929
+ row_cells = row.cells.map { |c| "#{c.ref}: #{c.value.inspect}" }
1930
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
1931
+ end
1932
+ ```
1933
+
1934
+ ### Console Output
1935
+
1936
+ ```text
1937
+ === Read Validation ===
1938
+ Row 0: A1: 10
1939
+ Row 1: A2: 50
1940
+ Row 2: A3: 90
1941
+ ```
1942
+
1943
+ <hr/>
1944
+
1945
+ ## Cf Contains Text
1946
+
1947
+ [Download .xlsx](files/cf_contains_text.xlsx)
1948
+
1949
+ Demonstrates conditional formatting highlighting cells containing specific text.
1950
+
1951
+ ### Rendered Output (LibreOffice Calc)
1952
+
1953
+ <div><img src="../../test/visual/baselines/cf_contains_text/page-1.png" width="100%" alt="Preview"/></div>
1954
+
1955
+ ### DSL Code
1956
+
1957
+ ```ruby
1958
+ # frozen_string_literal: true
1959
+ require "xlsxrb"
1960
+ output_path = ARGV[0] || "cf_contains_text.xlsx"
1961
+ Xlsxrb.generate(output_path) do |w|
1962
+ w.add_sheet("CF Contains") do |s|
1963
+ s.set_column(0, width: 25)
1964
+ s.set_column(1, width: 25)
1965
+ s.add_row(["Status"])
1966
+ s.add_row(["Error"])
1967
+ s.add_row(["Success"])
1968
+ s.add_row(["Pending"])
1969
+ s.add_conditional_format("A2:A4", type: "containsText", operator: "containsText", text: "Error", formula: 'NOT(ISERROR(SEARCH("Error",A2)))', fill_color: "FFFF0000")
1970
+ end
1971
+ end
1972
+
1973
+ # 2. Read the generated sheet and print cell values
1974
+ puts "=== Read Validation ==="
1975
+ workbook = Xlsxrb.read(output_path)
1976
+ sheet = workbook.sheets.first
1977
+ sheet.rows.first(5).each do |row|
1978
+ row_cells = row.cells.map { |c| "#{c.ref}: #{c.value.inspect}" }
1979
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
1980
+ end
1981
+ ```
1982
+
1983
+ ### Console Output
1984
+
1985
+ ```text
1986
+ === Read Validation ===
1987
+ Row 0: A1: "Status"
1988
+ Row 1: A2: "Error"
1989
+ Row 2: A3: "Success"
1990
+ Row 3: A4: "Pending"
1991
+ ```
1992
+
1993
+ <hr/>
1994
+
1995
+ ## Cf Data Bar
1996
+
1997
+ [Download .xlsx](files/cf_data_bar.xlsx)
1998
+
1999
+ Demonstrates data bar visual conditional formatting indicators.
2000
+
2001
+ ### Rendered Output (LibreOffice Calc)
2002
+
2003
+ <div><img src="../../test/visual/baselines/cf_data_bar/page-1.png" width="100%" alt="Preview"/></div>
2004
+
2005
+ ### DSL Code
2006
+
2007
+ ```ruby
2008
+ # frozen_string_literal: true
2009
+ require "xlsxrb"
2010
+ output_path = ARGV[0] || "cf_data_bar.xlsx"
2011
+ Xlsxrb.generate(output_path) do |w|
2012
+ w.add_style("center") { |style| style.align_horizontal("center") }
2013
+ w.add_sheet("Data Bars") do |s|
2014
+ s.set_column(0, width: 25)
2015
+ s.set_column(1, width: 25)
2016
+ s.add_row([20], styles: ["center"])
2017
+ s.add_row([60], styles: ["center"])
2018
+ s.add_row([100], styles: ["center"])
2019
+ s.add_conditional_format("A1:A3", type: :dataBar, priority: 1, color: "FF0070C0")
2020
+ end
2021
+ end
2022
+
2023
+ # 2. Read the generated sheet and print cell values
2024
+ puts "=== Read Validation ==="
2025
+ workbook = Xlsxrb.read(output_path)
2026
+ sheet = workbook.sheets.first
2027
+ sheet.rows.first(5).each do |row|
2028
+ row_cells = row.cells.map { |c| "#{c.ref}: #{c.value.inspect}" }
2029
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
2030
+ end
2031
+ ```
2032
+
2033
+ ### Console Output
2034
+
2035
+ ```text
2036
+ === Read Validation ===
2037
+ Row 0: A1: 20
2038
+ Row 1: A2: 60
2039
+ Row 2: A3: 100
2040
+ ```
2041
+
2042
+ <hr/>
2043
+
2044
+ ## Cf Ends With
2045
+
2046
+ [Download .xlsx](files/cf_ends_with.xlsx)
2047
+
2048
+ Demonstrates conditional formatting highlighting cells ending with specific text.
2049
+
2050
+ ### Rendered Output (LibreOffice Calc)
2051
+
2052
+ <div><img src="../../test/visual/baselines/cf_ends_with/page-1.png" width="100%" alt="Preview"/></div>
2053
+
2054
+ ### DSL Code
2055
+
2056
+ ```ruby
2057
+ # frozen_string_literal: true
2058
+ require "xlsxrb"
2059
+ output_path = ARGV[0] || "cf_ends_with.xlsx"
2060
+ Xlsxrb.generate(output_path) do |w|
2061
+ w.add_sheet("CF Ends") do |s|
2062
+ s.set_column(0, width: 25)
2063
+ s.set_column(1, width: 25)
2064
+ s.add_row(["Code"])
2065
+ s.add_row(["100-Z"])
2066
+ s.add_row(["200-Y"])
2067
+ s.add_row(["300-Z"])
2068
+ s.add_conditional_format("A2:A4", type: "endsWith", operator: "endsWith", text: "Z", formula: 'RIGHT(A2,1)="Z"', fill_color: "FFFFC7CE")
2069
+ end
2070
+ end
2071
+
2072
+ # 2. Read the generated sheet and print cell values
2073
+ puts "=== Read Validation ==="
2074
+ workbook = Xlsxrb.read(output_path)
2075
+ sheet = workbook.sheets.first
2076
+ sheet.rows.first(5).each do |row|
2077
+ row_cells = row.cells.map { |c| "#{c.ref}: #{c.value.inspect}" }
2078
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
2079
+ end
2080
+ ```
2081
+
2082
+ ### Console Output
2083
+
2084
+ ```text
2085
+ === Read Validation ===
2086
+ Row 0: A1: "Code"
2087
+ Row 1: A2: "100-Z"
2088
+ Row 2: A3: "200-Y"
2089
+ Row 3: A4: "300-Z"
2090
+ ```
2091
+
2092
+ <hr/>
2093
+
2094
+ ## Cf Expression Formula
2095
+
2096
+ [Download .xlsx](files/cf_expression_formula.xlsx)
2097
+
2098
+ Demonstrates conditional formatting using a custom formula expression.
2099
+
2100
+ ### Rendered Output (LibreOffice Calc)
2101
+
2102
+ <div><img src="../../test/visual/baselines/cf_expression_formula/page-1.png" width="100%" alt="Preview"/></div>
2103
+
2104
+ ### DSL Code
2105
+
2106
+ ```ruby
2107
+ # frozen_string_literal: true
2108
+ require "xlsxrb"
2109
+ output_path = ARGV[0] || "cf_expression_formula.xlsx"
2110
+ Xlsxrb.generate(output_path) do |w|
2111
+ w.add_style("center") { |style| style.align_horizontal("center") }
2112
+ w.add_sheet("CF Expression") do |s|
2113
+ s.set_column(0, width: 25)
2114
+ s.set_column(1, width: 25)
2115
+ s.add_row(["Values"])
2116
+ s.add_row([10], styles: ["center"])
2117
+ s.add_row([20], styles: ["center"])
2118
+ s.add_row([30], styles: ["center"])
2119
+ s.add_row([100], styles: ["center"]) # Average is 40. 100 is above average.
2120
+ s.add_conditional_format("A2:A5", type: "expression", formula: "A2>AVERAGE($A$2:$A$5)", fill_color: "FFFFC7CE")
2121
+ end
2122
+ end
2123
+
2124
+ # 2. Read the generated sheet and print cell values
2125
+ puts "=== Read Validation ==="
2126
+ workbook = Xlsxrb.read(output_path)
2127
+ sheet = workbook.sheets.first
2128
+ sheet.rows.first(5).each do |row|
2129
+ row_cells = row.cells.map { |c| "#{c.ref}: #{c.value.inspect}" }
2130
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
2131
+ end
2132
+ ```
2133
+
2134
+ ### Console Output
2135
+
2136
+ ```text
2137
+ === Read Validation ===
2138
+ Row 0: A1: "Values"
2139
+ Row 1: A2: 10
2140
+ Row 2: A3: 20
2141
+ Row 3: A4: 30
2142
+ Row 4: A5: 100
2143
+ ```
2144
+
2145
+ <hr/>
2146
+
2147
+ ## Cf Icon Set
2148
+
2149
+ [Download .xlsx](files/cf_icon_set.xlsx)
2150
+
2151
+ Demonstrates icon set indicators (red/yellow/green arrows).
2152
+
2153
+ ### Rendered Output (LibreOffice Calc)
2154
+
2155
+ <div><img src="../../test/visual/baselines/cf_icon_set/page-1.png" width="100%" alt="Preview"/></div>
2156
+
2157
+ ### DSL Code
2158
+
2159
+ ```ruby
2160
+ # frozen_string_literal: true
2161
+ require "xlsxrb"
2162
+ output_path = ARGV[0] || "cf_icon_set.xlsx"
2163
+ Xlsxrb.generate(output_path) do |w|
2164
+ w.add_style("center") { |style| style.align_horizontal("center") }
2165
+ w.add_sheet("Icons") do |s|
2166
+ s.set_column(0, width: 25)
2167
+ s.set_column(1, width: 25)
2168
+ s.add_row([25], styles: ["center"])
2169
+ s.add_row([50], styles: ["center"])
2170
+ s.add_row([75], styles: ["center"])
2171
+ s.add_conditional_format("A1:A3", type: :iconSet, icon_style: "3Arrows", priority: 1)
2172
+ end
2173
+ end
2174
+
2175
+ # 2. Read the generated sheet and print cell values
2176
+ puts "=== Read Validation ==="
2177
+ workbook = Xlsxrb.read(output_path)
2178
+ sheet = workbook.sheets.first
2179
+ sheet.rows.first(5).each do |row|
2180
+ row_cells = row.cells.map { |c| "#{c.ref}: #{c.value.inspect}" }
2181
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
2182
+ end
2183
+ ```
2184
+
2185
+ ### Console Output
2186
+
2187
+ ```text
2188
+ === Read Validation ===
2189
+ Row 0: A1: 25
2190
+ Row 1: A2: 50
2191
+ Row 2: A3: 75
2192
+ ```
2193
+
2194
+ <hr/>
2195
+
2196
+ ## Chart Area
2197
+
2198
+ [Download .xlsx](files/chart_area.xlsx)
2199
+
2200
+ Demonstrates embedding a standard 2D Area Chart.
2201
+
2202
+ ### Rendered Output (LibreOffice Calc)
2203
+
2204
+ <div><img src="../../test/visual/baselines/chart_area/page-1.png" width="100%" alt="Preview"/></div>
2205
+
2206
+ ### DSL Code
2207
+
2208
+ ```ruby
2209
+ # frozen_string_literal: true
2210
+ require "xlsxrb"
2211
+ output_path = ARGV[0] || "chart_area.xlsx"
2212
+ Xlsxrb.generate(output_path) do |w|
2213
+ w.add_sheet("Data") do |s|
2214
+ s.set_sheet_property(:fit_to_page, true)
2215
+ s.set_page_setup(fit_to_width: 1, fit_to_height: 1)
2216
+ s.set_column(0, width: 25)
2217
+ s.set_column(1, width: 25)
2218
+ s.add_row(["Day", "Value"])
2219
+ s.add_row(["Mon", 10])
2220
+ s.add_row(["Tue", 15])
2221
+ s.add_chart(
2222
+ type: :area,
2223
+ title: "Daily Area",
2224
+ from_col: 3, from_row: 0, to_col: 8, to_row: 12,
2225
+ series: [{ cat_ref: "Data!$A$2:$A$3", val_ref: "Data!$B$2:$B$3", fill_color: "4F81BD" }]
2226
+ )
2227
+ end
2228
+ end
2229
+
2230
+ # 2. Read the generated sheet and print the chart count
2231
+ puts "=== Read Validation ==="
2232
+ workbook = Xlsxrb.read(output_path)
2233
+ sheet = workbook.sheets.first
2234
+ puts "Sheet '#{sheet.name}' has #{sheet.charts.size} chart(s)"
2235
+ ```
2236
+
2237
+ ### Console Output
2238
+
2239
+ ```text
2240
+ === Read Validation ===
2241
+ Sheet 'Data' has 0 chart(s)
2242
+ ```
2243
+
2244
+ <hr/>
2245
+
2246
+ ## Chart Area Stacked
2247
+
2248
+ [Download .xlsx](files/chart_area_stacked.xlsx)
2249
+
2250
+ Demonstrates embedding a stacked 2D Area Chart.
2251
+
2252
+ ### Rendered Output (LibreOffice Calc)
2253
+
2254
+ <div><img src="../../test/visual/baselines/chart_area_stacked/page-1.png" width="100%" alt="Preview"/></div>
2255
+
2256
+ ### DSL Code
2257
+
2258
+ ```ruby
2259
+ # frozen_string_literal: true
2260
+ require "xlsxrb"
2261
+ output_path = ARGV[0] || "chart_area_stacked.xlsx"
2262
+ Xlsxrb.generate(output_path) do |w|
2263
+ w.add_sheet("Data") do |s|
2264
+ s.set_column(0, width: 25)
2265
+ s.set_column(1, width: 25)
2266
+ s.add_row(["Year", "Sales A", "Sales B"])
2267
+ s.add_row([2024, 100, 150])
2268
+ s.add_row([2025, 120, 180])
2269
+ s.add_row([2026, 140, 210])
2270
+ s.set_sheet_property(:fit_to_page, true)
2271
+ s.set_page_setup(fit_to_width: 1, fit_to_height: 1)
2272
+ s.add_chart(
2273
+ type: :area,
2274
+ grouping: :stacked,
2275
+ title: "Stacked Area Chart",
2276
+ from_col: 0, from_row: 5, to_col: 6, to_row: 17,
2277
+ series: [
2278
+ { cat_ref: "'Data'!$A$2:$A$4", val_ref: "'Data'!$B$2:$B$4", name: "'Data'!$B$1", fill_color: "4F81BD" },
2279
+ { cat_ref: "'Data'!$A$2:$A$4", val_ref: "'Data'!$C$2:$C$4", name: "'Data'!$C$1", fill_color: "C0504D" }
2280
+ ]
2281
+ )
2282
+ end
2283
+ end
2284
+
2285
+ # 2. Read the generated sheet and print the chart count
2286
+ puts "=== Read Validation ==="
2287
+ workbook = Xlsxrb.read(output_path)
2288
+ sheet = workbook.sheets.first
2289
+ puts "Sheet '#{sheet.name}' has #{sheet.charts.size} chart(s)"
2290
+ ```
2291
+
2292
+ ### Console Output
2293
+
2294
+ ```text
2295
+ === Read Validation ===
2296
+ Sheet 'Data' has 0 chart(s)
2297
+ ```
2298
+
2299
+ <hr/>
2300
+
2301
+ ## Chart Bar
2302
+
2303
+ [Download .xlsx](files/chart_bar.xlsx)
2304
+
2305
+ Demonstrates embedding a standard 2D Bar Chart referencing worksheet cell ranges.
2306
+
2307
+ ### Rendered Output (LibreOffice Calc)
2308
+
2309
+ <div><img src="../../test/visual/baselines/chart_bar/page-1.png" width="100%" alt="Preview"/></div>
2310
+
2311
+ ### DSL Code
2312
+
2313
+ ```ruby
2314
+ # frozen_string_literal: true
2315
+
2316
+ require "xlsxrb"
2317
+
2318
+ output_path = ARGV[0] || "chart_bar.xlsx"
2319
+
2320
+ Xlsxrb.generate(output_path) do |w|
2321
+ w.add_sheet("Sales Data") do |s|
2322
+ s.set_sheet_property(:fit_to_page, true)
2323
+ s.set_page_setup(fit_to_width: 1, fit_to_height: 1)
2324
+ s.set_column(0, width: 25)
2325
+ s.set_column(1, width: 25)
2326
+ s.add_row(["Month", "Value"])
2327
+ s.add_row(["Jan", 100])
2328
+ s.add_row(["Feb", 200])
2329
+
2330
+ s.add_chart(
2331
+ type: :bar,
2332
+ title: "Monthly Sales",
2333
+ from_col: 3,
2334
+ from_row: 0,
2335
+ to_col: 8,
2336
+ to_row: 12,
2337
+ series: [
2338
+ { cat_ref: "'Sales Data'!$A$2:$A$3", val_ref: "'Sales Data'!$B$2:$B$3", fill_color: "4F81BD" }
2339
+ ]
2340
+ )
2341
+ end
2342
+ end
2343
+
2344
+ # 2. Read the generated sheet and print the chart count
2345
+ puts "=== Read Validation ==="
2346
+ workbook = Xlsxrb.read(output_path)
2347
+ sheet = workbook.sheets.first
2348
+ puts "Sheet '#{sheet.name}' has #{sheet.charts.size} chart(s)"
2349
+ ```
2350
+
2351
+ ### Console Output
2352
+
2353
+ ```text
2354
+ === Read Validation ===
2355
+ Sheet 'Sales Data' has 0 chart(s)
2356
+ ```
2357
+
2358
+ <hr/>
2359
+
2360
+ ## Chart Bar Percent Stacked
2361
+
2362
+ [Download .xlsx](files/chart_bar_percent_stacked.xlsx)
2363
+
2364
+ Demonstrates embedding a 100% stacked 2D Bar Chart.
2365
+
2366
+ ### Rendered Output (LibreOffice Calc)
2367
+
2368
+ <div><img src="../../test/visual/baselines/chart_bar_percent_stacked/page-1.png" width="100%" alt="Preview"/></div>
2369
+
2370
+ ### DSL Code
2371
+
2372
+ ```ruby
2373
+ # frozen_string_literal: true
2374
+ require "xlsxrb"
2375
+ output_path = ARGV[0] || "chart_bar_percent_stacked.xlsx"
2376
+ Xlsxrb.generate(output_path) do |w|
2377
+ w.add_sheet("Data") do |s|
2378
+ s.set_column(0, width: 25)
2379
+ s.set_column(1, width: 25)
2380
+ s.add_row(["Year", "Sales A", "Sales B"])
2381
+ s.add_row([2024, 100, 150])
2382
+ s.add_row([2025, 120, 180])
2383
+ s.add_row([2026, 140, 210])
2384
+ s.set_sheet_property(:fit_to_page, true)
2385
+ s.set_page_setup(fit_to_width: 1, fit_to_height: 1)
2386
+ s.add_chart(
2387
+ type: :bar,
2388
+ grouping: :percentStacked,
2389
+ title: "100% Stacked Bar Chart",
2390
+ from_col: 0, from_row: 5, to_col: 6, to_row: 17,
2391
+ series: [
2392
+ { cat_ref: "'Data'!$A$2:$A$4", val_ref: "'Data'!$B$2:$B$4", name: "'Data'!$B$1", fill_color: "4F81BD" },
2393
+ { cat_ref: "'Data'!$A$2:$A$4", val_ref: "'Data'!$C$2:$C$4", name: "'Data'!$C$1", fill_color: "C0504D" }
2394
+ ]
2395
+ )
2396
+ end
2397
+ end
2398
+
2399
+ # 2. Read the generated sheet and print the chart count
2400
+ puts "=== Read Validation ==="
2401
+ workbook = Xlsxrb.read(output_path)
2402
+ sheet = workbook.sheets.first
2403
+ puts "Sheet '#{sheet.name}' has #{sheet.charts.size} chart(s)"
2404
+ ```
2405
+
2406
+ ### Console Output
2407
+
2408
+ ```text
2409
+ === Read Validation ===
2410
+ Sheet 'Data' has 0 chart(s)
2411
+ ```
2412
+
2413
+ <hr/>
2414
+
2415
+ ## Chart Bar Stacked
2416
+
2417
+ [Download .xlsx](files/chart_bar_stacked.xlsx)
2418
+
2419
+ Demonstrates embedding a stacked 2D Bar Chart.
2420
+
2421
+ ### Rendered Output (LibreOffice Calc)
2422
+
2423
+ <div><img src="../../test/visual/baselines/chart_bar_stacked/page-1.png" width="100%" alt="Preview"/></div>
2424
+
2425
+ ### DSL Code
2426
+
2427
+ ```ruby
2428
+ # frozen_string_literal: true
2429
+ require "xlsxrb"
2430
+ output_path = ARGV[0] || "chart_bar_stacked.xlsx"
2431
+ Xlsxrb.generate(output_path) do |w|
2432
+ w.add_sheet("Data") do |s|
2433
+ s.set_column(0, width: 25)
2434
+ s.set_column(1, width: 25)
2435
+ s.add_row(["Year", "Sales A", "Sales B"])
2436
+ s.add_row([2024, 100, 150])
2437
+ s.add_row([2025, 120, 180])
2438
+ s.add_row([2026, 140, 210])
2439
+ s.set_sheet_property(:fit_to_page, true)
2440
+ s.set_page_setup(fit_to_width: 1, fit_to_height: 1)
2441
+ s.add_chart(
2442
+ type: :bar,
2443
+ grouping: :stacked,
2444
+ title: "Stacked Bar Chart",
2445
+ from_col: 0, from_row: 5, to_col: 6, to_row: 17,
2446
+ series: [
2447
+ { cat_ref: "'Data'!$A$2:$A$4", val_ref: "'Data'!$B$2:$B$4", name: "'Data'!$B$1", fill_color: "4F81BD" },
2448
+ { cat_ref: "'Data'!$A$2:$A$4", val_ref: "'Data'!$C$2:$C$4", name: "'Data'!$C$1", fill_color: "C0504D" }
2449
+ ]
2450
+ )
2451
+ end
2452
+ end
2453
+
2454
+ # 2. Read the generated sheet and print the chart count
2455
+ puts "=== Read Validation ==="
2456
+ workbook = Xlsxrb.read(output_path)
2457
+ sheet = workbook.sheets.first
2458
+ puts "Sheet '#{sheet.name}' has #{sheet.charts.size} chart(s)"
2459
+ ```
2460
+
2461
+ ### Console Output
2462
+
2463
+ ```text
2464
+ === Read Validation ===
2465
+ Sheet 'Data' has 0 chart(s)
2466
+ ```
2467
+
2468
+ <hr/>
2469
+
2470
+ ## Chart Doughnut
2471
+
2472
+ [Download .xlsx](files/chart_doughnut.xlsx)
2473
+
2474
+ Demonstrates embedding a standard 2D Doughnut Chart.
2475
+
2476
+ ### Rendered Output (LibreOffice Calc)
2477
+
2478
+ <div><img src="../../test/visual/baselines/chart_doughnut/page-1.png" width="100%" alt="Preview"/></div>
2479
+
2480
+ ### DSL Code
2481
+
2482
+ ```ruby
2483
+ # frozen_string_literal: true
2484
+ require "xlsxrb"
2485
+ output_path = ARGV[0] || "chart_doughnut.xlsx"
2486
+ Xlsxrb.generate(output_path) do |w|
2487
+ w.add_sheet("Data") do |s|
2488
+ s.set_sheet_property(:fit_to_page, true)
2489
+ s.set_page_setup(fit_to_width: 1, fit_to_height: 1)
2490
+ s.set_column(0, width: 25)
2491
+ s.set_column(1, width: 25)
2492
+ s.add_row(["Label", "Percent"])
2493
+ s.add_row(["A", 40])
2494
+ s.add_row(["B", 60])
2495
+ s.add_chart(
2496
+ type: :doughnut,
2497
+ title: "Ratio",
2498
+ from_col: 3, from_row: 0, to_col: 8, to_row: 12,
2499
+ series: [{
2500
+ cat_ref: "Data!$A$2:$A$3",
2501
+ val_ref: "Data!$B$2:$B$3",
2502
+ data_points: [
2503
+ { idx: 0, fill_color: "4F81BD" },
2504
+ { idx: 1, fill_color: "C0504D" }
2505
+ ]
2506
+ }]
2507
+ )
2508
+ end
2509
+ end
2510
+
2511
+ # 2. Read the generated sheet and print the chart count
2512
+ puts "=== Read Validation ==="
2513
+ workbook = Xlsxrb.read(output_path)
2514
+ sheet = workbook.sheets.first
2515
+ puts "Sheet '#{sheet.name}' has #{sheet.charts.size} chart(s)"
2516
+ ```
2517
+
2518
+ ### Console Output
2519
+
2520
+ ```text
2521
+ === Read Validation ===
2522
+ Sheet 'Data' has 0 chart(s)
2523
+ ```
2524
+
2525
+ <hr/>
2526
+
2527
+ ## Chart Line
2528
+
2529
+ [Download .xlsx](files/chart_line.xlsx)
2530
+
2531
+ Demonstrates embedding a standard 2D Line Chart.
2532
+
2533
+ ### Rendered Output (LibreOffice Calc)
2534
+
2535
+ <div><img src="../../test/visual/baselines/chart_line/page-1.png" width="100%" alt="Preview"/></div>
2536
+
2537
+ ### DSL Code
2538
+
2539
+ ```ruby
2540
+ # frozen_string_literal: true
2541
+ require "xlsxrb"
2542
+ output_path = ARGV[0] || "chart_line.xlsx"
2543
+ Xlsxrb.generate(output_path) do |w|
2544
+ w.add_sheet("Data") do |s|
2545
+ s.set_sheet_property(:fit_to_page, true)
2546
+ s.set_page_setup(fit_to_width: 1, fit_to_height: 1)
2547
+ s.set_column(0, width: 25)
2548
+ s.set_column(1, width: 25)
2549
+ s.add_row(["Day", "Value"])
2550
+ s.add_row(["Mon", 10])
2551
+ s.add_row(["Tue", 15])
2552
+ s.add_row(["Wed", 12])
2553
+ s.add_chart(
2554
+ type: :line,
2555
+ title: "Daily Value",
2556
+ from_col: 3, from_row: 0, to_col: 8, to_row: 12,
2557
+ series: [{ cat_ref: "Data!$A$2:$A$4", val_ref: "Data!$B$2:$B$4", line_color: "4F81BD", line_width: 2.0 }]
2558
+ )
2559
+ end
2560
+ end
2561
+
2562
+ # 2. Read the generated sheet and print the chart count
2563
+ puts "=== Read Validation ==="
2564
+ workbook = Xlsxrb.read(output_path)
2565
+ sheet = workbook.sheets.first
2566
+ puts "Sheet '#{sheet.name}' has #{sheet.charts.size} chart(s)"
2567
+ ```
2568
+
2569
+ ### Console Output
2570
+
2571
+ ```text
2572
+ === Read Validation ===
2573
+ Sheet 'Data' has 0 chart(s)
2574
+ ```
2575
+
2576
+ <hr/>
2577
+
2578
+ ## Chart Line Stacked
2579
+
2580
+ [Download .xlsx](files/chart_line_stacked.xlsx)
2581
+
2582
+ Demonstrates embedding a stacked 2D Line Chart.
2583
+
2584
+ ### Rendered Output (LibreOffice Calc)
2585
+
2586
+ <div><img src="../../test/visual/baselines/chart_line_stacked/page-1.png" width="100%" alt="Preview"/></div>
2587
+
2588
+ ### DSL Code
2589
+
2590
+ ```ruby
2591
+ # frozen_string_literal: true
2592
+ require "xlsxrb"
2593
+ output_path = ARGV[0] || "chart_line_stacked.xlsx"
2594
+ Xlsxrb.generate(output_path) do |w|
2595
+ w.add_sheet("Data") do |s|
2596
+ s.set_column(0, width: 25)
2597
+ s.set_column(1, width: 25)
2598
+ s.add_row(["Year", "Sales A", "Sales B"])
2599
+ s.add_row([2024, 100, 150])
2600
+ s.add_row([2025, 120, 180])
2601
+ s.add_row([2026, 140, 210])
2602
+ s.set_sheet_property(:fit_to_page, true)
2603
+ s.set_page_setup(fit_to_width: 1, fit_to_height: 1)
2604
+ s.add_chart(
2605
+ type: :line,
2606
+ grouping: :stacked,
2607
+ title: "Stacked Line Chart",
2608
+ from_col: 0, from_row: 5, to_col: 6, to_row: 17,
2609
+ series: [
2610
+ { cat_ref: "'Data'!$A$2:$A$4", val_ref: "'Data'!$B$2:$B$4", name: "'Data'!$B$1", line_color: "4F81BD", line_width: 2.0 },
2611
+ { cat_ref: "'Data'!$A$2:$A$4", val_ref: "'Data'!$C$2:$C$4", name: "'Data'!$C$1", line_color: "C0504D", line_width: 2.0 }
2612
+ ]
2613
+ )
2614
+ end
2615
+ end
2616
+
2617
+ # 2. Read the generated sheet and print the chart count
2618
+ puts "=== Read Validation ==="
2619
+ workbook = Xlsxrb.read(output_path)
2620
+ sheet = workbook.sheets.first
2621
+ puts "Sheet '#{sheet.name}' has #{sheet.charts.size} chart(s)"
2622
+ ```
2623
+
2624
+ ### Console Output
2625
+
2626
+ ```text
2627
+ === Read Validation ===
2628
+ Sheet 'Data' has 0 chart(s)
2629
+ ```
2630
+
2631
+ <hr/>
2632
+
2633
+ ## Chart Pie
2634
+
2635
+ [Download .xlsx](files/chart_pie.xlsx)
2636
+
2637
+ Demonstrates embedding a standard 2D Pie Chart.
2638
+
2639
+ ### Rendered Output (LibreOffice Calc)
2640
+
2641
+ <div><img src="../../test/visual/baselines/chart_pie/page-1.png" width="100%" alt="Preview"/></div>
2642
+
2643
+ ### DSL Code
2644
+
2645
+ ```ruby
2646
+ # frozen_string_literal: true
2647
+ require "xlsxrb"
2648
+ output_path = ARGV[0] || "chart_pie.xlsx"
2649
+ Xlsxrb.generate(output_path) do |w|
2650
+ w.add_sheet("Data") do |s|
2651
+ s.set_sheet_property(:fit_to_page, true)
2652
+ s.set_page_setup(fit_to_width: 1, fit_to_height: 1)
2653
+ s.set_column(0, width: 25)
2654
+ s.set_column(1, width: 25)
2655
+ s.add_row(["Label", "Percent"])
2656
+ s.add_row(["Yes", 70])
2657
+ s.add_row(["No", 30])
2658
+ s.add_chart(
2659
+ type: :pie,
2660
+ title: "Responses",
2661
+ from_col: 3, from_row: 0, to_col: 8, to_row: 12,
2662
+ series: [{
2663
+ cat_ref: "Data!$A$2:$A$3",
2664
+ val_ref: "Data!$B$2:$B$3",
2665
+ data_points: [
2666
+ { idx: 0, fill_color: "4F81BD" },
2667
+ { idx: 1, fill_color: "C0504D" }
2668
+ ]
2669
+ }]
2670
+ )
2671
+ end
2672
+ end
2673
+
2674
+ # 2. Read the generated sheet and print the chart count
2675
+ puts "=== Read Validation ==="
2676
+ workbook = Xlsxrb.read(output_path)
2677
+ sheet = workbook.sheets.first
2678
+ puts "Sheet '#{sheet.name}' has #{sheet.charts.size} chart(s)"
2679
+ ```
2680
+
2681
+ ### Console Output
2682
+
2683
+ ```text
2684
+ === Read Validation ===
2685
+ Sheet 'Data' has 0 chart(s)
2686
+ ```
2687
+
2688
+ <hr/>
2689
+
2690
+ ## Chart Radar
2691
+
2692
+ [Download .xlsx](files/chart_radar.xlsx)
2693
+
2694
+ Demonstrates embedding a standard 2D Radar Chart.
2695
+
2696
+ ### Rendered Output (LibreOffice Calc)
2697
+
2698
+ <div><img src="../../test/visual/baselines/chart_radar/page-1.png" width="100%" alt="Preview"/></div>
2699
+
2700
+ ### DSL Code
2701
+
2702
+ ```ruby
2703
+ # frozen_string_literal: true
2704
+ require "xlsxrb"
2705
+ output_path = ARGV[0] || "chart_radar.xlsx"
2706
+ Xlsxrb.generate(output_path) do |w|
2707
+ w.add_sheet("Data") do |s|
2708
+ s.set_sheet_property(:fit_to_page, true)
2709
+ s.set_page_setup(fit_to_width: 1, fit_to_height: 1)
2710
+ s.set_column(0, width: 25)
2711
+ s.set_column(1, width: 25)
2712
+ s.add_row(["Stat", "Value"])
2713
+ s.add_row(["Atk", 80])
2714
+ s.add_row(["Def", 60])
2715
+ s.add_chart(
2716
+ type: :radar,
2717
+ title: "Stats",
2718
+ from_col: 3, from_row: 0, to_col: 8, to_row: 12,
2719
+ series: [{ cat_ref: "Data!$A$2:$A$3", val_ref: "Data!$B$2:$B$3", line_color: "4F81BD", line_width: 2.0 }]
2720
+ )
2721
+ end
2722
+ end
2723
+
2724
+ # 2. Read the generated sheet and print the chart count
2725
+ puts "=== Read Validation ==="
2726
+ workbook = Xlsxrb.read(output_path)
2727
+ sheet = workbook.sheets.first
2728
+ puts "Sheet '#{sheet.name}' has #{sheet.charts.size} chart(s)"
2729
+ ```
2730
+
2731
+ ### Console Output
2732
+
2733
+ ```text
2734
+ === Read Validation ===
2735
+ Sheet 'Data' has 0 chart(s)
2736
+ ```
2737
+
2738
+ <hr/>
2739
+
2740
+ ## Chart Scatter
2741
+
2742
+ [Download .xlsx](files/chart_scatter.xlsx)
2743
+
2744
+ Demonstrates embedding a standard 2D Scatter Plot.
2745
+
2746
+ ### Rendered Output (LibreOffice Calc)
2747
+
2748
+ <div><img src="../../test/visual/baselines/chart_scatter/page-1.png" width="100%" alt="Preview"/></div>
2749
+
2750
+ ### DSL Code
2751
+
2752
+ ```ruby
2753
+ # frozen_string_literal: true
2754
+ require "xlsxrb"
2755
+ output_path = ARGV[0] || "chart_scatter.xlsx"
2756
+ Xlsxrb.generate(output_path) do |w|
2757
+ w.add_sheet("Data") do |s|
2758
+ s.set_sheet_property(:fit_to_page, true)
2759
+ s.set_page_setup(fit_to_width: 1, fit_to_height: 1)
2760
+ s.set_column(0, width: 25)
2761
+ s.set_column(1, width: 25)
2762
+ s.add_row(["X", "Y"])
2763
+ s.add_row([1, 10])
2764
+ s.add_row([2, 15])
2765
+ s.add_chart(
2766
+ type: :scatter,
2767
+ title: "Scatter Plot",
2768
+ from_col: 3, from_row: 0, to_col: 8, to_row: 12,
2769
+ series: [{ cat_ref: "Data!$A$2:$A$3", val_ref: "Data!$B$2:$B$3", line_color: "4F81BD", line_width: 2.0 }]
2770
+ )
2771
+ end
2772
+ end
2773
+
2774
+ # 2. Read the generated sheet and print the chart count
2775
+ puts "=== Read Validation ==="
2776
+ workbook = Xlsxrb.read(output_path)
2777
+ sheet = workbook.sheets.first
2778
+ puts "Sheet '#{sheet.name}' has #{sheet.charts.size} chart(s)"
2779
+ ```
2780
+
2781
+ ### Console Output
2782
+
2783
+ ```text
2784
+ === Read Validation ===
2785
+ Sheet 'Data' has 0 chart(s)
2786
+ ```
2787
+
2788
+ <hr/>
2789
+
2790
+ ## Col Grouping
2791
+
2792
+ [Download .xlsx](files/col_grouping.xlsx)
2793
+
2794
+ Demonstrates outline grouping for columns.
2795
+
2796
+ ### Rendered Output (LibreOffice Calc)
2797
+
2798
+ <div><img src="../../test/visual/baselines/col_grouping/page-1.png" width="100%" alt="Preview"/></div>
2799
+
2800
+ ### DSL Code
2801
+
2802
+ ```ruby
2803
+ # frozen_string_literal: true
2804
+ require "xlsxrb"
2805
+ output_path = ARGV[0] || "col_grouping.xlsx"
2806
+ Xlsxrb.generate(output_path) do |w|
2807
+ w.add_style("border") { |style| style.border_all(style: "thin", color: "FF000000") }
2808
+ w.add_sheet("Col Grouping") do |s|
2809
+ s.set_sheet_property(:fit_to_page, true)
2810
+ s.set_page_setup(fit_to_width: 1, fit_to_height: 1)
2811
+ s.set_column(0, width: 25, outline_level: 0)
2812
+ s.set_column(1, width: 25, outline_level: 1)
2813
+ s.set_column(2, width: 25, outline_level: 1)
2814
+ s.add_row(["Col A", "Col B (Grouped)", "Col C (Grouped)"], styles: ["border", "border", "border"])
2815
+ end
2816
+ end
2817
+
2818
+ # 2. Read the generated sheet and print column dimensions
2819
+ puts "=== Read Validation ==="
2820
+ workbook = Xlsxrb.read(output_path)
2821
+ sheet = workbook.sheets.first
2822
+ sheet.columns.each do |col|
2823
+ puts "Column #{col.index}: width=#{col.width}, hidden=#{col.hidden}, outline_level=#{col.outline_level}"
2824
+ end
2825
+ ```
2826
+
2827
+ ### Console Output
2828
+
2829
+ ```text
2830
+ === Read Validation ===
2831
+ Column 0: width=25.0, hidden=false, outline_level=0
2832
+ Column 1: width=25.0, hidden=false, outline_level=1
2833
+ Column 2: width=25.0, hidden=false, outline_level=1
2834
+ ```
2835
+
2836
+ <hr/>
2837
+
2838
+ ## Col Width Tall
2839
+
2840
+ [Download .xlsx](files/col_width_tall.xlsx)
2841
+
2842
+ Demonstrates setting very wide column widths.
2843
+
2844
+ ### Rendered Output (LibreOffice Calc)
2845
+
2846
+ <div><img src="../../test/visual/baselines/col_width_tall/page-1.png" width="100%" alt="Preview"/></div>
2847
+
2848
+ ### DSL Code
2849
+
2850
+ ```ruby
2851
+ # frozen_string_literal: true
2852
+ require "xlsxrb"
2853
+ output_path = ARGV[0] || "col_width_tall.xlsx"
2854
+ Xlsxrb.generate(output_path) do |w|
2855
+ w.add_style("border") { |style| style.border_all(style: "thin", color: "FF000000") }
2856
+ w.add_sheet("Col Width") do |s|
2857
+ s.set_column(0, width: 50)
2858
+ s.set_column(1, width: 10)
2859
+ s.add_row(["Very Wide Column (Width 50)", "Normal (10)"], styles: ["border", "border"])
2860
+ end
2861
+ end
2862
+
2863
+ # 2. Read the generated sheet and print column dimensions
2864
+ puts "=== Read Validation ==="
2865
+ workbook = Xlsxrb.read(output_path)
2866
+ sheet = workbook.sheets.first
2867
+ sheet.columns.each do |col|
2868
+ puts "Column #{col.index}: width=#{col.width}, hidden=#{col.hidden}, outline_level=#{col.outline_level}"
2869
+ end
2870
+ ```
2871
+
2872
+ ### Console Output
2873
+
2874
+ ```text
2875
+ === Read Validation ===
2876
+ Column 0: width=50.0, hidden=false, outline_level=
2877
+ Column 1: width=10.0, hidden=false, outline_level=
2878
+ ```
2879
+
2880
+ <hr/>
2881
+
2882
+ ## Col Widths
2883
+
2884
+ [Download .xlsx](files/col_widths.xlsx)
2885
+
2886
+ Demonstrates setting custom column widths.
2887
+
2888
+ ### Rendered Output (LibreOffice Calc)
2889
+
2890
+ <div><img src="../../test/visual/baselines/col_widths/page-1.png" width="100%" alt="Preview"/></div>
2891
+
2892
+ ### DSL Code
2893
+
2894
+ ```ruby
2895
+ # frozen_string_literal: true
2896
+ require "xlsxrb"
2897
+ output_path = ARGV[0] || "col_widths.xlsx"
2898
+ Xlsxrb.generate(output_path) do |w|
2899
+ w.add_style("border") { |style| style.border_all(style: "thin", color: "FF000000") }
2900
+ w.add_sheet("Widths") do |s|
2901
+ s.set_column(0, width: 30)
2902
+ s.set_column(1, width: 10)
2903
+ s.add_row(["Wide Column A", "Narrow B"], styles: ["border", "border"])
2904
+ end
2905
+ end
2906
+
2907
+ # 2. Read the generated sheet and print column dimensions
2908
+ puts "=== Read Validation ==="
2909
+ workbook = Xlsxrb.read(output_path)
2910
+ sheet = workbook.sheets.first
2911
+ sheet.columns.each do |col|
2912
+ puts "Column #{col.index}: width=#{col.width}, hidden=#{col.hidden}, outline_level=#{col.outline_level}"
2913
+ end
2914
+ ```
2915
+
2916
+ ### Console Output
2917
+
2918
+ ```text
2919
+ === Read Validation ===
2920
+ Column 0: width=30.0, hidden=false, outline_level=
2921
+ Column 1: width=10.0, hidden=false, outline_level=
2922
+ ```
2923
+
2924
+ <hr/>
2925
+
2926
+ ## Conditional Formatting
2927
+
2928
+ [Download .xlsx](files/conditional_formatting.xlsx)
2929
+
2930
+ Demonstrates adding conditional formatting rules that style cells automatically based on value ranges.
2931
+
2932
+ ### Rendered Output (LibreOffice Calc)
2933
+
2934
+ <div><img src="../../test/visual/baselines/conditional_formatting/page-1.png" width="100%" alt="Preview"/></div>
2935
+
2936
+ ### DSL Code
2937
+
2938
+ ```ruby
2939
+ # frozen_string_literal: true
2940
+
2941
+ require "xlsxrb"
2942
+
2943
+ output_path = ARGV[0] || "conditional_formatting.xlsx"
2944
+
2945
+ Xlsxrb.generate(output_path) do |w|
2946
+ w.add_style("center") { |style| style.align_horizontal("center") }
2947
+ w.add_sheet("Scores") do |s|
2948
+ s.set_column(0, width: 25)
2949
+ s.set_column(1, width: 25)
2950
+ s.add_row([90, 45, 72, 88], styles: ["center", "center", "center", "center"])
2951
+
2952
+ s.add_conditional_format("A1:D1",
2953
+ type: :cell_is, operator: :greaterThan,
2954
+ formula: "80", priority: 1,
2955
+ fill_color: "FFFFC7CE")
2956
+ end
2957
+ end
2958
+
2959
+ # 2. Read the generated sheet and print cell values
2960
+ puts "=== Read Validation ==="
2961
+ workbook = Xlsxrb.read(output_path)
2962
+ sheet = workbook.sheets.first
2963
+ sheet.rows.first(5).each do |row|
2964
+ row_cells = row.cells.map { |c| "#{c.ref}: #{c.value.inspect}" }
2965
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
2966
+ end
2967
+ ```
2968
+
2969
+ ### Console Output
2970
+
2971
+ ```text
2972
+ === Read Validation ===
2973
+ Row 0: A1: 90, B1: 45, C1: 72, D1: 88
2974
+ ```
2975
+
2976
+ <hr/>
2977
+
2978
+ ## Embedded Images
2979
+
2980
+ [Download .xlsx](files/embedded_images.xlsx)
2981
+
2982
+ Demonstrates embedding raster PNG images in cell ranges.
2983
+
2984
+ ### Rendered Output (LibreOffice Calc)
2985
+
2986
+ <div><img src="../../test/visual/baselines/embedded_images/page-1.png" width="100%" alt="Preview"/></div>
2987
+
2988
+ ### DSL Code
2989
+
2990
+ ```ruby
2991
+ # frozen_string_literal: true
2992
+ require "xlsxrb"
2993
+ output_path = ARGV[0] || "embedded_images.xlsx"
2994
+
2995
+ require "zlib"
2996
+
2997
+ def make_png(width, height, red, green, blue)
2998
+ png = "\x89PNG\r\n\x1a\n".dup.force_encoding("BINARY")
2999
+ ihdr_data = [width, height, 8, 2, 0, 0, 0].pack("N2C5")
3000
+ ihdr_chunk = "IHDR".dup.force_encoding("BINARY") + ihdr_data
3001
+ ihdr_crc = Zlib.crc32(ihdr_chunk)
3002
+ png << [ihdr_data.bytesize].pack("N") << ihdr_chunk << [ihdr_crc].pack("N")
3003
+ raw_data = Array.new(height) { "\x00".dup.force_encoding("BINARY") + [red, green, blue].pack("C3") * width }.join
3004
+ compressed = Zlib.deflate(raw_data)
3005
+ idat_chunk = "IDAT".dup.force_encoding("BINARY") + compressed
3006
+ idat_crc = Zlib.crc32(idat_chunk)
3007
+ png << [compressed.bytesize].pack("N") << idat_chunk << [idat_crc].pack("N")
3008
+ iend_chunk = "IEND".dup.force_encoding("BINARY")
3009
+ iend_crc = Zlib.crc32(iend_chunk)
3010
+ png << [0].pack("N") << iend_chunk << [iend_crc].pack("N")
3011
+ png
3012
+ end
3013
+
3014
+ dummy_png = make_png(100, 100, 255, 0, 0)
3015
+
3016
+ Xlsxrb.generate(output_path) do |w|
3017
+ w.add_style("center") { |st| st.align_horizontal(:center) }
3018
+ w.add_sheet("Images") do |s|
3019
+ s.add_row(["Logo Target cell:", "", "", "Boundary"], styles: ["left", "center", "center", "center"])
3020
+ s.add_row(["", "", "", ""])
3021
+ s.add_row(["", "", "", ""])
3022
+ s.add_row(["", "", "", ""])
3023
+ s.add_row(["", "", "", "Boundary End"], styles: ["left", "center", "center", "center"])
3024
+ s.add_image(dummy_png, ext: "png", from_col: 1, from_row: 1, to_col: 3, to_row: 5)
3025
+ s.set_column(0, width: 20)
3026
+ s.set_column(1, width: 15)
3027
+ s.set_column(2, width: 15)
3028
+ s.set_column(3, width: 15)
3029
+ end
3030
+ end
3031
+
3032
+ # 2. Read the generated sheet and print cell values + parsed embedded images details
3033
+ puts "=== Read Validation ==="
3034
+ reader = Xlsxrb::Ooxml::Reader.new(output_path)
3035
+ workbook = Xlsxrb.read(output_path)
3036
+ sheet = workbook.sheets.first
3037
+ sheet.rows.first(5).each do |row|
3038
+ row_cells = row.cells.map { |c| "#{c.ref}: #{c.value.inspect}" }
3039
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
3040
+ end
3041
+ images = reader.images(sheet: sheet.name)
3042
+ images.each_with_index do |img, idx|
3043
+ zip_path = File.expand_path(img[:target], "/xl/drawings").sub(%r{^/}, "")
3044
+ img_data = reader.send(:extract_zip_entry, zip_path)
3045
+ is_png = img_data && img_data[0..3] == "\x89PNG".b
3046
+ puts "Image ##{idx + 1}: name='#{img[:name]}', target='#{img[:target]}' -> ZIP path='#{zip_path}', size=#{img_data&.bytesize} bytes, valid_png=#{is_png}, range=Col #{img[:from_col]} Row #{img[:from_row]} to Col #{img[:to_col]} Row #{img[:to_row]}"
3047
+ end
3048
+ ```
3049
+
3050
+ ### Console Output
3051
+
3052
+ ```text
3053
+ === Read Validation ===
3054
+ Row 0: A1: "Logo Target cell:", B1: "", C1: "", D1: "Boundary"
3055
+ Row 1: A2: "", B2: "", C2: "", D2: ""
3056
+ Row 2: A3: "", B3: "", C3: "", D3: ""
3057
+ Row 3: A4: "", B4: "", C4: "", D4: ""
3058
+ Row 4: A5: "", B5: "", C5: "", D5: "Boundary End"
3059
+ Image #1: name='', target='../media/image1.png' -> ZIP path='xl/media/image1.png', size=334 bytes, valid_png=true, range=Col 1 Row 1 to Col 3 Row 5
3060
+ ```
3061
+
3062
+ <hr/>
3063
+
3064
+ ## Fill Gradients
3065
+
3066
+ [Download .xlsx](files/fill_gradients.xlsx)
3067
+
3068
+ Demonstrates linear gradients inside cell backgrounds.
3069
+
3070
+ ### Rendered Output (LibreOffice Calc)
3071
+
3072
+ <div><img src="../../test/visual/baselines/fill_gradients/page-1.png" width="100%" alt="Preview"/></div>
3073
+
3074
+ ### DSL Code
3075
+
3076
+ ```ruby
3077
+ # frozen_string_literal: true
3078
+ require "xlsxrb"
3079
+ output_path = ARGV[0] || "fill_gradients.xlsx"
3080
+ Xlsxrb.generate(output_path) do |w|
3081
+ w.add_style("gradient") do |style|
3082
+ style.fill_gradient(type: "linear", degree: 45, stops: [
3083
+ { position: 0, color: "FFFFFFFF" },
3084
+ { position: 1, color: "FF4F81BD" }
3085
+ ])
3086
+ end
3087
+ w.add_sheet("Gradients") do |s|
3088
+ s.set_column(0, width: 25)
3089
+ s.set_column(1, width: 25)
3090
+ s.add_row(["Normal Cell", "Gradient Cell"], styles: { 1 => "gradient" })
3091
+ end
3092
+ end
3093
+
3094
+ # 2. Read the generated sheet and print cell fill properties
3095
+ puts "=== Read Validation ==="
3096
+ workbook = Xlsxrb.read(output_path)
3097
+ sheet = workbook.sheets.first
3098
+ row = sheet.rows.first
3099
+ row.cells.each do |c|
3100
+ xf = workbook.styles[:cell_xfs][c.style_index] if c.style_index
3101
+ fill = xf ? workbook.styles[:fills][xf[:fill_id]] : nil
3102
+ puts "Cell #{c.ref} ('#{c.value}'): fill pattern = #{fill&.[](:pattern).inspect}, fg_color = #{fill&.[](:fg_color).inspect}"
3103
+ end
3104
+ ```
3105
+
3106
+ ### Console Output
3107
+
3108
+ ```text
3109
+ === Read Validation ===
3110
+ Cell A1 ('Normal Cell'): fill pattern = nil, fg_color = nil
3111
+ Cell B1 ('Gradient Cell'): fill pattern = nil, fg_color = nil
3112
+ ```
3113
+
3114
+ <hr/>
3115
+
3116
+ ## Fill Patterns
3117
+
3118
+ [Download .xlsx](files/fill_patterns.xlsx)
3119
+
3120
+ Demonstrates standard pattern fills (darkGray, darkGrid) in cell backgrounds.
3121
+
3122
+ ### Rendered Output (LibreOffice Calc)
3123
+
3124
+ <div><img src="../../test/visual/baselines/fill_patterns/page-1.png" width="100%" alt="Preview"/></div>
3125
+
3126
+ ### DSL Code
3127
+
3128
+ ```ruby
3129
+ # frozen_string_literal: true
3130
+ require "xlsxrb"
3131
+ output_path = ARGV[0] || "fill_patterns.xlsx"
3132
+ Xlsxrb.generate(output_path) do |w|
3133
+ w.add_style("dark_gray") { |s| s.fill(pattern: "darkGray", fg_color: "FFC0C0C0", bg_color: "FFFFFFFF") }
3134
+ w.add_style("grid_fill") { |s| s.fill(pattern: "darkGrid", fg_color: "FFC0C0C0", bg_color: "FFFFFFFF") }
3135
+ w.add_sheet("Patterns") do |s|
3136
+ s.set_column(0, width: 25)
3137
+ s.set_column(1, width: 25)
3138
+ s.add_row(["Pattern", "Preview"])
3139
+ s.add_row(["Dark Gray", "Pattern Fill"], styles: { 1 => "dark_gray" })
3140
+ s.add_row(["Dark Grid", "Grid Fill"], styles: { 1 => "grid_fill" })
3141
+ end
3142
+ end
3143
+
3144
+ # 2. Read the generated sheet and print cell fill properties
3145
+ puts "=== Read Validation ==="
3146
+ workbook = Xlsxrb.read(output_path)
3147
+ sheet = workbook.sheets.first
3148
+ row = sheet.rows.first
3149
+ row.cells.each do |c|
3150
+ xf = workbook.styles[:cell_xfs][c.style_index] if c.style_index
3151
+ fill = xf ? workbook.styles[:fills][xf[:fill_id]] : nil
3152
+ puts "Cell #{c.ref} ('#{c.value}'): fill pattern = #{fill&.[](:pattern).inspect}, fg_color = #{fill&.[](:fg_color).inspect}"
3153
+ end
3154
+ ```
3155
+
3156
+ ### Console Output
3157
+
3158
+ ```text
3159
+ === Read Validation ===
3160
+ Cell A1 ('Pattern'): fill pattern = nil, fg_color = nil
3161
+ Cell B1 ('Preview'): fill pattern = nil, fg_color = nil
3162
+ ```
3163
+
3164
+ <hr/>
3165
+
3166
+ ## Fill Solid Colors
3167
+
3168
+ [Download .xlsx](files/fill_solid_colors.xlsx)
3169
+
3170
+ Demonstrates solid cell background fill colors.
3171
+
3172
+ ### Rendered Output (LibreOffice Calc)
3173
+
3174
+ <div><img src="../../test/visual/baselines/fill_solid_colors/page-1.png" width="100%" alt="Preview"/></div>
3175
+
3176
+ ### DSL Code
3177
+
3178
+ ```ruby
3179
+ # frozen_string_literal: true
3180
+ require "xlsxrb"
3181
+ output_path = ARGV[0] || "fill_solid_colors.xlsx"
3182
+ Xlsxrb.generate(output_path) do |w|
3183
+ w.add_style("red_fill") { |s| s.fill_color("FFFFC7CE") }
3184
+ w.add_style("green_fill") { |s| s.fill_color("FFC6EFCE") }
3185
+ w.add_sheet("Fills") do |s|
3186
+ s.set_column(0, width: 25)
3187
+ s.set_column(1, width: 25)
3188
+ s.add_row(["Color", "Preview"])
3189
+ s.add_row(["Red", "Red Fill"], styles: { 1 => "red_fill" })
3190
+ s.add_row(["Green", "Green Fill"], styles: { 1 => "green_fill" })
3191
+ end
3192
+ end
3193
+
3194
+ # 2. Read the generated sheet and print cell fill properties
3195
+ puts "=== Read Validation ==="
3196
+ workbook = Xlsxrb.read(output_path)
3197
+ sheet = workbook.sheets.first
3198
+ row = sheet.rows.first
3199
+ row.cells.each do |c|
3200
+ xf = workbook.styles[:cell_xfs][c.style_index] if c.style_index
3201
+ fill = xf ? workbook.styles[:fills][xf[:fill_id]] : nil
3202
+ puts "Cell #{c.ref} ('#{c.value}'): fill pattern = #{fill&.[](:pattern).inspect}, fg_color = #{fill&.[](:fg_color).inspect}"
3203
+ end
3204
+ ```
3205
+
3206
+ ### Console Output
3207
+
3208
+ ```text
3209
+ === Read Validation ===
3210
+ Cell A1 ('Color'): fill pattern = nil, fg_color = nil
3211
+ Cell B1 ('Preview'): fill pattern = nil, fg_color = nil
3212
+ ```
3213
+
3214
+ <hr/>
3215
+
3216
+ ## Fonts
3217
+
3218
+ [Download .xlsx](files/fonts.xlsx)
3219
+
3220
+ Demonstrates cell fonts properties (Arial, Georgia, Courier New, Times New Roman, Tahoma, sizes 10pt/16pt/24pt, red/green/blue colors, bold/italic/underline/double underline/strike-through styles, superscript/subscript).
3221
+
3222
+ ### Rendered Output (LibreOffice Calc)
3223
+
3224
+ <div><img src="../../test/visual/baselines/fonts/page-1.png" width="100%" alt="Preview"/></div>
3225
+
3226
+ ### DSL Code
3227
+
3228
+ ```ruby
3229
+ # frozen_string_literal: true
3230
+
3231
+ require "xlsxrb"
3232
+
3233
+ output_path = ARGV[0] || "fonts.xlsx"
3234
+
3235
+ Xlsxrb.generate(output_path) do |w|
3236
+ # Font Families
3237
+ w.add_style("f_arial") { |s| s.font_name("Arial") }
3238
+ w.add_style("f_times") { |s| s.font_name("Times New Roman") }
3239
+ w.add_style("f_courier") { |s| s.font_name("Courier New") }
3240
+ w.add_style("f_georgia") { |s| s.font_name("Georgia") }
3241
+ w.add_style("f_tahoma") { |s| s.font_name("Tahoma") }
3242
+
3243
+ # Font Sizes
3244
+ w.add_style("sz_10") { |s| s.size(10) }
3245
+ w.add_style("sz_16") { |s| s.size(16) }
3246
+ w.add_style("sz_24") { |s| s.size(24) }
3247
+
3248
+ # Font Colors
3249
+ w.add_style("c_red") { |s| s.font_color("FFC00000") }
3250
+ w.add_style("c_green") { |s| s.font_color("FF008000") }
3251
+ w.add_style("c_blue") { |s| s.font_color("FF0000FF") }
3252
+
3253
+ # Font Styles
3254
+ w.add_style("st_bold") { |s| s.bold }
3255
+ w.add_style("st_italic") { |s| s.italic }
3256
+ w.add_style("st_underline") { |s| s.underline("single") }
3257
+ w.add_style("st_double_u") { |s| s.underline("double") }
3258
+ w.add_style("st_strike") { |s| s.strike }
3259
+
3260
+ # Vertical Alignments (Superscript/Subscript)
3261
+ w.add_style("v_super") { |s| s.vert_align("superscript") }
3262
+ w.add_style("v_sub") { |s| s.vert_align("subscript") }
3263
+
3264
+ w.add_sheet("Fonts") do |s|
3265
+ s.set_column(0, width: 25)
3266
+ s.set_column(1, width: 25)
3267
+ s.set_print_option(:grid_lines, true)
3268
+
3269
+ s.add_row(["Font Feature", "Text Preview"])
3270
+ s.add_row(["Family: Arial", "Arial Text"], styles: { 1 => "f_arial" })
3271
+ s.add_row(["Family: Times New Roman", "Times New Roman"], styles: { 1 => "f_times" })
3272
+ s.add_row(["Family: Courier New", "Courier New Text"], styles: { 1 => "f_courier" })
3273
+ s.add_row(["Family: Georgia", "Georgia Text"], styles: { 1 => "f_georgia" })
3274
+ s.add_row(["Family: Tahoma", "Tahoma Text"], styles: { 1 => "f_tahoma" })
3275
+
3276
+ s.add_row(["Size: 10pt", "10pt Font Size"], styles: { 1 => "sz_10" })
3277
+ s.add_row(["Size: 16pt", "16pt Font Size"], styles: { 1 => "sz_16" })
3278
+ s.add_row(["Size: 24pt", "24pt Font Size"], styles: { 1 => "sz_24" })
3279
+
3280
+ s.add_row(["Color: Red", "Red Text"], styles: { 1 => "c_red" })
3281
+ s.add_row(["Color: Green", "Green Text"], styles: { 1 => "c_green" })
3282
+ s.add_row(["Color: Blue", "Blue Text"], styles: { 1 => "c_blue" })
3283
+
3284
+ s.add_row(["Style: Bold", "Bold Text"], styles: { 1 => "st_bold" })
3285
+ s.add_row(["Style: Italic", "Italic Text"], styles: { 1 => "st_italic" })
3286
+ s.add_row(["Style: Underline", "Underline Text"], styles: { 1 => "st_underline" })
3287
+ s.add_row(["Style: Double Underline", "Double Underline"], styles: { 1 => "st_double_u" })
3288
+ s.add_row(["Style: Strike-through", "Strike-through Text"], styles: { 1 => "st_strike" })
3289
+
3290
+ s.add_row(["Align: Superscript", "x2 (2 is super)"], styles: { 1 => "v_super" })
3291
+ s.add_row(["Align: Subscript", "H2O (2 is sub)"], styles: { 1 => "v_sub" })
3292
+ end
3293
+ end
3294
+
3295
+ # Read check
3296
+ puts "=== Read Validation ==="
3297
+ workbook = Xlsxrb.read(output_path)
3298
+ sheet = workbook.sheets.first
3299
+ sheet.rows.each do |row|
3300
+ row_cells = row.cells.map do |c|
3301
+ xf = workbook.styles[:cell_xfs][c.style_index] if c.style_index
3302
+ font_id = xf ? xf[:font_id] : 0
3303
+ "#{c.ref}: #{c.value.inspect} (font_id: #{font_id})"
3304
+ end
3305
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
3306
+ end
3307
+ ```
3308
+
3309
+ ### Console Output
3310
+
3311
+ ```text
3312
+ === Read Validation ===
3313
+ Row 0: A1: "Font Feature" (font_id: 0), B1: "Text Preview" (font_id: 0)
3314
+ Row 1: A2: "Family: Arial" (font_id: 0), B2: "Arial Text" (font_id: 1)
3315
+ Row 2: A3: "Family: Times New Roman" (font_id: 0), B3: "Times New Roman" (font_id: 2)
3316
+ Row 3: A4: "Family: Courier New" (font_id: 0), B4: "Courier New Text" (font_id: 3)
3317
+ Row 4: A5: "Family: Georgia" (font_id: 0), B5: "Georgia Text" (font_id: 4)
3318
+ Row 5: A6: "Family: Tahoma" (font_id: 0), B6: "Tahoma Text" (font_id: 5)
3319
+ Row 6: A7: "Size: 10pt" (font_id: 0), B7: "10pt Font Size" (font_id: 6)
3320
+ Row 7: A8: "Size: 16pt" (font_id: 0), B8: "16pt Font Size" (font_id: 7)
3321
+ Row 8: A9: "Size: 24pt" (font_id: 0), B9: "24pt Font Size" (font_id: 8)
3322
+ Row 9: A10: "Color: Red" (font_id: 0), B10: "Red Text" (font_id: 9)
3323
+ Row 10: A11: "Color: Green" (font_id: 0), B11: "Green Text" (font_id: 10)
3324
+ Row 11: A12: "Color: Blue" (font_id: 0), B12: "Blue Text" (font_id: 11)
3325
+ Row 12: A13: "Style: Bold" (font_id: 0), B13: "Bold Text" (font_id: 12)
3326
+ Row 13: A14: "Style: Italic" (font_id: 0), B14: "Italic Text" (font_id: 13)
3327
+ Row 14: A15: "Style: Underline" (font_id: 0), B15: "Underline Text" (font_id: 14)
3328
+ Row 15: A16: "Style: Double Underline" (font_id: 0), B16: "Double Underline" (font_id: 15)
3329
+ Row 16: A17: "Style: Strike-through" (font_id: 0), B17: "Strike-through Text" (font_id: 16)
3330
+ Row 17: A18: "Align: Superscript" (font_id: 0), B18: "x2 (2 is super)" (font_id: 17)
3331
+ Row 18: A19: "Align: Subscript" (font_id: 0), B19: "H2O (2 is sub)" (font_id: 18)
3332
+ ```
3333
+
3334
+ <hr/>
3335
+
3336
+ ## Interactive Autofilter
3337
+
3338
+ [Download .xlsx](files/interactive_autofilter.xlsx)
3339
+
3340
+ > [!TIP]
3341
+ > **Interactive Feature**: This example uses interactive Excel behaviors (such as validation dropdowns, comments, or autofilters). Since the static visual preview below represents a printed page layout, please **[Download the .xlsx file](files/interactive_autofilter.xlsx)** and open it in Microsoft Excel, LibreOffice Calc, or Apple Numbers to interact with it!
3342
+
3343
+ Demonstrates enabling auto-filter sorting headers on tables. Download the sheet to interactively filter and sort columns.
3344
+
3345
+ ### Rendered Output (LibreOffice Calc)
3346
+
3347
+ <div><img src="../../test/visual/baselines/interactive_autofilter/page-1.png" width="100%" alt="Preview"/></div>
3348
+
3349
+ <div><img src="../../test/visual/support/illustrations/interactive_autofilter_page-2.png" width="100%" alt="Preview"/></div>
3350
+
3351
+ ### DSL Code
3352
+
3353
+ ```ruby
3354
+ # frozen_string_literal: true
3355
+ require "xlsxrb"
3356
+ output_path = ARGV[0] || "interactive_autofilter.xlsx"
3357
+ Xlsxrb.generate(output_path) do |w|
3358
+ w.add_sheet("Filter") do |s|
3359
+ s.set_column(0, width: 25)
3360
+ s.set_column(1, width: 25)
3361
+ s.add_row(["Name", "Department"])
3362
+ s.add_row(["Alice", "HR"])
3363
+ s.add_row(["Bob", "Eng"])
3364
+ s.set_auto_filter("A1:B3")
3365
+ end
3366
+ w.add_defined_name("_xlnm._FilterDatabase", "Filter!$A$1:$B$3", sheet: "Filter", hidden: true)
3367
+ end
3368
+
3369
+ # 2. Read the generated sheet and print cell values
3370
+ puts "=== Read Validation ==="
3371
+ workbook = Xlsxrb.read(output_path)
3372
+ sheet = workbook.sheets.first
3373
+ sheet.rows.first(5).each do |row|
3374
+ row_cells = row.cells.map { |c| "#{c.ref}: #{c.value.inspect}" }
3375
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
3376
+ end
3377
+ ```
3378
+
3379
+ ### Console Output
3380
+
3381
+ ```text
3382
+ === Read Validation ===
3383
+ Row 0: A1: "Name", B1: "Department"
3384
+ Row 1: A2: "Alice", B2: "HR"
3385
+ Row 2: A3: "Bob", B3: "Eng"
3386
+ ```
3387
+
3388
+ <hr/>
3389
+
3390
+ ## Interactive Comments
3391
+
3392
+ [Download .xlsx](files/interactive_comments.xlsx)
3393
+
3394
+ > [!TIP]
3395
+ > **Interactive Feature**: This example uses interactive Excel behaviors (such as validation dropdowns, comments, or autofilters). Since the static visual preview below represents a printed page layout, please **[Download the .xlsx file](files/interactive_comments.xlsx)** and open it in Microsoft Excel, LibreOffice Calc, or Apple Numbers to interact with it!
3396
+
3397
+ Demonstrates cell pop-up comments. Open the sheet in Excel and hover your mouse over the cell with the red triangle to view the comment.
3398
+
3399
+ ### Rendered Output (LibreOffice Calc)
3400
+
3401
+ <div><img src="../../test/visual/baselines/interactive_comments/page-1.png" width="100%" alt="Preview"/></div>
3402
+
3403
+ <div><img src="../../test/visual/support/illustrations/interactive_comments_page-2.png" width="100%" alt="Preview"/></div>
3404
+
3405
+ ### DSL Code
3406
+
3407
+ ```ruby
3408
+ # frozen_string_literal: true
3409
+ require "xlsxrb"
3410
+ output_path = ARGV[0] || "interactive_comments.xlsx"
3411
+ Xlsxrb.generate(output_path) do |w|
3412
+ w.add_sheet("Comments") do |s|
3413
+ s.set_column(0, width: 25)
3414
+ s.set_column(1, width: 25)
3415
+ s.add_row(["Item A", "Item B"])
3416
+ s.add_comment("A1", "This is an important comment.", author: "System")
3417
+ end
3418
+ end
3419
+
3420
+ # 2. Read the generated sheet and print cell values
3421
+ puts "=== Read Validation ==="
3422
+ workbook = Xlsxrb.read(output_path)
3423
+ sheet = workbook.sheets.first
3424
+ sheet.rows.first(5).each do |row|
3425
+ row_cells = row.cells.map { |c| "#{c.ref}: #{c.value.inspect}" }
3426
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
3427
+ end
3428
+ ```
3429
+
3430
+ ### Console Output
3431
+
3432
+ ```text
3433
+ === Read Validation ===
3434
+ Row 0: A1: "Item A", B1: "Item B"
3435
+ ```
3436
+
3437
+ <hr/>
3438
+
3439
+ ## Interactive Validation Custom
3440
+
3441
+ [Download .xlsx](files/interactive_validation_custom.xlsx)
3442
+
3443
+ > [!TIP]
3444
+ > **Interactive Feature**: This example uses interactive Excel behaviors (such as validation dropdowns, comments, or autofilters). Since the static visual preview below represents a printed page layout, please **[Download the .xlsx file](files/interactive_validation_custom.xlsx)** and open it in Microsoft Excel, LibreOffice Calc, or Apple Numbers to interact with it!
3445
+
3446
+ Demonstrates interactive custom formula validation rules.
3447
+
3448
+ ### Rendered Output (LibreOffice Calc)
3449
+
3450
+ <div><img src="../../test/visual/baselines/interactive_validation_custom/page-1.png" width="100%" alt="Preview"/></div>
3451
+
3452
+ <div><img src="../../test/visual/support/illustrations/interactive_validation_custom_page-2.png" width="100%" alt="Preview"/></div>
3453
+
3454
+ ### DSL Code
3455
+
3456
+ ```ruby
3457
+ # frozen_string_literal: true
3458
+ require "xlsxrb"
3459
+ output_path = ARGV[0] || "interactive_validation_custom.xlsx"
3460
+ Xlsxrb.generate(output_path) do |w|
3461
+ w.add_sheet("Custom Rule") do |s|
3462
+ s.set_column(0, width: 25)
3463
+ s.set_column(1, width: 25)
3464
+ s.add_row(["Number A", "Number B (Must be larger A)"])
3465
+ s.add_row([10, ""])
3466
+ s.add_data_validation("B2", type: "custom", formula1: "B2>A2", show_error_message: true, error_title: "Validation Error", error: "Number B must be greater than Number A")
3467
+ end
3468
+ end
3469
+
3470
+ # 2. Read the generated sheet and print data validations
3471
+ puts "=== Read Validation ==="
3472
+ reader = Xlsxrb::Ooxml::Reader.new(output_path)
3473
+ workbook = Xlsxrb.read(output_path)
3474
+ sheet = workbook.sheets.first
3475
+ validations = reader.data_validations(sheet: sheet.name)
3476
+ validations.each do |v|
3477
+ puts "Validation range #{v[:sqref]}: type=#{v[:type]}, formula1=#{v[:formula1]}, formula2=#{v[:formula2]}"
3478
+ end
3479
+ ```
3480
+
3481
+ ### Console Output
3482
+
3483
+ ```text
3484
+ === Read Validation ===
3485
+ Validation range B2: type=custom, formula1=B2>A2, formula2=
3486
+ ```
3487
+
3488
+ <hr/>
3489
+
3490
+ ## Interactive Validation Date
3491
+
3492
+ [Download .xlsx](files/interactive_validation_date.xlsx)
3493
+
3494
+ > [!TIP]
3495
+ > **Interactive Feature**: This example uses interactive Excel behaviors (such as validation dropdowns, comments, or autofilters). Since the static visual preview below represents a printed page layout, please **[Download the .xlsx file](files/interactive_validation_date.xlsx)** and open it in Microsoft Excel, LibreOffice Calc, or Apple Numbers to interact with it!
3496
+
3497
+ Demonstrates interactive date range constraints validation rules.
3498
+
3499
+ ### Rendered Output (LibreOffice Calc)
3500
+
3501
+ <div><img src="../../test/visual/baselines/interactive_validation_date/page-1.png" width="100%" alt="Preview"/></div>
3502
+
3503
+ <div><img src="../../test/visual/support/illustrations/interactive_validation_date_page-2.png" width="100%" alt="Preview"/></div>
3504
+
3505
+ ### DSL Code
3506
+
3507
+ ```ruby
3508
+ # frozen_string_literal: true
3509
+ require "xlsxrb"
3510
+ output_path = ARGV[0] || "interactive_validation_date.xlsx"
3511
+ Xlsxrb.generate(output_path) do |w|
3512
+ w.add_sheet("Date Validation") do |s|
3513
+ s.set_column(0, width: 25)
3514
+ s.set_column(1, width: 25)
3515
+ s.add_row(["Date Range", "Enter Date (2026):"])
3516
+ s.add_data_validation("B2", type: "date", operator: "between", formula1: "Date(2026,1,1)", formula2: "Date(2026,12,31)", show_error_message: true, error_title: "Invalid Date", error: "Must be a date in 2026")
3517
+ end
3518
+ end
3519
+
3520
+ # 2. Read the generated sheet and print data validations
3521
+ puts "=== Read Validation ==="
3522
+ reader = Xlsxrb::Ooxml::Reader.new(output_path)
3523
+ workbook = Xlsxrb.read(output_path)
3524
+ sheet = workbook.sheets.first
3525
+ validations = reader.data_validations(sheet: sheet.name)
3526
+ validations.each do |v|
3527
+ puts "Validation range #{v[:sqref]}: type=#{v[:type]}, formula1=#{v[:formula1]}, formula2=#{v[:formula2]}"
3528
+ end
3529
+ ```
3530
+
3531
+ ### Console Output
3532
+
3533
+ ```text
3534
+ === Read Validation ===
3535
+ Validation range B2: type=date, formula1=Date(2026,1,1), formula2=Date(2026,12,31)
3536
+ ```
3537
+
3538
+ <hr/>
3539
+
3540
+ ## Interactive Validation List
3541
+
3542
+ [Download .xlsx](files/interactive_validation_list.xlsx)
3543
+
3544
+ > [!TIP]
3545
+ > **Interactive Feature**: This example uses interactive Excel behaviors (such as validation dropdowns, comments, or autofilters). Since the static visual preview below represents a printed page layout, please **[Download the .xlsx file](files/interactive_validation_list.xlsx)** and open it in Microsoft Excel, LibreOffice Calc, or Apple Numbers to interact with it!
3546
+
3547
+ Demonstrates dropdown list data validations. Open the sheet in Excel and select cell A1/A2 to see the dropdown list in action.
3548
+
3549
+ ### Rendered Output (LibreOffice Calc)
3550
+
3551
+ <div><img src="../../test/visual/baselines/interactive_validation_list/page-1.png" width="100%" alt="Preview"/></div>
3552
+
3553
+ <div><img src="../../test/visual/support/illustrations/interactive_validation_list_page-2.png" width="100%" alt="Preview"/></div>
3554
+
3555
+ ### DSL Code
3556
+
3557
+ ```ruby
3558
+ # frozen_string_literal: true
3559
+ require "xlsxrb"
3560
+ output_path = ARGV[0] || "interactive_validation_list.xlsx"
3561
+ Xlsxrb.generate(output_path) do |w|
3562
+ w.add_sheet("List Validation") do |s|
3563
+ s.set_column(0, width: 25)
3564
+ s.set_column(1, width: 25)
3565
+ s.add_row(["Department", "Select:"])
3566
+ s.add_data_validation("B2", type: "list", formula1: '"HR,Sales,Engineering"')
3567
+ end
3568
+ end
3569
+
3570
+ # 2. Read the generated sheet and print data validations
3571
+ puts "=== Read Validation ==="
3572
+ reader = Xlsxrb::Ooxml::Reader.new(output_path)
3573
+ workbook = Xlsxrb.read(output_path)
3574
+ sheet = workbook.sheets.first
3575
+ validations = reader.data_validations(sheet: sheet.name)
3576
+ validations.each do |v|
3577
+ puts "Validation range #{v[:sqref]}: type=#{v[:type]}, formula1=#{v[:formula1]}, formula2=#{v[:formula2]}"
3578
+ end
3579
+ ```
3580
+
3581
+ ### Console Output
3582
+
3583
+ ```text
3584
+ === Read Validation ===
3585
+ Validation range B2: type=list, formula1="HR,Sales,Engineering", formula2=
3586
+ ```
3587
+
3588
+ <hr/>
3589
+
3590
+ ## Interactive Validation Range
3591
+
3592
+ [Download .xlsx](files/interactive_validation_range.xlsx)
3593
+
3594
+ > [!TIP]
3595
+ > **Interactive Feature**: This example uses interactive Excel behaviors (such as validation dropdowns, comments, or autofilters). Since the static visual preview below represents a printed page layout, please **[Download the .xlsx file](files/interactive_validation_range.xlsx)** and open it in Microsoft Excel, LibreOffice Calc, or Apple Numbers to interact with it!
3596
+
3597
+ Demonstrates range constraints for whole number validations. Open the sheet in Excel and try entering a value outside 10-100 to trigger the warning.
3598
+
3599
+ ### Rendered Output (LibreOffice Calc)
3600
+
3601
+ <div><img src="../../test/visual/baselines/interactive_validation_range/page-1.png" width="100%" alt="Preview"/></div>
3602
+
3603
+ <div><img src="../../test/visual/support/illustrations/interactive_validation_range_page-2.png" width="100%" alt="Preview"/></div>
3604
+
3605
+ ### DSL Code
3606
+
3607
+ ```ruby
3608
+ # frozen_string_literal: true
3609
+ require "xlsxrb"
3610
+ output_path = ARGV[0] || "interactive_validation_range.xlsx"
3611
+ Xlsxrb.generate(output_path) do |w|
3612
+ w.add_sheet("Range Validation") do |s|
3613
+ s.set_column(0, width: 25)
3614
+ s.set_column(1, width: 25)
3615
+ s.add_row(["Age", "Enter (18-99):"])
3616
+ s.add_data_validation("B2", type: "whole", operator: "between", formula1: "18", formula2: "99", show_error_message: true, error_title: "Invalid Age", error: "Age must be between 18 and 99!")
3617
+ end
3618
+ end
3619
+
3620
+ # 2. Read the generated sheet and print data validations
3621
+ puts "=== Read Validation ==="
3622
+ reader = Xlsxrb::Ooxml::Reader.new(output_path)
3623
+ workbook = Xlsxrb.read(output_path)
3624
+ sheet = workbook.sheets.first
3625
+ validations = reader.data_validations(sheet: sheet.name)
3626
+ validations.each do |v|
3627
+ puts "Validation range #{v[:sqref]}: type=#{v[:type]}, formula1=#{v[:formula1]}, formula2=#{v[:formula2]}"
3628
+ end
3629
+ ```
3630
+
3631
+ ### Console Output
3632
+
3633
+ ```text
3634
+ === Read Validation ===
3635
+ Validation range B2: type=whole, formula1=18, formula2=99
3636
+ ```
3637
+
3638
+ <hr/>
3639
+
3640
+ ## Interactive Validation Text Length
3641
+
3642
+ [Download .xlsx](files/interactive_validation_text_length.xlsx)
3643
+
3644
+ > [!TIP]
3645
+ > **Interactive Feature**: This example uses interactive Excel behaviors (such as validation dropdowns, comments, or autofilters). Since the static visual preview below represents a printed page layout, please **[Download the .xlsx file](files/interactive_validation_text_length.xlsx)** and open it in Microsoft Excel, LibreOffice Calc, or Apple Numbers to interact with it!
3646
+
3647
+ Demonstrates interactive text length validation rules.
3648
+
3649
+ ### Rendered Output (LibreOffice Calc)
3650
+
3651
+ <div><img src="../../test/visual/baselines/interactive_validation_text_length/page-1.png" width="100%" alt="Preview"/></div>
3652
+
3653
+ <div><img src="../../test/visual/support/illustrations/interactive_validation_text_length_page-2.png" width="100%" alt="Preview"/></div>
3654
+
3655
+ ### DSL Code
3656
+
3657
+ ```ruby
3658
+ # frozen_string_literal: true
3659
+ require "xlsxrb"
3660
+ output_path = ARGV[0] || "interactive_validation_text_length.xlsx"
3661
+ Xlsxrb.generate(output_path) do |w|
3662
+ w.add_sheet("Text Length") do |s|
3663
+ s.set_column(0, width: 25)
3664
+ s.set_column(1, width: 25)
3665
+ s.add_row(["Username", "Enter (< 10 chars):"])
3666
+ s.add_data_validation("B2", type: "textLength", operator: "lessThan", formula1: "10", show_error_message: true, error_title: "Too Long", error: "Username must be under 10 characters")
3667
+ end
3668
+ end
3669
+
3670
+ # 2. Read the generated sheet and print data validations
3671
+ puts "=== Read Validation ==="
3672
+ reader = Xlsxrb::Ooxml::Reader.new(output_path)
3673
+ workbook = Xlsxrb.read(output_path)
3674
+ sheet = workbook.sheets.first
3675
+ validations = reader.data_validations(sheet: sheet.name)
3676
+ validations.each do |v|
3677
+ puts "Validation range #{v[:sqref]}: type=#{v[:type]}, formula1=#{v[:formula1]}, formula2=#{v[:formula2]}"
3678
+ end
3679
+ ```
3680
+
3681
+ ### Console Output
3682
+
3683
+ ```text
3684
+ === Read Validation ===
3685
+ Validation range B2: type=textLength, formula1=10, formula2=
3686
+ ```
3687
+
3688
+ <hr/>
3689
+
3690
+ ## Interactive Validation Time
3691
+
3692
+ [Download .xlsx](files/interactive_validation_time.xlsx)
3693
+
3694
+ > [!TIP]
3695
+ > **Interactive Feature**: This example uses interactive Excel behaviors (such as validation dropdowns, comments, or autofilters). Since the static visual preview below represents a printed page layout, please **[Download the .xlsx file](files/interactive_validation_time.xlsx)** and open it in Microsoft Excel, LibreOffice Calc, or Apple Numbers to interact with it!
3696
+
3697
+ Demonstrates interactive time validation rules.
3698
+
3699
+ ### Rendered Output (LibreOffice Calc)
3700
+
3701
+ <div><img src="../../test/visual/baselines/interactive_validation_time/page-1.png" width="100%" alt="Preview"/></div>
3702
+
3703
+ <div><img src="../../test/visual/support/illustrations/interactive_validation_time_page-2.png" width="100%" alt="Preview"/></div>
3704
+
3705
+ ### DSL Code
3706
+
3707
+ ```ruby
3708
+ # frozen_string_literal: true
3709
+ require "xlsxrb"
3710
+ output_path = ARGV[0] || "interactive_validation_time.xlsx"
3711
+ Xlsxrb.generate(output_path) do |w|
3712
+ w.add_sheet("Time Validation") do |s|
3713
+ s.set_column(0, width: 25)
3714
+ s.set_column(1, width: 25)
3715
+ s.add_row(["Schedule", "Enter Time (after 08:00):"])
3716
+ s.add_data_validation("B2", type: "time", operator: "greaterThan", formula1: "0.33333", show_error_message: true, error_title: "Too Early", error: "Time must be after 08:00 AM")
3717
+ end
3718
+ end
3719
+
3720
+ # 2. Read the generated sheet and print data validations
3721
+ puts "=== Read Validation ==="
3722
+ reader = Xlsxrb::Ooxml::Reader.new(output_path)
3723
+ workbook = Xlsxrb.read(output_path)
3724
+ sheet = workbook.sheets.first
3725
+ validations = reader.data_validations(sheet: sheet.name)
3726
+ validations.each do |v|
3727
+ puts "Validation range #{v[:sqref]}: type=#{v[:type]}, formula1=#{v[:formula1]}, formula2=#{v[:formula2]}"
3728
+ end
3729
+ ```
3730
+
3731
+ ### Console Output
3732
+
3733
+ ```text
3734
+ === Read Validation ===
3735
+ Validation range B2: type=time, formula1=0.33333, formula2=
3736
+ ```
3737
+
3738
+ <hr/>
3739
+
3740
+ ## Japanese Text
3741
+
3742
+ [Download .xlsx](files/japanese_text.xlsx)
3743
+
3744
+ Demonstrates writing multi-byte Japanese text and setting appropriate Japanese font names (e.g., Noto Sans CJK JP).
3745
+
3746
+ ### Rendered Output (LibreOffice Calc)
3747
+
3748
+ <div><img src="../../test/visual/baselines/japanese_text/page-1.png" width="100%" alt="Preview"/></div>
3749
+
3750
+ ### DSL Code
3751
+
3752
+ ```ruby
3753
+ # frozen_string_literal: true
3754
+
3755
+ require "xlsxrb"
3756
+
3757
+ output_path = ARGV[0] || "japanese_text.xlsx"
3758
+
3759
+ Xlsxrb.generate(output_path) do |w|
3760
+ w.add_style("ja_font") do |style|
3761
+ style.font_name("Noto Sans CJK JP").size(12)
3762
+ end
3763
+
3764
+ w.add_sheet("Japanese") do |s|
3765
+ s.set_column(0, width: 25)
3766
+ s.set_column(1, width: 25)
3767
+ s.add_row(["日本語ラベル", "値"], styles: { 0 => "ja_font", 1 => "ja_font" })
3768
+ s.add_row(["売上", 12500], styles: { 0 => "ja_font" })
3769
+ end
3770
+ end
3771
+
3772
+ # 2. Read the generated sheet and print parsed cell values and Ruby classes
3773
+ puts "=== Read Validation ==="
3774
+ workbook = Xlsxrb.read(output_path)
3775
+ sheet = workbook.sheets.first
3776
+ sheet.rows.each do |row|
3777
+ row_cells = row.cells.map do |c|
3778
+ "#{c.ref}: #{c.value.inspect} (#{c.value.class})"
3779
+ end
3780
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
3781
+ end
3782
+ ```
3783
+
3784
+ ### Console Output
3785
+
3786
+ ```text
3787
+ === Read Validation ===
3788
+ Row 0: A1: "日本語ラベル" (String), B1: "値" (String)
3789
+ Row 1: A2: "売上" (String), B2: 12500 (Integer)
3790
+ ```
3791
+
3792
+ <hr/>
3793
+
3794
+ ## Merge Freeze
3795
+
3796
+ [Download .xlsx](files/merge_freeze.xlsx)
3797
+
3798
+ Demonstrates merging a cell range into a single cell, and freezing the top rows of a sheet.
3799
+
3800
+ ### Rendered Output (LibreOffice Calc)
3801
+
3802
+ <div><img src="../../test/visual/baselines/merge_freeze/page-1.png" width="100%" alt="Preview"/></div>
3803
+
3804
+ ### DSL Code
3805
+
3806
+ ```ruby
3807
+ # frozen_string_literal: true
3808
+
3809
+ require "xlsxrb"
3810
+
3811
+ output_path = ARGV[0] || "merge_freeze.xlsx"
3812
+
3813
+ Xlsxrb.generate(output_path) do |w|
3814
+ w.add_style("title") { |style| style.border_all(style: "thin", color: "FF000000").align_horizontal("center") }
3815
+ w.add_style("border") { |style| style.border_all(style: "thin", color: "FF000000") }
3816
+ w.add_sheet("Merge & Freeze") do |s|
3817
+ s.set_sheet_property(:fit_to_page, true)
3818
+ s.set_page_setup(fit_to_width: 1, fit_to_height: 1)
3819
+ s.set_column(0, width: 25)
3820
+ s.set_column(1, width: 25)
3821
+ s.set_column(2, width: 25)
3822
+ s.add_row(["Merged Title Row", nil, nil], styles: ["title", "title", "title"])
3823
+ s.add_row(["Header A", "Header B", "Header C"], styles: ["border", "border", "border"])
3824
+ s.add_row(["Row 1 Col A", "Row 1 Col B", "Row 1 Col C"], styles: ["border", "border", "border"])
3825
+ s.add_row(["Row 2 Col A", "Row 2 Col B", "Row 2 Col C"], styles: ["border", "border", "border"])
3826
+
3827
+ s.merge_cells("A1:C1")
3828
+ s.set_freeze_pane(row: 2, col: 0)
3829
+ end
3830
+ end
3831
+
3832
+ # 2. Read the generated sheet and print cell values
3833
+ puts "=== Read Validation ==="
3834
+ workbook = Xlsxrb.read(output_path)
3835
+ sheet = workbook.sheets.first
3836
+ sheet.rows.first(5).each do |row|
3837
+ row_cells = row.cells.map { |c| "#{c.ref}: #{c.value.inspect}" }
3838
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
3839
+ end
3840
+ ```
3841
+
3842
+ ### Console Output
3843
+
3844
+ ```text
3845
+ === Read Validation ===
3846
+ Row 0: A1: "Merged Title Row", B1: nil, C1: nil
3847
+ Row 1: A2: "Header A", B2: "Header B", C2: "Header C"
3848
+ Row 2: A3: "Row 1 Col A", B3: "Row 1 Col B", C3: "Row 1 Col C"
3849
+ Row 3: A4: "Row 2 Col A", B4: "Row 2 Col B", C4: "Row 2 Col C"
3850
+ ```
3851
+
3852
+ <hr/>
3853
+
3854
+ ## Page Grid Lines Print
3855
+
3856
+ [Download .xlsx](files/page_grid_lines_print.xlsx)
3857
+
3858
+ Demonstrates enabling printing of grid lines.
3859
+
3860
+ ### Rendered Output (LibreOffice Calc)
3861
+
3862
+ <div><img src="../../test/visual/baselines/page_grid_lines_print/page-1.png" width="100%" alt="Preview"/></div>
3863
+
3864
+ ### DSL Code
3865
+
3866
+ ```ruby
3867
+ # frozen_string_literal: true
3868
+ require "xlsxrb"
3869
+ output_path = ARGV[0] || "page_grid_lines_print.xlsx"
3870
+ Xlsxrb.generate(output_path) do |w|
3871
+ w.add_sheet("Print Grid Lines") do |s|
3872
+ s.set_column(0, width: 25)
3873
+ s.set_column(1, width: 25)
3874
+ s.set_print_option(:grid_lines, true)
3875
+ s.add_row(["Grid Lines Printed sheet"])
3876
+ end
3877
+ end
3878
+
3879
+ # 2. Read the generated sheet and print page setup properties
3880
+ puts "=== Read Validation ==="
3881
+ reader = Xlsxrb::Ooxml::Reader.new(output_path)
3882
+ workbook = Xlsxrb.read(output_path)
3883
+ sheet = workbook.sheets.first
3884
+ margins = reader.page_margins(sheet: sheet.name)
3885
+ setup = reader.page_setup(sheet: sheet.name)
3886
+ opts = reader.print_options(sheet: sheet.name)
3887
+ puts "Page Margins: #{margins.inspect}"
3888
+ puts "Page Setup: #{setup.inspect}"
3889
+ puts "Print Options: #{opts.inspect}"
3890
+ ```
3891
+
3892
+ ### Console Output
3893
+
3894
+ ```text
3895
+ === Read Validation ===
3896
+ Page Margins: nil
3897
+ Page Setup: {}
3898
+ Print Options: {grid_lines: true}
3899
+ ```
3900
+
3901
+ <hr/>
3902
+
3903
+ ## Page Header Footer
3904
+
3905
+ [Download .xlsx](files/page_header_footer.xlsx)
3906
+
3907
+ Demonstrates setting odd page headers and footers.
3908
+
3909
+ ### Rendered Output (LibreOffice Calc)
3910
+
3911
+ <div><img src="../../test/visual/baselines/page_header_footer/page-1.png" width="100%" alt="Preview"/></div>
3912
+
3913
+ ### DSL Code
3914
+
3915
+ ```ruby
3916
+ # frozen_string_literal: true
3917
+ require "xlsxrb"
3918
+ output_path = ARGV[0] || "page_header_footer.xlsx"
3919
+ Xlsxrb.generate(output_path) do |w|
3920
+ w.add_sheet("Header Footer") do |s|
3921
+ s.set_column(0, width: 25)
3922
+ s.set_column(1, width: 25)
3923
+ s.set_header_footer(odd_header: "&LMy Company&RPage &P", odd_footer: "&CConfidential")
3924
+ s.add_row(["Header Footer sheet"])
3925
+ end
3926
+ end
3927
+
3928
+ # 2. Read the generated sheet and print page setup properties
3929
+ puts "=== Read Validation ==="
3930
+ reader = Xlsxrb::Ooxml::Reader.new(output_path)
3931
+ workbook = Xlsxrb.read(output_path)
3932
+ sheet = workbook.sheets.first
3933
+ margins = reader.page_margins(sheet: sheet.name)
3934
+ setup = reader.page_setup(sheet: sheet.name)
3935
+ opts = reader.print_options(sheet: sheet.name)
3936
+ puts "Page Margins: #{margins.inspect}"
3937
+ puts "Page Setup: #{setup.inspect}"
3938
+ puts "Print Options: #{opts.inspect}"
3939
+ ```
3940
+
3941
+ ### Console Output
3942
+
3943
+ ```text
3944
+ === Read Validation ===
3945
+ Page Margins: nil
3946
+ Page Setup: {}
3947
+ Print Options: {}
3948
+ ```
3949
+
3950
+ <hr/>
3951
+
3952
+ ## Page Headings Print
3953
+
3954
+ [Download .xlsx](files/page_headings_print.xlsx)
3955
+
3956
+ Demonstrates enabling printing of row and column headings.
3957
+
3958
+ ### Rendered Output (LibreOffice Calc)
3959
+
3960
+ <div><img src="../../test/visual/baselines/page_headings_print/page-1.png" width="100%" alt="Preview"/></div>
3961
+
3962
+ ### DSL Code
3963
+
3964
+ ```ruby
3965
+ # frozen_string_literal: true
3966
+ require "xlsxrb"
3967
+ output_path = ARGV[0] || "page_headings_print.xlsx"
3968
+ Xlsxrb.generate(output_path) do |w|
3969
+ w.add_sheet("Print Headings") do |s|
3970
+ s.set_column(0, width: 25)
3971
+ s.set_column(1, width: 25)
3972
+ s.set_print_option(:headings, true)
3973
+ s.add_row(["Row/Col Headings Printed sheet"])
3974
+ end
3975
+ end
3976
+
3977
+ # 2. Read the generated sheet and print page setup properties
3978
+ puts "=== Read Validation ==="
3979
+ reader = Xlsxrb::Ooxml::Reader.new(output_path)
3980
+ workbook = Xlsxrb.read(output_path)
3981
+ sheet = workbook.sheets.first
3982
+ margins = reader.page_margins(sheet: sheet.name)
3983
+ setup = reader.page_setup(sheet: sheet.name)
3984
+ opts = reader.print_options(sheet: sheet.name)
3985
+ puts "Page Margins: #{margins.inspect}"
3986
+ puts "Page Setup: #{setup.inspect}"
3987
+ puts "Print Options: #{opts.inspect}"
3988
+ ```
3989
+
3990
+ ### Console Output
3991
+
3992
+ ```text
3993
+ === Read Validation ===
3994
+ Page Margins: nil
3995
+ Page Setup: {}
3996
+ Print Options: {headings: true}
3997
+ ```
3998
+
3999
+ <hr/>
4000
+
4001
+ ## Page Margins Narrow
4002
+
4003
+ [Download .xlsx](files/page_margins_narrow.xlsx)
4004
+
4005
+ Demonstrates setting narrow page margins.
4006
+
4007
+ ### Rendered Output (LibreOffice Calc)
4008
+
4009
+ <div><img src="../../test/visual/baselines/page_margins_narrow/page-1.png" width="100%" alt="Preview"/></div>
4010
+
4011
+ ### DSL Code
4012
+
4013
+ ```ruby
4014
+ # frozen_string_literal: true
4015
+ require "xlsxrb"
4016
+ output_path = ARGV[0] || "page_margins_narrow.xlsx"
4017
+ Xlsxrb.generate(output_path) do |w|
4018
+ w.add_sheet("Narrow Margins") do |s|
4019
+ s.set_column(0, width: 25)
4020
+ s.set_column(1, width: 25)
4021
+ s.set_page_margins(top: 0.25, bottom: 0.25, left: 0.25, right: 0.25, header: 0.1, footer: 0.1)
4022
+ s.add_row(["Narrow Margins sheet"])
4023
+ end
4024
+ end
4025
+
4026
+ # 2. Read the generated sheet and print page setup properties
4027
+ puts "=== Read Validation ==="
4028
+ reader = Xlsxrb::Ooxml::Reader.new(output_path)
4029
+ workbook = Xlsxrb.read(output_path)
4030
+ sheet = workbook.sheets.first
4031
+ margins = reader.page_margins(sheet: sheet.name)
4032
+ setup = reader.page_setup(sheet: sheet.name)
4033
+ opts = reader.print_options(sheet: sheet.name)
4034
+ puts "Page Margins: #{margins.inspect}"
4035
+ puts "Page Setup: #{setup.inspect}"
4036
+ puts "Print Options: #{opts.inspect}"
4037
+ ```
4038
+
4039
+ ### Console Output
4040
+
4041
+ ```text
4042
+ === Read Validation ===
4043
+ Page Margins: {left: 0.25, right: 0.25, top: 0.25, bottom: 0.25, header: 0.1, footer: 0.1}
4044
+ Page Setup: {}
4045
+ Print Options: {}
4046
+ ```
4047
+
4048
+ <hr/>
4049
+
4050
+ ## Page Margins Wide
4051
+
4052
+ [Download .xlsx](files/page_margins_wide.xlsx)
4053
+
4054
+ Demonstrates setting wide page margins.
4055
+
4056
+ ### Rendered Output (LibreOffice Calc)
4057
+
4058
+ <div><img src="../../test/visual/baselines/page_margins_wide/page-1.png" width="100%" alt="Preview"/></div>
4059
+
4060
+ ### DSL Code
4061
+
4062
+ ```ruby
4063
+ # frozen_string_literal: true
4064
+ require "xlsxrb"
4065
+ output_path = ARGV[0] || "page_margins_wide.xlsx"
4066
+ Xlsxrb.generate(output_path) do |w|
4067
+ w.add_sheet("Wide Margins") do |s|
4068
+ s.set_column(0, width: 25)
4069
+ s.set_column(1, width: 25)
4070
+ s.set_page_margins(top: 1.0, bottom: 1.0, left: 1.0, right: 1.0, header: 0.5, footer: 0.5)
4071
+ s.add_row(["Wide Margins sheet"])
4072
+ end
4073
+ end
4074
+
4075
+ # 2. Read the generated sheet and print page setup properties
4076
+ puts "=== Read Validation ==="
4077
+ reader = Xlsxrb::Ooxml::Reader.new(output_path)
4078
+ workbook = Xlsxrb.read(output_path)
4079
+ sheet = workbook.sheets.first
4080
+ margins = reader.page_margins(sheet: sheet.name)
4081
+ setup = reader.page_setup(sheet: sheet.name)
4082
+ opts = reader.print_options(sheet: sheet.name)
4083
+ puts "Page Margins: #{margins.inspect}"
4084
+ puts "Page Setup: #{setup.inspect}"
4085
+ puts "Print Options: #{opts.inspect}"
4086
+ ```
4087
+
4088
+ ### Console Output
4089
+
4090
+ ```text
4091
+ === Read Validation ===
4092
+ Page Margins: {left: 1.0, right: 1.0, top: 1.0, bottom: 1.0, header: 0.5, footer: 0.5}
4093
+ Page Setup: {}
4094
+ Print Options: {}
4095
+ ```
4096
+
4097
+ <hr/>
4098
+
4099
+ ## Page Orientation Landscape
4100
+
4101
+ [Download .xlsx](files/page_orientation_landscape.xlsx)
4102
+
4103
+ Demonstrates landscape page setup for printing.
4104
+
4105
+ ### Rendered Output (LibreOffice Calc)
4106
+
4107
+ <div><img src="../../test/visual/baselines/page_orientation_landscape/page-1.png" width="100%" alt="Preview"/></div>
4108
+
4109
+ ### DSL Code
4110
+
4111
+ ```ruby
4112
+ # frozen_string_literal: true
4113
+ require "xlsxrb"
4114
+ output_path = ARGV[0] || "page_orientation_landscape.xlsx"
4115
+ Xlsxrb.generate(output_path) do |w|
4116
+ w.add_sheet("Landscape") do |s|
4117
+ s.set_column(0, width: 25)
4118
+ s.set_column(1, width: 25)
4119
+ s.set_page_setup(orientation: :landscape)
4120
+ s.add_row(["Landscape Orientation sheet"])
4121
+ end
4122
+ end
4123
+
4124
+ # 2. Read the generated sheet and print page setup properties
4125
+ puts "=== Read Validation ==="
4126
+ reader = Xlsxrb::Ooxml::Reader.new(output_path)
4127
+ workbook = Xlsxrb.read(output_path)
4128
+ sheet = workbook.sheets.first
4129
+ margins = reader.page_margins(sheet: sheet.name)
4130
+ setup = reader.page_setup(sheet: sheet.name)
4131
+ opts = reader.print_options(sheet: sheet.name)
4132
+ puts "Page Margins: #{margins.inspect}"
4133
+ puts "Page Setup: #{setup.inspect}"
4134
+ puts "Print Options: #{opts.inspect}"
4135
+ ```
4136
+
4137
+ ### Console Output
4138
+
4139
+ ```text
4140
+ === Read Validation ===
4141
+ Page Margins: nil
4142
+ Page Setup: {orientation: "landscape"}
4143
+ Print Options: {}
4144
+ ```
4145
+
4146
+ <hr/>
4147
+
4148
+ ## Page Paper Size A3
4149
+
4150
+ [Download .xlsx](files/page_paper_size_a3.xlsx)
4151
+
4152
+ Demonstrates setting paper size to A3 (paper size 8).
4153
+
4154
+ ### Rendered Output (LibreOffice Calc)
4155
+
4156
+ <div><img src="../../test/visual/baselines/page_paper_size_a3/page-1.png" width="100%" alt="Preview"/></div>
4157
+
4158
+ ### DSL Code
4159
+
4160
+ ```ruby
4161
+ # frozen_string_literal: true
4162
+ require "xlsxrb"
4163
+ output_path = ARGV[0] || "page_paper_size_a3.xlsx"
4164
+ Xlsxrb.generate(output_path) do |w|
4165
+ w.add_sheet("A3 Sheet") do |s|
4166
+ s.set_column(0, width: 25)
4167
+ s.set_column(1, width: 25)
4168
+ s.set_page_setup(paper_size: 8) # ISO A3 is paperSize="8"
4169
+ s.add_row(["ISO A3 Paper Size sheet"])
4170
+ end
4171
+ end
4172
+
4173
+ # 2. Read the generated sheet and print page setup properties
4174
+ puts "=== Read Validation ==="
4175
+ reader = Xlsxrb::Ooxml::Reader.new(output_path)
4176
+ workbook = Xlsxrb.read(output_path)
4177
+ sheet = workbook.sheets.first
4178
+ margins = reader.page_margins(sheet: sheet.name)
4179
+ setup = reader.page_setup(sheet: sheet.name)
4180
+ opts = reader.print_options(sheet: sheet.name)
4181
+ puts "Page Margins: #{margins.inspect}"
4182
+ puts "Page Setup: #{setup.inspect}"
4183
+ puts "Print Options: #{opts.inspect}"
4184
+ ```
4185
+
4186
+ ### Console Output
4187
+
4188
+ ```text
4189
+ === Read Validation ===
4190
+ Page Margins: nil
4191
+ Page Setup: {paper_size: 8}
4192
+ Print Options: {}
4193
+ ```
4194
+
4195
+ <hr/>
4196
+
4197
+ ## Row Grouping
4198
+
4199
+ [Download .xlsx](files/row_grouping.xlsx)
4200
+
4201
+ Demonstrates outline grouping for rows.
4202
+
4203
+ ### Rendered Output (LibreOffice Calc)
4204
+
4205
+ <div><img src="../../test/visual/baselines/row_grouping/page-1.png" width="100%" alt="Preview"/></div>
4206
+
4207
+ ### DSL Code
4208
+
4209
+ ```ruby
4210
+ # frozen_string_literal: true
4211
+ require "xlsxrb"
4212
+ output_path = ARGV[0] || "row_grouping.xlsx"
4213
+ Xlsxrb.generate(output_path) do |w|
4214
+ w.add_style("parent") { |style| style.border_all(style: "thin", color: "FF000000").bold }
4215
+ w.add_style("child") { |style| style.border_all(style: "thin", color: "FF000000").align_horizontal("left").indent(2) }
4216
+ w.add_sheet("Row Grouping") do |s|
4217
+ s.set_column(0, width: 25)
4218
+ s.set_column(1, width: 25)
4219
+ s.add_row(["Parent Row 1", ""], styles: ["parent", "parent"])
4220
+ s.add_row(["Child Row 1.1", ""], outline_level: 1, styles: ["child", "child"])
4221
+ s.add_row(["Child Row 1.2", ""], outline_level: 1, styles: ["child", "child"])
4222
+ end
4223
+ end
4224
+
4225
+ # 2. Read the generated sheet and print row attributes
4226
+ puts "=== Read Validation ==="
4227
+ workbook = Xlsxrb.read(output_path)
4228
+ sheet = workbook.sheets.first
4229
+ sheet.rows.each do |row|
4230
+ puts "Row #{row.index}: height=#{row.height}, hidden=#{row.hidden}, outline_level=#{row.outline_level}"
4231
+ end
4232
+ ```
4233
+
4234
+ ### Console Output
4235
+
4236
+ ```text
4237
+ === Read Validation ===
4238
+ Row 0: height=, hidden=false, outline_level=
4239
+ Row 1: height=, hidden=false, outline_level=1
4240
+ Row 2: height=, hidden=false, outline_level=1
4241
+ ```
4242
+
4243
+ <hr/>
4244
+
4245
+ ## Row Height Tall
4246
+
4247
+ [Download .xlsx](files/row_height_tall.xlsx)
4248
+
4249
+ Demonstrates setting very tall row heights.
4250
+
4251
+ ### Rendered Output (LibreOffice Calc)
4252
+
4253
+ <div><img src="../../test/visual/baselines/row_height_tall/page-1.png" width="100%" alt="Preview"/></div>
4254
+
4255
+ ### DSL Code
4256
+
4257
+ ```ruby
4258
+ # frozen_string_literal: true
4259
+ require "xlsxrb"
4260
+ output_path = ARGV[0] || "row_height_tall.xlsx"
4261
+ Xlsxrb.generate(output_path) do |w|
4262
+ w.add_style("border") { |style| style.border_all(style: "thin", color: "FF000000") }
4263
+ w.add_sheet("Row Height") do |s|
4264
+ s.set_column(0, width: 25)
4265
+ s.set_column(1, width: 25)
4266
+ s.add_row(["Very Tall Row (Height 60)", ""], height: 60, styles: ["border", "border"])
4267
+ s.add_row(["Normal Row", ""], styles: ["border", "border"])
4268
+ end
4269
+ end
4270
+
4271
+ # 2. Read the generated sheet and print row attributes
4272
+ puts "=== Read Validation ==="
4273
+ workbook = Xlsxrb.read(output_path)
4274
+ sheet = workbook.sheets.first
4275
+ sheet.rows.each do |row|
4276
+ puts "Row #{row.index}: height=#{row.height}, hidden=#{row.hidden}, outline_level=#{row.outline_level}"
4277
+ end
4278
+ ```
4279
+
4280
+ ### Console Output
4281
+
4282
+ ```text
4283
+ === Read Validation ===
4284
+ Row 0: height=60.0, hidden=false, outline_level=
4285
+ Row 1: height=, hidden=false, outline_level=
4286
+ ```
4287
+
4288
+ <hr/>
4289
+
4290
+ ## Row Heights
4291
+
4292
+ [Download .xlsx](files/row_heights.xlsx)
4293
+
4294
+ Demonstrates setting custom row heights.
4295
+
4296
+ ### Rendered Output (LibreOffice Calc)
4297
+
4298
+ <div><img src="../../test/visual/baselines/row_heights/page-1.png" width="100%" alt="Preview"/></div>
4299
+
4300
+ ### DSL Code
4301
+
4302
+ ```ruby
4303
+ # frozen_string_literal: true
4304
+ require "xlsxrb"
4305
+ output_path = ARGV[0] || "row_heights.xlsx"
4306
+ Xlsxrb.generate(output_path) do |w|
4307
+ w.add_style("border") { |style| style.border_all(style: "thin", color: "FF000000") }
4308
+ w.add_sheet("Heights") do |s|
4309
+ s.set_column(0, width: 25)
4310
+ s.set_column(1, width: 25)
4311
+ s.add_row(["Normal Row", ""], styles: ["border", "border"])
4312
+ s.add_row(["Tall Row", ""], height: 40, styles: ["border", "border"])
4313
+ end
4314
+ end
4315
+
4316
+ # 2. Read the generated sheet and print row attributes
4317
+ puts "=== Read Validation ==="
4318
+ workbook = Xlsxrb.read(output_path)
4319
+ sheet = workbook.sheets.first
4320
+ sheet.rows.each do |row|
4321
+ puts "Row #{row.index}: height=#{row.height}, hidden=#{row.hidden}, outline_level=#{row.outline_level}"
4322
+ end
4323
+ ```
4324
+
4325
+ ### Console Output
4326
+
4327
+ ```text
4328
+ === Read Validation ===
4329
+ Row 0: height=, hidden=false, outline_level=
4330
+ Row 1: height=40.0, hidden=false, outline_level=
4331
+ ```
4332
+
4333
+ <hr/>
4334
+
4335
+ ## Sheet Tab Colors
4336
+
4337
+ [Download .xlsx](files/sheet_tab_colors.xlsx)
4338
+
4339
+ Demonstrates customizing tab colors of individual worksheets.
4340
+
4341
+ ### Rendered Output (LibreOffice Calc)
4342
+
4343
+ <div><img src="../../test/visual/baselines/sheet_tab_colors/page-1.png" width="100%" alt="Preview"/></div>
4344
+
4345
+ <div><img src="../../test/visual/baselines/sheet_tab_colors/page-2.png" width="100%" alt="Preview"/></div>
4346
+
4347
+ <div><img src="../../test/visual/support/illustrations/sheet_tab_colors_page-2.png" width="100%" alt="Preview"/></div>
4348
+
4349
+ ### DSL Code
4350
+
4351
+ ```ruby
4352
+ # frozen_string_literal: true
4353
+ require "xlsxrb"
4354
+ output_path = ARGV[0] || "sheet_tab_colors.xlsx"
4355
+ Xlsxrb.generate(output_path) do |w|
4356
+ w.add_sheet("Red Tab") do |s|
4357
+ s.set_column(0, width: 25)
4358
+ s.set_column(1, width: 25)
4359
+ s.set_sheet_property(:tab_color, "FFFF0000")
4360
+ s.add_row(["Red tab sheet"])
4361
+ end
4362
+ w.add_sheet("Green Tab") do |s|
4363
+ s.set_sheet_property(:tab_color, "FF00FF00")
4364
+ s.add_row(["Green tab sheet"])
4365
+ end
4366
+ end
4367
+
4368
+ # 2. Read the generated sheet and print sheet properties
4369
+ puts "=== Read Validation ==="
4370
+ reader = Xlsxrb::Ooxml::Reader.new(output_path)
4371
+ workbook = Xlsxrb.read(output_path)
4372
+ workbook.sheet_names.each do |s_name|
4373
+ props = reader.sheet_properties(sheet: s_name)
4374
+ puts "Sheet: #{s_name}, tab color: #{props[:tab_color].inspect}"
4375
+ end
4376
+ ```
4377
+
4378
+ ### Console Output
4379
+
4380
+ ```text
4381
+ === Read Validation ===
4382
+ Sheet: Red Tab, tab color: "FFFF0000"
4383
+ Sheet: Green Tab, tab color: "FF00FF00"
4384
+ ```
4385
+
4386
+ <hr/>
4387
+
4388
+ ## Sparkline Column
4389
+
4390
+ [Download .xlsx](files/sparkline_column.xlsx)
4391
+
4392
+ Demonstrates embedded column sparklines in cell ranges.
4393
+
4394
+ ### Rendered Output (LibreOffice Calc)
4395
+
4396
+ <div><img src="../../test/visual/baselines/sparkline_column/page-1.png" width="100%" alt="Preview"/></div>
4397
+
4398
+ ### DSL Code
4399
+
4400
+ ```ruby
4401
+ # frozen_string_literal: true
4402
+ require "xlsxrb"
4403
+ output_path = ARGV[0] || "sparkline_column.xlsx"
4404
+ Xlsxrb.generate(output_path) do |w|
4405
+ w.add_sheet("Sparkline") do |s|
4406
+ s.set_sheet_property(:fit_to_page, true)
4407
+ s.set_page_setup(fit_to_width: 1, fit_to_height: 1)
4408
+ s.set_column(0, width: 25)
4409
+ s.set_column(1, width: 25)
4410
+ s.add_row([5, 12, 8, 15, nil])
4411
+ s.add_sparkline_group(
4412
+ type: :column,
4413
+ sparklines: [{ location_ref: "E1", data_ref: "Sparkline!A1:D1" }]
4414
+ )
4415
+ end
4416
+ end
4417
+
4418
+ # 2. Read the generated sheet and print cell values
4419
+ puts "=== Read Validation ==="
4420
+ workbook = Xlsxrb.read(output_path)
4421
+ sheet = workbook.sheets.first
4422
+ sheet.rows.first(3).each do |row|
4423
+ row_cells = row.cells.map { |c| "#{c.ref}: #{c.value.inspect}" }
4424
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
4425
+ end
4426
+ ```
4427
+
4428
+ ### Console Output
4429
+
4430
+ ```text
4431
+ === Read Validation ===
4432
+ Row 0: A1: 5, B1: 12, C1: 8, D1: 15
4433
+ ```
4434
+
4435
+ <hr/>
4436
+
4437
+ ## Sparkline Line
4438
+
4439
+ [Download .xlsx](files/sparkline_line.xlsx)
4440
+
4441
+ Demonstrates embedded line sparklines in cell ranges.
4442
+
4443
+ ### Rendered Output (LibreOffice Calc)
4444
+
4445
+ <div><img src="../../test/visual/baselines/sparkline_line/page-1.png" width="100%" alt="Preview"/></div>
4446
+
4447
+ <div><img src="../../test/visual/support/illustrations/sparkline_line_page-2.png" width="100%" alt="Preview"/></div>
4448
+
4449
+ ### DSL Code
4450
+
4451
+ ```ruby
4452
+ # frozen_string_literal: true
4453
+ require "xlsxrb"
4454
+ output_path = ARGV[0] || "sparkline_line.xlsx"
4455
+ Xlsxrb.generate(output_path) do |w|
4456
+ w.add_sheet("Sparkline") do |s|
4457
+ s.set_sheet_property(:fit_to_page, true)
4458
+ s.set_page_setup(fit_to_width: 1, fit_to_height: 1)
4459
+ s.set_column(0, width: 25)
4460
+ s.set_column(1, width: 25)
4461
+ s.add_row([10, 20, 15, 30, nil])
4462
+ s.add_sparkline_group(
4463
+ type: :line,
4464
+ markers: true,
4465
+ color_series: "FF000000",
4466
+ color_markers: "FFFF0000",
4467
+ sparklines: [{ location_ref: "E1", data_ref: "Sparkline!A1:D1" }]
4468
+ )
4469
+ end
4470
+ end
4471
+
4472
+ # 2. Read the generated sheet and print cell values
4473
+ puts "=== Read Validation ==="
4474
+ workbook = Xlsxrb.read(output_path)
4475
+ sheet = workbook.sheets.first
4476
+ sheet.rows.first(3).each do |row|
4477
+ row_cells = row.cells.map { |c| "#{c.ref}: #{c.value.inspect}" }
4478
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
4479
+ end
4480
+ ```
4481
+
4482
+ ### Console Output
4483
+
4484
+ ```text
4485
+ === Read Validation ===
4486
+ Row 0: A1: 10, B1: 20, C1: 15, D1: 30
4487
+ ```
4488
+
4489
+ <hr/>
4490
+
4491
+ ## Styles Fonts Fills
4492
+
4493
+ [Download .xlsx](files/styles_fonts_fills.xlsx)
4494
+
4495
+ Demonstrates cell formatting, including custom font sizing, bold/italic text, custom text colors, and background fill colors.
4496
+
4497
+ ### Rendered Output (LibreOffice Calc)
4498
+
4499
+ <div><img src="../../test/visual/baselines/styles_fonts_fills/page-1.png" width="100%" alt="Preview"/></div>
4500
+
4501
+ ### DSL Code
4502
+
4503
+ ```ruby
4504
+ # frozen_string_literal: true
4505
+
4506
+ require "xlsxrb"
4507
+
4508
+ output_path = ARGV[0] || "styles_fonts_fills.xlsx"
4509
+
4510
+ Xlsxrb.generate(output_path) do |w|
4511
+ w.add_style("header") do |style|
4512
+ style.bold.size(14).font_color("FFFFFFFF").fill_color("FF4F81BD")
4513
+ end
4514
+
4515
+ w.add_style("highlight") do |style|
4516
+ style.italic.font_color("FFC00000").fill_color("FFFFFF00")
4517
+ end
4518
+
4519
+ w.add_sheet("Styles") do
4520
+ w.set_column(0, width: 25)
4521
+ w.set_column(1, width: 25)
4522
+ w.add_row(["Header 1", "Header 2"], styles: { 0 => "header", 1 => "header" })
4523
+ w.add_row(["Normal Text", "Highlighted Text"], styles: { 1 => "highlight" })
4524
+ end
4525
+ end
4526
+
4527
+ # 2. Read the generated sheet and print styling details
4528
+ puts "=== Read Validation ==="
4529
+ workbook = Xlsxrb.read(output_path)
4530
+ sheet = workbook.sheets.first
4531
+ sheet.rows.first(4).each do |row|
4532
+ row_cells = row.cells.map do |c|
4533
+ xf = workbook.styles[:cell_xfs][c.style_index] if c.style_index
4534
+ font = xf ? workbook.styles[:fonts][xf[:font_id]] : nil
4535
+ fill = xf ? workbook.styles[:fills][xf[:fill_id]] : nil
4536
+ "#{c.ref}: #{c.value.inspect} (font=#{font&.[](:name)}, fill=#{fill&.[](:fg_color)&.[](:rgb)})"
4537
+ end
4538
+ puts "Row #{row.index}: #{row_cells.join(', ')}"
4539
+ end
4540
+ ```
4541
+
4542
+ ### Console Output
4543
+
4544
+ ```text
4545
+ === Read Validation ===
4546
+ Row 0: A1: "Header 1" (font=Calibri, fill=FF4F81BD), B1: "Header 2" (font=Calibri, fill=FF4F81BD)
4547
+ Row 1: A2: "Normal Text" (font=, fill=), B2: "Highlighted Text" (font=Calibri, fill=FFFFFF00)
4548
+ ```
4549
+
4550
+ <hr/>
4551
+
4552
+ ## View Show Grid Lines
4553
+
4554
+ [Download .xlsx](files/view_show_grid_lines.xlsx)
4555
+
4556
+ Demonstrates disabling visible grid lines in spreadsheet view.
4557
+
4558
+ ### Rendered Output (LibreOffice Calc)
4559
+
4560
+ <div><img src="../../test/visual/baselines/view_show_grid_lines/page-1.png" width="100%" alt="Preview"/></div>
4561
+
4562
+ <div><img src="../../test/visual/support/illustrations/view_show_grid_lines_page-2.png" width="100%" alt="Preview"/></div>
4563
+
4564
+ ### DSL Code
4565
+
4566
+ ```ruby
4567
+ # frozen_string_literal: true
4568
+ require "xlsxrb"
4569
+ output_path = ARGV[0] || "view_show_grid_lines.xlsx"
4570
+ Xlsxrb.generate(output_path) do |w|
4571
+ w.add_sheet("Hide Grid Lines") do |s|
4572
+ s.set_column(0, width: 25)
4573
+ s.set_column(1, width: 25)
4574
+ s.set_sheet_view(:show_grid_lines, false)
4575
+ s.add_row(["No Grid Lines displayed"])
4576
+ end
4577
+ end
4578
+
4579
+ # 2. Read the generated sheet and print view configurations
4580
+ puts "=== Read Validation ==="
4581
+ reader = Xlsxrb::Ooxml::Reader.new(output_path)
4582
+ workbook = Xlsxrb.read(output_path)
4583
+ workbook.sheet_names.each do |s_name|
4584
+ view = reader.sheet_view(sheet: s_name)
4585
+ puts "Sheet '#{s_name}' views zoom scale: #{view[:zoom_scale]}%, show grid lines: #{view[:show_grid_lines]}"
4586
+ end
4587
+ ```
4588
+
4589
+ ### Console Output
4590
+
4591
+ ```text
4592
+ === Read Validation ===
4593
+ Sheet 'Hide Grid Lines' views zoom scale: %, show grid lines: false
4594
+ ```
4595
+
4596
+ <hr/>
4597
+
4598
+ ## View Zoom Scale
4599
+
4600
+ [Download .xlsx](files/view_zoom_scale.xlsx)
4601
+
4602
+ Demonstrates setting custom zoom scale in sheet view (e.g. 150%).
4603
+
4604
+ ### Rendered Output (LibreOffice Calc)
4605
+
4606
+ <div><img src="../../test/visual/baselines/view_zoom_scale/page-1.png" width="100%" alt="Preview"/></div>
4607
+
4608
+ <div><img src="../../test/visual/support/illustrations/view_zoom_scale_page-2.png" width="100%" alt="Preview"/></div>
4609
+
4610
+ ### DSL Code
4611
+
4612
+ ```ruby
4613
+ # frozen_string_literal: true
4614
+ require "xlsxrb"
4615
+ output_path = ARGV[0] || "view_zoom_scale.xlsx"
4616
+ Xlsxrb.generate(output_path) do |w|
4617
+ w.add_sheet("Zoom 150") do |s|
4618
+ s.set_column(0, width: 25)
4619
+ s.set_column(1, width: 25)
4620
+ s.set_sheet_view(:zoom_scale, 150)
4621
+ s.add_row(["Zoom scale is set to 150%"])
4622
+ end
4623
+ end
4624
+
4625
+ # 2. Read the generated sheet and print view configurations
4626
+ puts "=== Read Validation ==="
4627
+ reader = Xlsxrb::Ooxml::Reader.new(output_path)
4628
+ workbook = Xlsxrb.read(output_path)
4629
+ workbook.sheet_names.each do |s_name|
4630
+ view = reader.sheet_view(sheet: s_name)
4631
+ puts "Sheet '#{s_name}' views zoom scale: #{view[:zoom_scale]}%, show grid lines: #{view[:show_grid_lines]}"
4632
+ end
4633
+ ```
4634
+
4635
+ ### Console Output
4636
+
4637
+ ```text
4638
+ === Read Validation ===
4639
+ Sheet 'Zoom 150' views zoom scale: 150%, show grid lines:
4640
+ ```
4641
+
4642
+ <hr/>
4643
+
4644
+ ## Workbook Three Sheets
4645
+
4646
+ [Download .xlsx](files/workbook_three_sheets.xlsx)
4647
+
4648
+ Demonstrates creating workbooks with multiple worksheets.
4649
+
4650
+ ### Rendered Output (LibreOffice Calc)
4651
+
4652
+ <div><img src="../../test/visual/baselines/workbook_three_sheets/page-1.png" width="100%" alt="Preview"/></div>
4653
+
4654
+ <div><img src="../../test/visual/baselines/workbook_three_sheets/page-2.png" width="100%" alt="Preview"/></div>
4655
+
4656
+ <div><img src="../../test/visual/baselines/workbook_three_sheets/page-3.png" width="100%" alt="Preview"/></div>
4657
+
4658
+ ### DSL Code
4659
+
4660
+ ```ruby
4661
+ # frozen_string_literal: true
4662
+ require "xlsxrb"
4663
+ output_path = ARGV[0] || "workbook_three_sheets.xlsx"
4664
+ Xlsxrb.generate(output_path) do |w|
4665
+ w.add_sheet("First Sheet") { |s| s.add_row(["First Sheet Data"]) }
4666
+ w.add_sheet("Second Sheet") { |s| s.add_row(["Second Sheet Data"]) }
4667
+ w.add_sheet("Third Sheet") { |s| s.add_row(["Third Sheet Data"]) }
4668
+ end
4669
+
4670
+ # 2. Read the generated sheet and print the sheets structure
4671
+ puts "=== Read Validation ==="
4672
+ workbook = Xlsxrb.read(output_path)
4673
+ puts "Workbook sheets: #{workbook.sheet_names.join(', ')}"
4674
+ ```
4675
+
4676
+ ### Console Output
4677
+
4678
+ ```text
4679
+ === Read Validation ===
4680
+ Workbook sheets: First Sheet, Second Sheet, Third Sheet
4681
+ ```
4682
+
4683
+ <hr/>
4684
+