@trebco/treb 23.6.5 → 25.0.0-rc1

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 (217) hide show
  1. package/.eslintignore +8 -0
  2. package/.eslintrc.js +164 -0
  3. package/README-shadow-DOM.md +88 -0
  4. package/README.md +37 -130
  5. package/api-config.json +29 -0
  6. package/api-generator/api-generator-types.ts +82 -0
  7. package/api-generator/api-generator.ts +1172 -0
  8. package/api-generator/package.json +3 -0
  9. package/build/treb-spreadsheet.mjs +14 -0
  10. package/{treb.d.ts → build/treb.d.ts} +285 -269
  11. package/esbuild-custom-element.mjs +336 -0
  12. package/esbuild.js +305 -0
  13. package/package.json +43 -14
  14. package/treb-base-types/package.json +5 -0
  15. package/treb-base-types/src/api_types.ts +36 -0
  16. package/treb-base-types/src/area.ts +583 -0
  17. package/treb-base-types/src/basic_types.ts +45 -0
  18. package/treb-base-types/src/cell.ts +612 -0
  19. package/treb-base-types/src/cells.ts +1066 -0
  20. package/treb-base-types/src/color.ts +124 -0
  21. package/treb-base-types/src/import.ts +71 -0
  22. package/treb-base-types/src/index-standalone.ts +29 -0
  23. package/treb-base-types/src/index.ts +42 -0
  24. package/treb-base-types/src/layout.ts +47 -0
  25. package/treb-base-types/src/localization.ts +187 -0
  26. package/treb-base-types/src/rectangle.ts +145 -0
  27. package/treb-base-types/src/render_text.ts +72 -0
  28. package/treb-base-types/src/style.ts +545 -0
  29. package/treb-base-types/src/table.ts +109 -0
  30. package/treb-base-types/src/text_part.ts +54 -0
  31. package/treb-base-types/src/theme.ts +608 -0
  32. package/treb-base-types/src/union.ts +152 -0
  33. package/treb-base-types/src/value-type.ts +164 -0
  34. package/treb-base-types/style/resizable.css +59 -0
  35. package/treb-calculator/modern.tsconfig.json +11 -0
  36. package/treb-calculator/package.json +5 -0
  37. package/treb-calculator/src/calculator.ts +2546 -0
  38. package/treb-calculator/src/complex-math.ts +558 -0
  39. package/treb-calculator/src/dag/array-vertex.ts +198 -0
  40. package/treb-calculator/src/dag/graph.ts +951 -0
  41. package/treb-calculator/src/dag/leaf_vertex.ts +118 -0
  42. package/treb-calculator/src/dag/spreadsheet_vertex.ts +327 -0
  43. package/treb-calculator/src/dag/spreadsheet_vertex_base.ts +44 -0
  44. package/treb-calculator/src/dag/vertex.ts +352 -0
  45. package/treb-calculator/src/descriptors.ts +162 -0
  46. package/treb-calculator/src/expression-calculator.ts +1069 -0
  47. package/treb-calculator/src/function-error.ts +103 -0
  48. package/treb-calculator/src/function-library.ts +103 -0
  49. package/treb-calculator/src/functions/base-functions.ts +1214 -0
  50. package/treb-calculator/src/functions/checkbox.ts +164 -0
  51. package/treb-calculator/src/functions/complex-functions.ts +253 -0
  52. package/treb-calculator/src/functions/finance-functions.ts +399 -0
  53. package/treb-calculator/src/functions/information-functions.ts +102 -0
  54. package/treb-calculator/src/functions/matrix-functions.ts +182 -0
  55. package/treb-calculator/src/functions/sparkline.ts +335 -0
  56. package/treb-calculator/src/functions/statistics-functions.ts +350 -0
  57. package/treb-calculator/src/functions/text-functions.ts +298 -0
  58. package/treb-calculator/src/index.ts +27 -0
  59. package/treb-calculator/src/notifier-types.ts +59 -0
  60. package/treb-calculator/src/primitives.ts +428 -0
  61. package/treb-calculator/src/utilities.ts +305 -0
  62. package/treb-charts/package.json +5 -0
  63. package/treb-charts/src/chart-functions.ts +156 -0
  64. package/treb-charts/src/chart-types.ts +230 -0
  65. package/treb-charts/src/chart.ts +1288 -0
  66. package/treb-charts/src/index.ts +24 -0
  67. package/treb-charts/src/main.ts +37 -0
  68. package/treb-charts/src/rectangle.ts +52 -0
  69. package/treb-charts/src/renderer.ts +1841 -0
  70. package/treb-charts/src/util.ts +122 -0
  71. package/treb-charts/style/charts.scss +221 -0
  72. package/treb-charts/style/old-charts.scss +250 -0
  73. package/treb-embed/markup/layout.html +137 -0
  74. package/treb-embed/markup/toolbar.html +175 -0
  75. package/treb-embed/modern.tsconfig.json +25 -0
  76. package/treb-embed/src/custom-element/content-types.d.ts +18 -0
  77. package/treb-embed/src/custom-element/global.d.ts +11 -0
  78. package/treb-embed/src/custom-element/spreadsheet-constructor.ts +1227 -0
  79. package/treb-embed/src/custom-element/treb-global.ts +44 -0
  80. package/treb-embed/src/custom-element/treb-spreadsheet-element.ts +52 -0
  81. package/treb-embed/src/embedded-spreadsheet.ts +5362 -0
  82. package/treb-embed/src/index.ts +16 -0
  83. package/treb-embed/src/language-model.ts +41 -0
  84. package/treb-embed/src/options.ts +320 -0
  85. package/treb-embed/src/progress-dialog.ts +228 -0
  86. package/treb-embed/src/selection-state.ts +16 -0
  87. package/treb-embed/src/spinner.ts +42 -0
  88. package/treb-embed/src/toolbar-message.ts +96 -0
  89. package/treb-embed/src/types.ts +167 -0
  90. package/treb-embed/style/autocomplete.scss +103 -0
  91. package/treb-embed/style/dark-theme.scss +114 -0
  92. package/treb-embed/style/defaults.scss +36 -0
  93. package/treb-embed/style/dialog.scss +181 -0
  94. package/treb-embed/style/dropdown-select.scss +101 -0
  95. package/treb-embed/style/formula-bar.scss +193 -0
  96. package/treb-embed/style/grid.scss +374 -0
  97. package/treb-embed/style/layout.scss +424 -0
  98. package/treb-embed/style/mouse-mask.scss +67 -0
  99. package/treb-embed/style/note.scss +92 -0
  100. package/treb-embed/style/overlay-editor.scss +102 -0
  101. package/treb-embed/style/spinner.scss +92 -0
  102. package/treb-embed/style/tab-bar.scss +228 -0
  103. package/treb-embed/style/table.scss +80 -0
  104. package/treb-embed/style/theme-defaults.scss +444 -0
  105. package/treb-embed/style/toolbar.scss +416 -0
  106. package/treb-embed/style/tooltip.scss +68 -0
  107. package/treb-embed/style/treb-icons.scss +130 -0
  108. package/treb-embed/style/treb-spreadsheet-element.scss +20 -0
  109. package/treb-embed/style/z-index.scss +43 -0
  110. package/treb-export/docs/charts.md +68 -0
  111. package/treb-export/modern.tsconfig.json +19 -0
  112. package/treb-export/package.json +4 -0
  113. package/treb-export/src/address-type.ts +77 -0
  114. package/treb-export/src/base-template.ts +22 -0
  115. package/treb-export/src/column-width.ts +85 -0
  116. package/treb-export/src/drawing2/chart-template-components2.ts +389 -0
  117. package/treb-export/src/drawing2/chart2.ts +282 -0
  118. package/treb-export/src/drawing2/column-chart-template2.ts +521 -0
  119. package/treb-export/src/drawing2/donut-chart-template2.ts +296 -0
  120. package/treb-export/src/drawing2/drawing2.ts +355 -0
  121. package/treb-export/src/drawing2/embedded-image.ts +71 -0
  122. package/treb-export/src/drawing2/scatter-chart-template2.ts +555 -0
  123. package/treb-export/src/export-worker/export-worker.ts +99 -0
  124. package/treb-export/src/export-worker/index-modern.ts +22 -0
  125. package/treb-export/src/export2.ts +2204 -0
  126. package/treb-export/src/import2.ts +882 -0
  127. package/treb-export/src/relationship.ts +36 -0
  128. package/treb-export/src/shared-strings2.ts +128 -0
  129. package/treb-export/src/template-2.ts +22 -0
  130. package/treb-export/src/unescape_xml.ts +47 -0
  131. package/treb-export/src/workbook-sheet2.ts +182 -0
  132. package/treb-export/src/workbook-style2.ts +1285 -0
  133. package/treb-export/src/workbook-theme2.ts +88 -0
  134. package/treb-export/src/workbook2.ts +491 -0
  135. package/treb-export/src/xml-utils.ts +201 -0
  136. package/treb-export/template/base/[Content_Types].xml +2 -0
  137. package/treb-export/template/base/_rels/.rels +2 -0
  138. package/treb-export/template/base/docProps/app.xml +2 -0
  139. package/treb-export/template/base/docProps/core.xml +12 -0
  140. package/treb-export/template/base/xl/_rels/workbook.xml.rels +2 -0
  141. package/treb-export/template/base/xl/sharedStrings.xml +2 -0
  142. package/treb-export/template/base/xl/styles.xml +2 -0
  143. package/treb-export/template/base/xl/theme/theme1.xml +2 -0
  144. package/treb-export/template/base/xl/workbook.xml +2 -0
  145. package/treb-export/template/base/xl/worksheets/sheet1.xml +2 -0
  146. package/treb-export/template/base.xlsx +0 -0
  147. package/treb-format/package.json +8 -0
  148. package/treb-format/src/format.test.ts +213 -0
  149. package/treb-format/src/format.ts +942 -0
  150. package/treb-format/src/format_cache.ts +199 -0
  151. package/treb-format/src/format_parser.ts +723 -0
  152. package/treb-format/src/index.ts +25 -0
  153. package/treb-format/src/number_format_section.ts +100 -0
  154. package/treb-format/src/value_parser.ts +337 -0
  155. package/treb-grid/package.json +5 -0
  156. package/treb-grid/src/editors/autocomplete.ts +394 -0
  157. package/treb-grid/src/editors/autocomplete_matcher.ts +260 -0
  158. package/treb-grid/src/editors/formula_bar.ts +473 -0
  159. package/treb-grid/src/editors/formula_editor_base.ts +910 -0
  160. package/treb-grid/src/editors/overlay_editor.ts +511 -0
  161. package/treb-grid/src/index.ts +37 -0
  162. package/treb-grid/src/layout/base_layout.ts +2618 -0
  163. package/treb-grid/src/layout/grid_layout.ts +299 -0
  164. package/treb-grid/src/layout/rectangle_cache.ts +86 -0
  165. package/treb-grid/src/render/selection-renderer.ts +414 -0
  166. package/treb-grid/src/render/svg_header_overlay.ts +93 -0
  167. package/treb-grid/src/render/svg_selection_block.ts +187 -0
  168. package/treb-grid/src/render/tile_renderer.ts +2122 -0
  169. package/treb-grid/src/types/annotation.ts +216 -0
  170. package/treb-grid/src/types/border_constants.ts +34 -0
  171. package/treb-grid/src/types/clipboard_data.ts +31 -0
  172. package/treb-grid/src/types/data_model.ts +334 -0
  173. package/treb-grid/src/types/drag_mask.ts +81 -0
  174. package/treb-grid/src/types/grid.ts +7743 -0
  175. package/treb-grid/src/types/grid_base.ts +3644 -0
  176. package/treb-grid/src/types/grid_command.ts +470 -0
  177. package/treb-grid/src/types/grid_events.ts +124 -0
  178. package/treb-grid/src/types/grid_options.ts +97 -0
  179. package/treb-grid/src/types/grid_selection.ts +60 -0
  180. package/treb-grid/src/types/named_range.ts +369 -0
  181. package/treb-grid/src/types/scale-control.ts +202 -0
  182. package/treb-grid/src/types/serialize_options.ts +72 -0
  183. package/treb-grid/src/types/set_range_options.ts +52 -0
  184. package/treb-grid/src/types/sheet.ts +3099 -0
  185. package/treb-grid/src/types/sheet_types.ts +95 -0
  186. package/treb-grid/src/types/tab_bar.ts +464 -0
  187. package/treb-grid/src/types/tile.ts +59 -0
  188. package/treb-grid/src/types/update_flags.ts +75 -0
  189. package/treb-grid/src/util/dom_utilities.ts +44 -0
  190. package/treb-grid/src/util/fontmetrics2.ts +179 -0
  191. package/treb-grid/src/util/ua.ts +104 -0
  192. package/treb-logo.svg +18 -0
  193. package/treb-parser/package.json +5 -0
  194. package/treb-parser/src/csv-parser.ts +122 -0
  195. package/treb-parser/src/index.ts +25 -0
  196. package/treb-parser/src/md-parser.ts +526 -0
  197. package/treb-parser/src/parser-types.ts +397 -0
  198. package/treb-parser/src/parser.test.ts +298 -0
  199. package/treb-parser/src/parser.ts +2673 -0
  200. package/treb-utils/package.json +5 -0
  201. package/treb-utils/src/dispatch.ts +57 -0
  202. package/treb-utils/src/event_source.ts +147 -0
  203. package/treb-utils/src/ievent_source.ts +33 -0
  204. package/treb-utils/src/index.ts +31 -0
  205. package/treb-utils/src/measurement.ts +174 -0
  206. package/treb-utils/src/resizable.ts +160 -0
  207. package/treb-utils/src/scale.ts +137 -0
  208. package/treb-utils/src/serialize_html.ts +124 -0
  209. package/treb-utils/src/template.ts +70 -0
  210. package/treb-utils/src/validate_uri.ts +61 -0
  211. package/tsconfig.json +10 -0
  212. package/tsproject.json +30 -0
  213. package/util/license-plugin-esbuild.js +86 -0
  214. package/util/list-css-vars.sh +46 -0
  215. package/README-esm.md +0 -37
  216. package/treb-bundle.css +0 -2
  217. package/treb-bundle.mjs +0 -15
@@ -0,0 +1,1285 @@
1
+ /*
2
+ * This file is part of TREB.
3
+ *
4
+ * TREB is free software: you can redistribute it and/or modify it under the
5
+ * terms of the GNU General Public License as published by the Free Software
6
+ * Foundation, either version 3 of the License, or (at your option) any
7
+ * later version.
8
+ *
9
+ * TREB is distributed in the hope that it will be useful, but WITHOUT ANY
10
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12
+ * details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License along
15
+ * with TREB. If not, see <https://www.gnu.org/licenses/>.
16
+ *
17
+ * Copyright 2022-2023 trebco, llc.
18
+ * info@treb.app
19
+ *
20
+ */
21
+
22
+ // import * as ElementTree from 'elementtree';
23
+ // import { Element, ElementTree as Tree } from 'elementtree';
24
+
25
+ import { Style } from 'treb-base-types';
26
+ import { Theme } from './workbook-theme2';
27
+ import { NumberFormatCache } from 'treb-format';
28
+ import { XMLUtils } from './xml-utils';
29
+
30
+ import { Unescape } from './unescape_xml';
31
+
32
+ export interface Font {
33
+ size?: number;
34
+ name?: string;
35
+ family?: number;
36
+ color_theme?: number;
37
+ color_argb?: string;
38
+ color_tint?: number;
39
+ scheme?: string;
40
+ bold?: boolean;
41
+ italic?: boolean;
42
+ underline?: boolean;
43
+ strike?: boolean;
44
+ }
45
+
46
+ export interface NumberFormat {
47
+ id?: number;
48
+ symbolic_name?: string;
49
+ format?: string;
50
+ }
51
+
52
+ export interface XlColor {
53
+ theme?: number;
54
+ tint?: number;
55
+ indexed?: number;
56
+ argb?: string;
57
+ }
58
+
59
+ export interface Fill {
60
+ pattern_type: 'none'|'solid'|'gray';
61
+ pattern_gray?: number;
62
+ fg_color?: XlColor;
63
+ bg_color?: XlColor;
64
+ // color_argb?: string;
65
+ }
66
+
67
+ export interface CellXf {
68
+ number_format: number;
69
+ font: number;
70
+ fill: number;
71
+ border: number;
72
+ wrap_text?: boolean;
73
+ horizontal_alignment?: string;
74
+ vertical_alignment?: string;
75
+ xfid?: number;
76
+
77
+ // FIXME // apply_font?: boolean;
78
+ // FIXME // apply_border?: boolean;
79
+ // FIXME // apply_number_format?: boolean;
80
+ // FIXME // apply_alignment?: boolean;
81
+
82
+ }
83
+
84
+ interface ColorAttributes {
85
+ indexed?: string;
86
+ rgb?: string;
87
+ theme?: string;
88
+ tint?: string;
89
+ }
90
+
91
+ export interface BorderEdge {
92
+ style?: string;
93
+ color?: number; // indexed
94
+ rgba?: string;
95
+ theme?: number;
96
+ tint?: number;
97
+ }
98
+
99
+ /**
100
+ * this is flat so we can map/copy better, even thought it makes
101
+ * more sense as a map of simple objects
102
+ */
103
+ export interface BorderStyle {
104
+
105
+ top: BorderEdge,
106
+ left: BorderEdge,
107
+ bottom: BorderEdge,
108
+ right: BorderEdge,
109
+ diagonal: BorderEdge,
110
+
111
+ /*
112
+ left_style?: string; // 'thin' | ??
113
+ left_color?: number; // indexed // FIXME: argb
114
+ left_color_rgba?: string;
115
+ left_color_theme?: number;
116
+ left_color_tint?: number;
117
+
118
+ right_style?: string;
119
+ right_color?: number;
120
+ right_color_rgba?: string;
121
+ right_color_theme?: number;
122
+ right_color_tint?: number;
123
+
124
+ top_style?: string;
125
+ top_color?: number;
126
+ top_color_rgba?: string;
127
+ top_color_theme?: number;
128
+ top_color_tint?: number;
129
+
130
+ bottom_style?: string;
131
+ bottom_color?: number;
132
+ bottom_color_rgba?: string;
133
+ bottom_color_theme?: number;
134
+ bottom_color_tint?: number;
135
+
136
+ diagonal_style?: string;
137
+ diagonal_color?: number;
138
+ diagonal_color_rgba?: string;
139
+ diagonal_color_theme?: number;
140
+ diagonal_color_tint?: number;
141
+ */
142
+
143
+ }
144
+
145
+ const default_border = {
146
+ top: {}, left: {}, bottom: {}, right: {}, diagonal: {},
147
+ }
148
+
149
+ export interface StyleOptions {
150
+ font?: Font;
151
+ border?: BorderStyle;
152
+ number_format?: NumberFormat;
153
+ horizontal_alignment?: string;
154
+ vertical_alignment?: string;
155
+ wrap?: boolean;
156
+ fill?: Fill;
157
+ }
158
+
159
+
160
+ export class StyleCache {
161
+
162
+ /**
163
+ * thanks to
164
+ * http://polymathprogrammer.com/2011/02/15/built-in-styles-for-excel-open-xml/
165
+ */
166
+ public static default_styles: {[index: number]: string} = {
167
+ 0: 'General',
168
+ 1: '0',
169
+ 2: '0.00',
170
+ 3: '#,##0',
171
+ 4: '#,##0.00',
172
+ 9: '0%',
173
+ 10: '0.00%',
174
+ 11: '0.00E+00',
175
+ 12: '# ?/?',
176
+ 13: '# ??/??',
177
+ 14: 'mm-dd-yy',
178
+ 15: 'd-mmm-yy',
179
+ 16: 'd-mmm',
180
+ 17: 'mmm-yy',
181
+ 18: 'h:mm AM/PM',
182
+ 19: 'h:mm:ss AM/PM',
183
+ 20: 'h:mm',
184
+ 21: 'h:mm:ss',
185
+ 22: 'm/d/yy h:mm',
186
+ 37: '#,##0 ;(#,##0)',
187
+ 38: '#,##0 ;[Red](#,##0)',
188
+ 39: '#,##0.00;(#,##0.00)',
189
+ 40: '#,##0.00;[Red](#,##0.00)',
190
+ 45: 'mm:ss',
191
+ 46: '[h]:mm:ss',
192
+ 47: 'mmss.0',
193
+ 48: '##0.0E+0',
194
+ 49: '@',
195
+ };
196
+
197
+ public theme = new Theme();
198
+
199
+ public cell_xfs: CellXf[] = [];
200
+ public fonts: Font[] = [];
201
+ public borders: BorderStyle[] = [];
202
+ public fills: Fill[] = [];
203
+ public number_formats: NumberFormat[] = [];
204
+ public base_number_format_id = 200; // ?
205
+
206
+ // public dom?: Tree;
207
+
208
+ public modified = false;
209
+
210
+ public Clamp(value: number, min: number, max: number): number {
211
+ return Math.max(min, Math.min(value, max));
212
+ }
213
+
214
+ public TintColor(base: string, tint: number): string {
215
+
216
+ let r = parseInt(base.substr(0, 2), 16);
217
+ let g = parseInt(base.substr(2, 2), 16);
218
+ let b = parseInt(base.substr(4, 2), 16);
219
+
220
+ if (tint < 0) {
221
+ r = Math.round(r * tint + r);
222
+ g = Math.round(g * tint + g);
223
+ b = Math.round(b * tint + b);
224
+ }
225
+ else {
226
+ r = Math.round((255 - r) * tint + r);
227
+ g = Math.round((255 - g) * tint + g);
228
+ b = Math.round((255 - b) * tint + b);
229
+ }
230
+
231
+ return [r, g, b].map((x) => {
232
+ const s = this.Clamp(x, 0, 255).toString(16);
233
+ return s.length < 2 ? ('0' + s) : s;
234
+ }).join('');
235
+
236
+ }
237
+
238
+ ///
239
+
240
+ public StyleOptionsFromProperties(source: Style.Properties): StyleOptions {
241
+
242
+ const composite: Style.Properties = // Style.Composite(list);
243
+ JSON.parse(JSON.stringify(source));
244
+
245
+ for (const key of Object.keys(composite) as Style.PropertyKeys[]) {
246
+ if (composite[key] === 'none') {
247
+ delete composite[key];
248
+ }
249
+ }
250
+
251
+ const font: Font = {};
252
+ const fill: Fill = { pattern_type: 'none' };
253
+ const border: BorderStyle = JSON.parse(JSON.stringify(default_border));
254
+
255
+ const options: StyleOptions = {
256
+ font, border,
257
+ };
258
+
259
+ if (composite.number_format) {
260
+
261
+ // we have some symbolic number formats that we'll need to
262
+ // translate. these are defined by the cache.
263
+
264
+ options.number_format = {
265
+ format: NumberFormatCache.Translate(composite.number_format),
266
+ symbolic_name: composite.number_format, // for reference later
267
+ };
268
+
269
+ }
270
+
271
+ if (composite.font_size?.unit && composite.font_size.value) {
272
+ if (composite.font_size.unit !== 'pt') {
273
+ console.warn(`can't handle non-point font (FIXME)`);
274
+ }
275
+ else {
276
+ font.size = composite.font_size.value;
277
+ }
278
+ }
279
+
280
+ if (composite.bold) font.bold = true;
281
+ if (composite.italic) font.italic = true;
282
+ if (composite.underline) font.underline = true;
283
+
284
+ //if (composite.text_color && composite.text_color !== Style.DefaultProperties.text_color) {
285
+ // font.color_argb = composite.text_color;
286
+ //}
287
+
288
+ if (composite.text) {
289
+ if (composite.text.text) {
290
+ font.color_argb = composite.text.text;
291
+ }
292
+ else if (typeof composite.text.theme === 'number') {
293
+ font.color_theme = composite.text.theme;
294
+ if (composite.text.tint) {
295
+ font.color_tint = composite.text.tint;
296
+ }
297
+ }
298
+ }
299
+
300
+ const TranslateBorder = (src: Style.CompositeBorderEdge, dest: BorderEdge) => {
301
+ if (src.width) {
302
+ dest.style = 'thin';
303
+ if (src.color.text) {
304
+ dest.rgba =src.color.text;
305
+ }
306
+ else if (typeof src.color.theme === 'number') {
307
+ dest.theme = src.color.theme;
308
+ if (src.color.tint) {
309
+ dest.tint = src.color.tint;
310
+ }
311
+ }
312
+ else {
313
+ dest.color = 64;
314
+ }
315
+ }
316
+ };
317
+
318
+ const composite_borders = Style.CompositeBorders(composite);
319
+ TranslateBorder(composite_borders.top, border.top);
320
+ TranslateBorder(composite_borders.left, border.left);
321
+ TranslateBorder(composite_borders.right, border.right);
322
+ TranslateBorder(composite_borders.bottom, border.bottom);
323
+
324
+ /*
325
+ if (composite.border_top) { // && composite.border_top_fill) {
326
+
327
+ border.top.style = 'thin';
328
+ if (composite.border_top_fill?.text) {
329
+ border.top.rgba = composite.border_top_fill.text;
330
+ }
331
+ else if (typeof composite.border_top_fill?.theme === 'number') {
332
+ border.top.theme = composite.border_top_fill.theme;
333
+ if (composite.border_top_fill.tint) {
334
+ border.top.tint = composite.border_top_fill.tint;
335
+ }
336
+ }
337
+ else {
338
+ border.top.color = 64;
339
+ }
340
+ }
341
+ if (composite.border_bottom) { // && composite.border_bottom_fill) {
342
+
343
+ if (composite.border_bottom > 1) {
344
+ border.bottom.style = 'double';
345
+ }
346
+ else {
347
+ border.bottom.style = 'thin';
348
+ }
349
+ if (composite.border_bottom_fill?.text) {
350
+ border.bottom.rgba = composite.border_bottom_fill.text;
351
+ }
352
+ else if (typeof composite.border_bottom_fill?.theme === 'number') {
353
+ border.bottom.theme = composite.border_bottom_fill.theme;
354
+ if (composite.border_bottom_fill.tint) {
355
+ border.bottom.tint = composite.border_bottom_fill.tint;
356
+ }
357
+ }
358
+ else {
359
+ border.bottom.color = 64;
360
+ }
361
+ }
362
+ if (composite.border_left) { // && composite.border_left_fill) {
363
+
364
+ border.left.style = 'thin';
365
+ if (composite.border_left_fill?.text) {
366
+ border.left.rgba = composite.border_left_fill.text;
367
+ }
368
+ else if (typeof composite.border_left_fill?.theme === 'number') {
369
+ border.left.theme = composite.border_left_fill.theme;
370
+ if (composite.border_left_fill.tint) {
371
+ border.left.tint = composite.border_left_fill.tint;
372
+ }
373
+ }
374
+ else {
375
+ border.left.color = 64;
376
+ }
377
+ }
378
+ if (composite.border_right) { // && composite.border_right_fill) {
379
+
380
+ border.right.style = 'thin';
381
+ if (composite.border_right_fill?.text) {
382
+ border.right.rgba = composite.border_right_fill.text;
383
+ }
384
+ else if (typeof composite.border_right_fill?.theme === 'number') {
385
+ border.right.theme = composite.border_right_fill.theme;
386
+ if (composite.border_right_fill.tint) {
387
+ border.right.tint = composite.border_right_fill.tint;
388
+ }
389
+ }
390
+ else {
391
+ border.right.color = 64;
392
+ }
393
+
394
+ // console.info("BXX", JSON.stringify(composite, undefined, 2), JSON.stringify(border, undefined, 2));
395
+
396
+ }
397
+ */
398
+
399
+ // leave blank for bottom, default
400
+
401
+ switch (composite.vertical_align) {
402
+ case Style.VerticalAlign.Top:
403
+ options.vertical_alignment = 'top';
404
+ break;
405
+ case Style.VerticalAlign.Middle:
406
+ options.vertical_alignment = 'center';
407
+ break;
408
+ }
409
+
410
+ switch (composite.horizontal_align) {
411
+ case Style.HorizontalAlign.Center:
412
+ options.horizontal_alignment = 'center';
413
+ break;
414
+ case Style.HorizontalAlign.Left:
415
+ options.horizontal_alignment = 'left';
416
+ break;
417
+ case Style.HorizontalAlign.Right:
418
+ options.horizontal_alignment = 'right';
419
+ break;
420
+ }
421
+
422
+ if (composite.fill) {
423
+ fill.pattern_type = 'solid';
424
+ if (composite.fill.text) {
425
+ fill.fg_color = { argb: composite.fill.text };
426
+ }
427
+ else if (typeof composite.fill.theme === 'number') {
428
+ fill.fg_color = { theme: composite.fill.theme };
429
+ if (composite.fill.tint) {
430
+ fill.fg_color.tint = composite.fill.tint;
431
+ }
432
+ }
433
+ else {
434
+ fill.fg_color = { theme: 1 };
435
+ }
436
+ options.fill = fill;
437
+ }
438
+
439
+ if (composite.wrap) {
440
+ options.wrap = true;
441
+ }
442
+
443
+ return options;
444
+
445
+ }
446
+
447
+ ///
448
+
449
+ public CellXfToStyle(xf: CellXf): Style.Properties {
450
+
451
+ const props: Style.Properties = {};
452
+
453
+ // number format
454
+
455
+ let format_string = StyleCache.default_styles[xf.number_format];
456
+ if (!format_string) {
457
+ for (const candidate of this.number_formats) {
458
+ if (candidate.id === xf.number_format) {
459
+ if (candidate.format) {
460
+ format_string = candidate.format;
461
+ break;
462
+ }
463
+ }
464
+ }
465
+ }
466
+
467
+ if (format_string) {
468
+
469
+ // Excel uses number formats like
470
+
471
+ // #,##0.00\ [$€-40C];[Red]\-#,##0.00\ [$€-40C]
472
+ // [$¥-411]#,##0;[Red]\-[$¥-411]#,##0
473
+ // _("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"??_);_(@_)
474
+
475
+ // where [$¥-411] encodes a yen symbol, [$€-40C] is the euro, &c.
476
+ // I have no idea what that encoding is, or where it comes from
477
+ // (can't find it in Excel documentation; should probably check OOo).
478
+
479
+ // for the time being we will just drop, and assume the symbol
480
+ // (in position 2) is correct. are we sure there are always 3 hex
481
+ // characters? and always negative? (...)
482
+
483
+ // OK, got it, these are Microsoft LCIDs in hex. so the format seems to be:
484
+ //
485
+ // square bracket, dollar sign, symbol, hyphen, hex LCID, square bracket
486
+ //
487
+ // we can safely drop this for now, AFAIAC. LCID seems to be (in hex)
488
+ // usually 3-4 digits, but I suppose lower is conceivable.
489
+
490
+ const encoding_regex = /\[\$(.)-[0-9A-Za-z]{1,4}\]/g;
491
+ format_string = format_string.replace(encoding_regex, '$1');
492
+
493
+ // there are also locale indicators with no symbol -- we can remove these
494
+ // for now, but we need to consider how to deal with them. (...)
495
+
496
+ // also this could be merged with the above.
497
+
498
+ const locale_regex = /\[\$-[0-9A-Za-z]{1,4}\]/g;
499
+ format_string = format_string.replace(locale_regex, '');
500
+
501
+ props.number_format = format_string;
502
+ }
503
+
504
+ // font attributes (atm we are ignoring size, face)
505
+
506
+ const base_font = this.fonts[0];
507
+ const font = this.fonts[xf.font || 0];
508
+
509
+ if (font) {
510
+ if (font.bold) props.bold = true;
511
+ if (font.italic) props.italic = true;
512
+ if (font.underline) props.underline = true;
513
+ if (font.strike) props.strike = true;
514
+
515
+ // implement font size... experimental. treat font size as % of
516
+ // base size, which we assume is in slot 0.
517
+
518
+ if (base_font && base_font.size && font.size && base_font.size !== font.size) {
519
+ props.font_size = {
520
+ value: 100 * font.size / base_font.size,
521
+ unit: '%',
522
+ };
523
+ }
524
+
525
+ if (font.color_argb) {
526
+ props.text = {
527
+ text: '#' + (
528
+ font.color_argb.length > 6 ?
529
+ font.color_argb.substr(font.color_argb.length - 6) :
530
+ font.color_argb)
531
+ };
532
+ }
533
+
534
+ else if (typeof font.color_theme === 'number') {
535
+
536
+ // const index = Theme.color_map[];
537
+
538
+ // skipping 0, it's implicit
539
+ // no it is not (1 is implicit?)
540
+
541
+ props.text = { theme: font.color_theme };
542
+
543
+ /*
544
+ // FIXME: update to theme
545
+ console.warn('update to theme colors');
546
+
547
+ const index = Theme.color_map[font.color_theme];
548
+ const color = this.theme.colors[index];
549
+
550
+ // why was I _not_ accepting system here? (...) there's an argument
551
+ // against system 1 -> default...
552
+
553
+ if (color && color.type !== 'system' && color.value) {
554
+ if (typeof font.color_tint === 'number') {
555
+ props.text = '#' + this.TintColor(color.value, font.color_tint);
556
+ }
557
+ else {
558
+ props.text = '#' + color.value;
559
+ }
560
+ }
561
+
562
+ else if (color && color.type === 'system' && color.value) {
563
+
564
+ // let's drop color index 1, as that should be default? (...)
565
+ // should do this higher up
566
+
567
+ if (font.color_theme !== 1) {
568
+ props.text = '#' + color.value;
569
+ }
570
+ }
571
+ */
572
+
573
+ }
574
+
575
+ }
576
+
577
+ const fill = this.fills[xf.fill || 0];
578
+ if (fill && fill.pattern_type !== 'none') {
579
+
580
+ if (fill.pattern_type === 'gray') {
581
+ const value = Math.round((fill.pattern_gray || 0) / 1000 * 255);
582
+ // props.background = `rgb(${value}, ${value}, ${value})`;
583
+ props.fill = { text: `rgb(${value}, ${value}, ${value})` };
584
+ }
585
+ if (fill.pattern_type === 'solid') {
586
+ if (fill.fg_color) {
587
+ if (fill.fg_color.argb) {
588
+ props.fill = { text: '#' + (
589
+ fill.fg_color.argb.length > 6 ?
590
+ fill.fg_color.argb.substr(fill.fg_color.argb.length - 6) :
591
+ fill.fg_color.argb)
592
+ };
593
+ }
594
+ else if (typeof fill.fg_color.theme === 'number') {
595
+
596
+ props.fill = {
597
+ theme: fill.fg_color.theme,
598
+ // tint: fill.fg_color.tint,
599
+ };
600
+
601
+ if (fill.fg_color.tint) {
602
+ props.fill.tint = Math.round(fill.fg_color.tint * 1000) / 1000;
603
+ }
604
+
605
+ /*
606
+ const index = Theme.color_map[fill.fg_color.theme];
607
+ const color = this.theme.colors[index];
608
+ // if (color && color.type !== 'system' && color.value) {
609
+ if (color && color.value) {
610
+ if (typeof fill.fg_color.tint === 'number') {
611
+ props.background = '#' + this.TintColor(color.value, fill.fg_color.tint);
612
+ }
613
+ else {
614
+ props.background = '#' + color.value;
615
+ }
616
+ }
617
+ */
618
+
619
+ }
620
+ }
621
+ }
622
+ }
623
+
624
+ // alignments (TODO: valign)
625
+
626
+ switch (xf.horizontal_alignment) {
627
+ case 'center':
628
+ props.horizontal_align = Style.HorizontalAlign.Center;
629
+ break;
630
+ case 'right':
631
+ props.horizontal_align = Style.HorizontalAlign.Right;
632
+ break;
633
+ case 'left':
634
+ props.horizontal_align = Style.HorizontalAlign.Left;
635
+ break;
636
+ }
637
+
638
+ switch (xf.vertical_alignment) {
639
+ case 'center':
640
+ props.vertical_align = Style.VerticalAlign.Middle;
641
+ break;
642
+ case 'top':
643
+ props.vertical_align = Style.VerticalAlign.Top;
644
+ break;
645
+ case 'bottom':
646
+ props.vertical_align = Style.VerticalAlign.Bottom;
647
+ break;
648
+ }
649
+
650
+ // wrap
651
+
652
+ if (xf.wrap_text) {
653
+ props.wrap = true;
654
+ }
655
+
656
+ // borders
657
+
658
+ const border = this.borders[xf.border || 0];
659
+ if (border) {
660
+ if (border.bottom.style) {
661
+ if (border.bottom.style === 'double') {
662
+ props.border_bottom = 2;
663
+ }
664
+ else {
665
+ props.border_bottom = 1;
666
+ }
667
+ }
668
+ if (border.left.style) props.border_left = 1;
669
+ if (border.top.style) props.border_top = 1;
670
+ if (border.right.style) props.border_right = 1;
671
+ }
672
+
673
+ return props;
674
+ }
675
+
676
+ /** map all cell xfs to styles; retain order */
677
+ public CellXfToStyles(): Style.Properties[] {
678
+ return this.cell_xfs.map((xf) => this.CellXfToStyle(xf));
679
+ }
680
+
681
+
682
+ public EnsureNumberFormat(number_format: NumberFormat): number {
683
+
684
+ // there are a lot of default, implicit number formats.
685
+ // we should probably find out what they are. for the time
686
+ // being, just use 0 for no properties.
687
+
688
+ if (typeof number_format.format === 'undefined') return 0;
689
+
690
+ // we changed the casing on this at some point, so let's be
691
+ // broad here. general is important because it has the magic
692
+ // decimal point, we don't want to revert to an explicit style
693
+ // because there's no description syntax for that
694
+
695
+ if (number_format.symbolic_name) {
696
+ if (/^general$/i.test(number_format.symbolic_name)) {
697
+ return 0;
698
+ }
699
+ }
700
+
701
+ // check the rest of the built-in types... note this is not an array?
702
+ // (why not?) also, is the length guaranteed?
703
+
704
+ for (let i = 0; i < 100; i++) {
705
+ const check = StyleCache.default_styles[i];
706
+ if (check && check === number_format.format) {
707
+ return i;
708
+ }
709
+ }
710
+
711
+ for (const candidate of this.number_formats) {
712
+ if (candidate.format === number_format.format) return candidate.id || 0;
713
+ }
714
+
715
+ this.modified = true;
716
+
717
+ const new_format = {
718
+ id: this.base_number_format_id++,
719
+ format: number_format.format,
720
+ };
721
+ this.number_formats.push(new_format);
722
+
723
+ /*
724
+ if (!this.dom) throw new Error('missing dom');
725
+ let number_formats = this.dom.find('./numFmts');
726
+
727
+ if (!number_formats){
728
+ number_formats = Element('numFmts', {count: '1'});
729
+ const root = this.dom.getroot();
730
+ (root as any)._children = [number_formats].concat((root as any)._children);
731
+ // this.dom.getroot().append(number_formats);
732
+ }
733
+ else {
734
+ number_formats.attrib.count = (Number(number_formats.attrib.count || 0) + 1).toString();
735
+ }
736
+
737
+ number_formats.append(Element('numFmt', {
738
+ numFmtId: new_format.id.toString(),
739
+ formatCode: new_format.format,
740
+ }));
741
+ */
742
+
743
+ return new_format.id;
744
+
745
+ }
746
+
747
+ public CompareBorderEdge(a: BorderEdge, b: BorderEdge) {
748
+ return a.color === b.color
749
+ && a.rgba === b.rgba
750
+ && a.style === b.style
751
+ && a.theme === b.theme
752
+ && a.tint === b.tint;
753
+ }
754
+
755
+ public CompareBorder(a: BorderStyle, b: BorderStyle) {
756
+ return this.CompareBorderEdge(a.top, b.top)
757
+ && this.CompareBorderEdge(a.left, b.left)
758
+ && this.CompareBorderEdge(a.bottom, b.bottom)
759
+ && this.CompareBorderEdge(a.right, b.right)
760
+ && this.CompareBorderEdge(a.diagonal, b.diagonal);
761
+ }
762
+
763
+ public EnsureBorder(border: BorderStyle): number {
764
+
765
+ for (let i = 0; i < this.borders.length; i++ ){
766
+ const candidate = this.borders[i];
767
+
768
+ if (this.CompareBorder(candidate, border)){
769
+ return i;
770
+ }
771
+
772
+ }
773
+
774
+ this.modified = true;
775
+
776
+ const new_border: BorderStyle = JSON.parse(JSON.stringify(border)); // {...border};
777
+ this.borders.push(new_border);
778
+
779
+ /*
780
+
781
+ if (!this.dom) throw new Error('missing dom');
782
+ const borders = this.dom.find('./borders');
783
+
784
+ if (!borders) throw new Error('borders not found');
785
+ borders.attrib.count = (Number(borders.attrib.count || 0) + 1).toString();
786
+
787
+ const new_element = Element('border');
788
+
789
+ const left = Element('left');
790
+ if (border.left_style) {
791
+ left.attrib.style = border.left_style;
792
+ // left.append(Element('color', {indexed: (border.left_color || 0).toString() }));
793
+ const attrs: ColorAttributes = {};
794
+ if (border.left_color_rgba) {
795
+ attrs.rgb = border.left_color_rgba;
796
+ }
797
+ else if (typeof border.left_color_theme === 'number') {
798
+ attrs.theme = border.left_color_theme.toString();
799
+ if (border.left_color_tint) {
800
+ attrs.tint = border.left_color_tint.toString();
801
+ }
802
+ }
803
+ else {
804
+ attrs.indexed = (border.left_color || 0).toString();
805
+ }
806
+ left.append(Element('color', attrs as ElementTree.Attributes));
807
+ }
808
+ new_element.append(left);
809
+
810
+ const right = Element('right');
811
+ if (border.right_style) {
812
+ right.attrib.style = border.right_style;
813
+ // right.append(Element('color', {indexed: (border.right_color || 0).toString() }));
814
+ const attrs: ColorAttributes = {};
815
+ if (border.right_color_rgba) {
816
+ attrs.rgb = border.right_color_rgba;
817
+ }
818
+ else if (typeof border.right_color_theme === 'number') {
819
+ attrs.theme = border.right_color_theme.toString();
820
+ if (border.right_color_tint) {
821
+ attrs.tint = border.right_color_tint.toString();
822
+ }
823
+ }
824
+ else {
825
+ attrs.indexed = (border.right_color || 0).toString();
826
+ }
827
+ right.append(Element('color', attrs as ElementTree.Attributes));
828
+
829
+ }
830
+ new_element.append(right);
831
+
832
+ const top = Element('top');
833
+ if (border.top_style) {
834
+ top.attrib.style = border.top_style;
835
+ const attrs: ColorAttributes = {};
836
+ if (border.top_color_rgba) {
837
+ attrs.rgb = border.top_color_rgba;
838
+ }
839
+ else if (typeof border.top_color_theme === 'number') {
840
+ attrs.theme = border.top_color_theme.toString();
841
+ if (border.top_color_tint) {
842
+ attrs.tint = border.top_color_tint.toString();
843
+ }
844
+ }
845
+ else {
846
+ attrs.indexed = (border.top_color || 0).toString();
847
+ }
848
+ top.append(Element('color', attrs as ElementTree.Attributes));
849
+ }
850
+ new_element.append(top);
851
+
852
+ const bottom = Element('bottom');
853
+ if (border.bottom_style) {
854
+
855
+ // console.info("BOTTOM STYLE", border);
856
+
857
+ bottom.attrib.style = border.bottom_style;
858
+ // bottom.append(Element('color', {indexed: (border.bottom_color || 0).toString() }));
859
+ const attrs: ColorAttributes = {};
860
+ if (border.bottom_color_rgba) {
861
+ attrs.rgb = border.bottom_color_rgba;
862
+ }
863
+ else if (typeof border.bottom_color_theme === 'number') {
864
+ attrs.theme = border.bottom_color_theme.toString();
865
+ if (border.bottom_color_tint) {
866
+ attrs.tint = border.bottom_color_tint.toString();
867
+ }
868
+ }
869
+ else {
870
+ attrs.indexed = (border.bottom_color || 0).toString();
871
+ }
872
+ bottom.append(Element('color', attrs as ElementTree.Attributes));
873
+ }
874
+ new_element.append(bottom);
875
+
876
+ const diagonal = Element('diagonal');
877
+ if (border.diagonal_style) {
878
+ diagonal.attrib.style = border.diagonal_style;
879
+ diagonal.append(Element('color', {indexed: (border.diagonal_color || 0).toString() }));
880
+ }
881
+ new_element.append(diagonal);
882
+
883
+ borders.append(new_element);
884
+ */
885
+
886
+ return this.borders.length - 1;
887
+ }
888
+
889
+
890
+ public MatchColor(a: XlColor|undefined, b: XlColor|undefined): boolean {
891
+
892
+ if (!a && !b) { return true; }
893
+ if (!a || !b) { return false; }
894
+
895
+ return ( a.argb === b.argb
896
+ && a.indexed === b.indexed
897
+ && a.theme === b.theme
898
+ && a.tint === b.tint);
899
+
900
+ }
901
+
902
+ public EnsureFill(fill: Fill): number {
903
+
904
+ for (let i = 0; i < this.fills.length; i++) {
905
+ const candidate = this.fills[i];
906
+ if ( this.MatchColor(fill.bg_color, candidate.bg_color)
907
+ && this.MatchColor(fill.fg_color, candidate.fg_color)
908
+ && fill.pattern_gray === candidate.pattern_gray
909
+ && fill.pattern_type === candidate.pattern_type ) {
910
+ return i;
911
+ }
912
+ }
913
+
914
+ this.modified = true;
915
+
916
+ const new_fill: Fill = {...fill};
917
+ this.fills.push(new_fill);
918
+
919
+ /*
920
+ // add the node structure
921
+
922
+ if (!this.dom) throw new Error('missing dom');
923
+ const fills = this.dom.find('./fills');
924
+
925
+ if (!fills) throw new Error('fills not found');
926
+ fills.attrib.count = (Number(fills.attrib.count || 0) + 1).toString();
927
+
928
+ const new_element = Element('fill');
929
+ const pattern_fill = Element('patternFill', { patternType: fill.pattern_type });
930
+
931
+ switch (fill.pattern_type) {
932
+ case 'none':
933
+ break;
934
+ case 'solid':
935
+ if (fill.fg_color) {
936
+ const attrs: Record<string, string> = {};
937
+
938
+ if (fill.fg_color.argb) { attrs.rgb = fill.fg_color.argb; }
939
+ if (fill.fg_color.indexed) { attrs.indexed = fill.fg_color.indexed.toString(); }
940
+ if (fill.fg_color.tint) { attrs.tint = fill.fg_color.tint.toString(); }
941
+ if (typeof fill.fg_color.theme !== 'undefined') { attrs.theme = fill.fg_color.theme.toString(); }
942
+
943
+ pattern_fill.append(Element('fgColor', attrs));
944
+ }
945
+ break;
946
+ case 'gray':
947
+
948
+ // ...
949
+
950
+ break;
951
+ }
952
+
953
+ new_element.append(pattern_fill);
954
+
955
+ fills.append(new_element);
956
+ */
957
+
958
+ return this.fills.length - 1;
959
+ }
960
+
961
+ /**
962
+ * for the time being we are ignoring font face, family, size, color and
963
+ * scheme (whatever that is). every font is based on font 0, the default.
964
+ * we add bold/italic/underline as necessary.
965
+ */
966
+ public EnsureFont(font: Font): number {
967
+
968
+ // this is what we create, so we need to test against it
969
+
970
+ const composite_font: Font = {...this.fonts[0], ...font};
971
+
972
+ // const props = Object.keys(font).filter((key) => typeof (font as any)[key] !== 'undefined');
973
+ for (let i = 0; i < this.fonts.length; i++ ){
974
+ const candidate = this.fonts[i];
975
+ //let match = true;
976
+ //for (const prop of props) {
977
+ // match = match && (font as any)[prop] === (candidate as any)[prop];
978
+ //}
979
+
980
+ const match = (candidate.bold === composite_font.bold)
981
+ && (candidate.italic === composite_font.italic)
982
+ && (candidate.underline === composite_font.underline)
983
+ && (candidate.size === composite_font.size)
984
+ && (candidate.strike === composite_font.strike)
985
+ && (candidate.color_argb === composite_font.color_argb)
986
+ && (candidate.color_theme === composite_font.color_theme)
987
+ && (candidate.color_tint === composite_font.color_tint)
988
+ && (candidate.family === composite_font.family);
989
+
990
+ if (match) {
991
+ return i;
992
+ }
993
+
994
+ }
995
+
996
+ this.modified = true;
997
+
998
+ // const composite_font = test; // {...this.fonts[0], ...font};
999
+ this.fonts.push(composite_font);
1000
+
1001
+ /*
1002
+ // add the node structure
1003
+
1004
+ if (!this.dom) throw new Error('missing dom');
1005
+ const fonts = this.dom.find('./fonts');
1006
+
1007
+ if (!fonts) throw new Error('fonts not found');
1008
+ fonts.attrib.count = (Number(fonts.attrib.count || 0) + 1).toString();
1009
+
1010
+ const new_element = Element('font');
1011
+ new_element.append(Element('sz', { val: (composite_font.size || 0).toString() }));
1012
+
1013
+ // new_element.append(Element('color', { theme: (new_font.color_theme || 0).toString() }));
1014
+ // new_element.append(Element('color', { theme: (new_font.color_theme || 0).toString() }));
1015
+
1016
+ if (typeof composite_font.color_argb !== 'undefined') {
1017
+ new_element.append(Element('color', { rgb: composite_font.color_argb }));
1018
+ }
1019
+ else {
1020
+ new_element.append(Element('color', { theme: (composite_font.color_theme || 0).toString() }));
1021
+ }
1022
+
1023
+ new_element.append(Element('name', { val: composite_font.name }));
1024
+ new_element.append(Element('family', { val: (composite_font.family || 0).toString() }));
1025
+ new_element.append(Element('scheme', { val: composite_font.scheme }));
1026
+
1027
+ if (composite_font.bold) new_element.append(Element('b'));
1028
+ if (composite_font.underline) new_element.append(Element('u'));
1029
+ if (composite_font.italic) new_element.append(Element('i'));
1030
+ if (composite_font.strike) new_element.append(Element('strike'));
1031
+
1032
+ fonts.append(new_element);
1033
+ */
1034
+
1035
+ return this.fonts.length - 1;
1036
+
1037
+ }
1038
+
1039
+ public EnsureStyle(options: StyleOptions): number {
1040
+
1041
+ // find indexes for props
1042
+ const font_index = this.EnsureFont(options.font || {});
1043
+ const border_index = this.EnsureBorder(options.border || default_border);
1044
+ const number_format_index = this.EnsureNumberFormat(options.number_format || {});
1045
+ const fill_index = this.EnsureFill(options.fill || { pattern_type: 'none' });
1046
+
1047
+ // now find an XF that matches
1048
+ for (let i = 0; i < this.cell_xfs.length; i++){
1049
+ const xf = this.cell_xfs[i];
1050
+ if (xf.font === font_index &&
1051
+ xf.fill === fill_index &&
1052
+ xf.border === border_index &&
1053
+ xf.number_format === number_format_index &&
1054
+ !!xf.wrap_text === !!options.wrap &&
1055
+ ((!options.horizontal_alignment && !xf.horizontal_alignment) || options.horizontal_alignment === xf.horizontal_alignment) &&
1056
+ ((!options.vertical_alignment && !xf.vertical_alignment) || options.vertical_alignment === xf.vertical_alignment)) {
1057
+
1058
+ return i;
1059
+ }
1060
+ }
1061
+
1062
+ this.modified = true;
1063
+
1064
+ // need a new XF -- defaults?
1065
+ const new_xf: CellXf = {
1066
+ font: font_index,
1067
+ fill: fill_index,
1068
+ border: border_index,
1069
+ number_format: number_format_index,
1070
+ };
1071
+
1072
+ if (options.horizontal_alignment) {
1073
+ new_xf.horizontal_alignment = options.horizontal_alignment;
1074
+ }
1075
+ if (options.vertical_alignment) {
1076
+ new_xf.vertical_alignment = options.vertical_alignment;
1077
+ }
1078
+ if (options.wrap) {
1079
+ new_xf.wrap_text = true;
1080
+ }
1081
+
1082
+ this.cell_xfs.push(new_xf);
1083
+
1084
+ /*
1085
+
1086
+ // add the node structure
1087
+
1088
+ if (!this.dom) throw new Error('missing dom');
1089
+ const xfs = this.dom.find('./cellXfs');
1090
+
1091
+ if (!xfs) throw new Error('xfs not found');
1092
+ xfs.attrib.count = (Number(xfs.attrib.count || 0) + 1).toString();
1093
+
1094
+ const new_element = Element('xf', {
1095
+ borderId: new_xf.border.toString(),
1096
+ fillId: new_xf.fill.toString(),
1097
+ fontId: new_xf.font.toString(),
1098
+ numFmtId: new_xf.number_format.toString(),
1099
+ });
1100
+
1101
+ if (new_xf.horizontal_alignment || new_xf.vertical_alignment) {
1102
+ const attrs: {[index: string]: string} = {};
1103
+ if (new_xf.horizontal_alignment) {
1104
+ attrs.horizontal = new_xf.horizontal_alignment;
1105
+ }
1106
+ if (new_xf.vertical_alignment) {
1107
+ attrs.vertical = new_xf.vertical_alignment;
1108
+ }
1109
+ if (new_xf.wrap_text) {
1110
+ attrs.wrapText = '1';
1111
+ }
1112
+ new_element.append(Element('alignment', attrs));
1113
+ }
1114
+
1115
+ if (typeof new_xf.xfid !== 'undefined') {
1116
+ new_element.attrib.xfId = new_xf.xfid.toString();
1117
+ }
1118
+
1119
+ xfs.append(new_element);
1120
+ */
1121
+
1122
+ return this.cell_xfs.length - 1;
1123
+
1124
+ }
1125
+
1126
+ public FromXML(xml: any, theme: Theme): void {
1127
+
1128
+ const FindAll = XMLUtils.FindAll.bind(XMLUtils, xml);
1129
+
1130
+ this.theme = theme;
1131
+
1132
+ // ---
1133
+
1134
+ let composite = FindAll('styleSheet/numFmts/numFmt');
1135
+
1136
+ this.number_formats = composite.map(element => ({
1137
+ id: Number(element.a$?.numFmtId || 0),
1138
+ format: Unescape(element.a$?.formatCode || ''),
1139
+ }));
1140
+
1141
+ // ---
1142
+
1143
+ composite = FindAll('styleSheet/borders/border');
1144
+
1145
+ this.borders = composite.map(element => {
1146
+
1147
+ const border: BorderStyle = JSON.parse(JSON.stringify(default_border));
1148
+
1149
+ // we're relying on these being empty strings -> falsy, not a good look
1150
+
1151
+ if (element.left) {
1152
+ border.left.style = element.left.a$.style;
1153
+ border.left.color = Number(element.left.color?.a$?.indexed);
1154
+ }
1155
+
1156
+ if (element.right) {
1157
+ border.right.style = element.right.a$.style;
1158
+ border.right.color = Number(element.right.color?.a$?.indexed);
1159
+ }
1160
+
1161
+ if (element.top) {
1162
+ border.top.style = element.top.a$.style;
1163
+ border.top.color = Number(element.top.color?.a$?.indexed);
1164
+ }
1165
+
1166
+ if (element.bottom) {
1167
+ border.bottom.style = element.bottom.a$.style;
1168
+ border.bottom.color = Number(element.bottom.color?.a$?.indexed);
1169
+ }
1170
+
1171
+ return border;
1172
+
1173
+ });
1174
+
1175
+ // ---
1176
+
1177
+ composite = FindAll('styleSheet/cellXfs/xf');
1178
+ this.cell_xfs = composite.map(element => {
1179
+
1180
+ const xf: CellXf = {
1181
+ number_format: Number(element.a$.numFmtId),
1182
+ font: Number(element.a$.fontId),
1183
+ fill: Number(element.a$.fillId),
1184
+ border: Number(element.a$.borderId),
1185
+ xfid: Number(element.a$.xfId),
1186
+ };
1187
+
1188
+ if (element.alignment) {
1189
+ xf.horizontal_alignment = element.alignment.a$.horizontal;
1190
+ xf.vertical_alignment = element.alignment.a$.vertical;
1191
+ xf.wrap_text = !!element.alignment.a$.wrapText;
1192
+ }
1193
+
1194
+ return xf;
1195
+
1196
+ });
1197
+
1198
+ // ---
1199
+
1200
+ composite = FindAll('styleSheet/fills/fill');
1201
+
1202
+ this.fills = composite.map(element => {
1203
+
1204
+ const fill: Fill = { pattern_type: 'none' };
1205
+ if (element.patternFill) {
1206
+ const type = element.patternFill.a$?.patternType;
1207
+ switch (type) {
1208
+ case 'none':
1209
+ case undefined:
1210
+ break;
1211
+
1212
+ case 'solid':
1213
+ fill.pattern_type = 'solid';
1214
+ if (element.patternFill.fgColor) {
1215
+ fill.fg_color = {
1216
+ theme: element.patternFill.fgColor.a$?.theme ? Number(element.patternFill.fgColor.a$.theme) : undefined,
1217
+ indexed: element.patternFill.fgColor.a$?.indexed ? Number(element.patternFill.fgColor.a$.indexed) : undefined,
1218
+ tint: element.patternFill.fgColor.a$?.tint ? Number(element.patternFill.fgColor.a$.tint) : undefined,
1219
+ argb: element.patternFill.fgColor.a$?.rgb,
1220
+ };
1221
+ }
1222
+ break;
1223
+
1224
+ default:
1225
+ {
1226
+ const match = type?.match(/^gray(\d+)$/);
1227
+ if (match) {
1228
+ fill.pattern_type = 'gray';
1229
+ fill.pattern_gray = Number(match[1]);
1230
+ break;
1231
+ }
1232
+ }
1233
+
1234
+ }
1235
+ }
1236
+
1237
+ return fill;
1238
+
1239
+ });
1240
+
1241
+ // ---
1242
+
1243
+ composite = FindAll('styleSheet/fonts/font');
1244
+
1245
+ this.fonts = composite.map(element => {
1246
+
1247
+ const font: Font = {};
1248
+
1249
+ font.italic = !!(typeof element.i !== 'undefined');
1250
+ font.bold = !!(typeof element.b !== 'undefined');
1251
+ font.underline = !!(typeof element.u !== 'undefined');
1252
+ font.strike = !!(typeof element.strike !== 'undefined');
1253
+
1254
+ if (element.sz) {
1255
+ font.size = Number(element.sz.a$.val);
1256
+ }
1257
+ if (element.scheme) {
1258
+ font.scheme = element.scheme.a$.val;
1259
+ }
1260
+ if (element.name) {
1261
+ font.name = element.name.a$.val;
1262
+ }
1263
+ if (element.family) {
1264
+ font.family = Number(element.family.a$.val);
1265
+ }
1266
+
1267
+ if (element.color) {
1268
+ if (element.color.a$?.theme) {
1269
+ font.color_theme = Number(element.color.a$.theme);
1270
+ }
1271
+ if (element.color.a$?.tint) {
1272
+ font.color_tint = Number(element.color.a$.tint);
1273
+ }
1274
+ if (element.color.a$?.rgb) {
1275
+ font.color_argb = element.color.a$.rgb;
1276
+ }
1277
+ }
1278
+
1279
+ return font;
1280
+
1281
+ });
1282
+
1283
+ }
1284
+
1285
+ }