@cj-tech-master/excelts 4.1.0 → 4.2.0-canary.20260110080706.375ff37

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 (56) hide show
  1. package/dist/browser/index.browser.d.ts +2 -0
  2. package/dist/browser/index.browser.js +1 -0
  3. package/dist/browser/index.d.ts +2 -0
  4. package/dist/browser/index.js +1 -0
  5. package/dist/browser/modules/excel/form-control.d.ts +157 -0
  6. package/dist/browser/modules/excel/form-control.js +267 -0
  7. package/dist/browser/modules/excel/utils/ooxml-paths.d.ts +2 -0
  8. package/dist/browser/modules/excel/utils/ooxml-paths.js +8 -0
  9. package/dist/browser/modules/excel/worksheet.d.ts +32 -0
  10. package/dist/browser/modules/excel/worksheet.js +44 -1
  11. package/dist/browser/modules/excel/xlsx/rel-type.d.ts +1 -0
  12. package/dist/browser/modules/excel/xlsx/rel-type.js +2 -1
  13. package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +17 -1
  14. package/dist/browser/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.d.ts +22 -0
  15. package/dist/browser/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.js +52 -0
  16. package/dist/browser/modules/excel/xlsx/xform/drawing/vml-drawing-xform.d.ts +44 -0
  17. package/dist/browser/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +181 -0
  18. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +24 -1
  19. package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +1 -0
  20. package/dist/browser/modules/excel/xlsx/xlsx.browser.js +24 -5
  21. package/dist/cjs/index.js +3 -1
  22. package/dist/cjs/modules/excel/form-control.js +270 -0
  23. package/dist/cjs/modules/excel/utils/ooxml-paths.js +10 -0
  24. package/dist/cjs/modules/excel/worksheet.js +44 -1
  25. package/dist/cjs/modules/excel/xlsx/rel-type.js +2 -1
  26. package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +16 -0
  27. package/dist/cjs/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.js +55 -0
  28. package/dist/cjs/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +184 -0
  29. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +23 -0
  30. package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +23 -4
  31. package/dist/esm/index.browser.js +1 -0
  32. package/dist/esm/index.js +1 -0
  33. package/dist/esm/modules/excel/form-control.js +267 -0
  34. package/dist/esm/modules/excel/utils/ooxml-paths.js +8 -0
  35. package/dist/esm/modules/excel/worksheet.js +44 -1
  36. package/dist/esm/modules/excel/xlsx/rel-type.js +2 -1
  37. package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +17 -1
  38. package/dist/esm/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.js +52 -0
  39. package/dist/esm/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +181 -0
  40. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +24 -1
  41. package/dist/esm/modules/excel/xlsx/xlsx.browser.js +24 -5
  42. package/dist/iife/THIRD_PARTY_NOTICES.md +112 -0
  43. package/dist/iife/excelts.iife.js +466 -30
  44. package/dist/iife/excelts.iife.js.map +1 -1
  45. package/dist/iife/excelts.iife.min.js +30 -30
  46. package/dist/types/index.browser.d.ts +2 -0
  47. package/dist/types/index.d.ts +2 -0
  48. package/dist/types/modules/excel/form-control.d.ts +157 -0
  49. package/dist/types/modules/excel/utils/ooxml-paths.d.ts +2 -0
  50. package/dist/types/modules/excel/worksheet.d.ts +32 -0
  51. package/dist/types/modules/excel/xlsx/rel-type.d.ts +1 -0
  52. package/dist/types/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.d.ts +22 -0
  53. package/dist/types/modules/excel/xlsx/xform/drawing/vml-drawing-xform.d.ts +44 -0
  54. package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +1 -0
  55. package/package.json +7 -7
  56. /package/dist/{LICENSE → iife/LICENSE} +0 -0
@@ -0,0 +1,270 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FormCheckbox = exports.EMU_PER_POINT = exports.EMU_PER_PIXEL = void 0;
4
+ const col_cache_1 = require("./utils/col-cache.js");
5
+ /**
6
+ * Form Control Checkbox - Legacy checkbox control compatible with Office 2007+ and WPS/LibreOffice
7
+ *
8
+ * Unlike the modern In-Cell Checkbox (which only works in Microsoft 365),
9
+ * Form Control Checkboxes are floating controls that work in virtually all
10
+ * spreadsheet applications.
11
+ */
12
+ // ============================================================================
13
+ // Constants (exported for use by xforms)
14
+ // ============================================================================
15
+ /** EMU (English Metric Units) to pixels conversion factor at 96 DPI */
16
+ exports.EMU_PER_PIXEL = 9525;
17
+ /** EMU to points conversion factor */
18
+ exports.EMU_PER_POINT = 12700;
19
+ /** Default column offset in EMUs (~15 pixels) */
20
+ const DEFAULT_COL_OFF = 142875;
21
+ /** Default row offset in EMUs (~3 pixels) */
22
+ const DEFAULT_ROW_OFF = 28575;
23
+ /** Default end column offset in EMUs (~29 pixels) */
24
+ const DEFAULT_END_COL_OFF = 276225;
25
+ /** Default end row offset in EMUs (~20 pixels) */
26
+ const DEFAULT_END_ROW_OFF = 190500;
27
+ // ============================================================================
28
+ // FormCheckbox Class
29
+ // ============================================================================
30
+ class FormCheckbox {
31
+ constructor(worksheet, range, options) {
32
+ this.worksheet = worksheet;
33
+ // Parse range to get anchors
34
+ const { tl, br } = this._parseRange(range);
35
+ // Generate shape ID (starting from 1025)
36
+ const existingCount = worksheet.formControls?.length || 0;
37
+ const shapeId = 1025 + existingCount;
38
+ // Parse link cell reference
39
+ let link;
40
+ if (options?.link) {
41
+ // Ensure absolute reference format
42
+ link = this._toAbsoluteRef(options.link);
43
+ }
44
+ // Note: ctrlPropId is set later in worksheet-xform.ts prepare() for global uniqueness
45
+ this.model = {
46
+ shapeId,
47
+ ctrlPropId: 0, // Placeholder, set during prepare()
48
+ tl,
49
+ br,
50
+ link,
51
+ checked: options?.checked ? "Checked" : "Unchecked",
52
+ text: options?.text ?? "",
53
+ noThreeD: options?.noThreeD ?? true,
54
+ print: options?.print ?? false
55
+ };
56
+ }
57
+ /**
58
+ * Get the checked state
59
+ */
60
+ get checked() {
61
+ return this.model.checked === "Checked";
62
+ }
63
+ /**
64
+ * Set the checked state
65
+ */
66
+ set checked(value) {
67
+ this.model.checked = value ? "Checked" : "Unchecked";
68
+ }
69
+ /**
70
+ * Get the linked cell address
71
+ */
72
+ get link() {
73
+ return this.model.link;
74
+ }
75
+ /**
76
+ * Set the linked cell address
77
+ */
78
+ set link(value) {
79
+ this.model.link = value ? this._toAbsoluteRef(value) : undefined;
80
+ }
81
+ /**
82
+ * Get the label text
83
+ */
84
+ get text() {
85
+ return this.model.text;
86
+ }
87
+ /**
88
+ * Set the label text
89
+ */
90
+ set text(value) {
91
+ this.model.text = value;
92
+ }
93
+ /**
94
+ * Convert cell reference to absolute format (e.g., "A1" -> "$A$1")
95
+ */
96
+ _toAbsoluteRef(ref) {
97
+ // If already absolute, return as-is
98
+ if (ref.includes("$")) {
99
+ return ref;
100
+ }
101
+ // Parse and convert
102
+ const addr = col_cache_1.colCache.decodeAddress(ref);
103
+ return `$${col_cache_1.colCache.n2l(addr.col)}$${addr.row}`;
104
+ }
105
+ /**
106
+ * Parse range input into anchor positions
107
+ */
108
+ _parseRange(range) {
109
+ let tl;
110
+ let br;
111
+ if (typeof range === "string") {
112
+ // Parse cell reference like "B2" or "B2:D3"
113
+ const decoded = col_cache_1.colCache.decode(range);
114
+ if ("top" in decoded) {
115
+ // It's a range like "B2:D3"
116
+ tl = {
117
+ col: decoded.left - 1, // Convert to 0-based
118
+ colOff: DEFAULT_COL_OFF,
119
+ row: decoded.top - 1,
120
+ rowOff: DEFAULT_ROW_OFF
121
+ };
122
+ br = {
123
+ col: decoded.right - 1,
124
+ colOff: DEFAULT_END_COL_OFF,
125
+ row: decoded.bottom - 1,
126
+ rowOff: DEFAULT_END_ROW_OFF
127
+ };
128
+ }
129
+ else {
130
+ // Single cell reference - create default size checkbox
131
+ tl = {
132
+ col: decoded.col - 1,
133
+ colOff: DEFAULT_COL_OFF,
134
+ row: decoded.row - 1,
135
+ rowOff: DEFAULT_ROW_OFF
136
+ };
137
+ // Default size: about 2 columns wide, 1 row tall
138
+ br = {
139
+ col: decoded.col + 1,
140
+ colOff: DEFAULT_END_COL_OFF,
141
+ row: decoded.row,
142
+ rowOff: DEFAULT_END_ROW_OFF
143
+ };
144
+ }
145
+ }
146
+ else if ("startCol" in range) {
147
+ // startCol/startRow/endCol/endRow format (0-based)
148
+ tl = {
149
+ col: range.startCol,
150
+ colOff: range.startColOff ?? DEFAULT_COL_OFF,
151
+ row: range.startRow,
152
+ rowOff: range.startRowOff ?? DEFAULT_ROW_OFF
153
+ };
154
+ br = {
155
+ col: range.endCol,
156
+ colOff: range.endColOff ?? DEFAULT_END_COL_OFF,
157
+ row: range.endRow,
158
+ rowOff: range.endRowOff ?? DEFAULT_END_ROW_OFF
159
+ };
160
+ }
161
+ else {
162
+ // Object format with tl/br
163
+ if (typeof range.tl === "string") {
164
+ const decoded = col_cache_1.colCache.decodeAddress(range.tl);
165
+ tl = {
166
+ col: decoded.col - 1,
167
+ colOff: DEFAULT_COL_OFF,
168
+ row: decoded.row - 1,
169
+ rowOff: DEFAULT_ROW_OFF
170
+ };
171
+ }
172
+ else {
173
+ tl = {
174
+ col: range.tl.col,
175
+ colOff: range.tl.colOff ?? DEFAULT_COL_OFF,
176
+ row: range.tl.row,
177
+ rowOff: range.tl.rowOff ?? DEFAULT_ROW_OFF
178
+ };
179
+ }
180
+ if (range.br) {
181
+ if (typeof range.br === "string") {
182
+ const decoded = col_cache_1.colCache.decodeAddress(range.br);
183
+ br = {
184
+ col: decoded.col - 1,
185
+ colOff: DEFAULT_END_COL_OFF,
186
+ row: decoded.row - 1,
187
+ rowOff: DEFAULT_END_ROW_OFF
188
+ };
189
+ }
190
+ else {
191
+ br = {
192
+ col: range.br.col,
193
+ colOff: range.br.colOff ?? DEFAULT_END_COL_OFF,
194
+ row: range.br.row,
195
+ rowOff: range.br.rowOff ?? DEFAULT_END_ROW_OFF
196
+ };
197
+ }
198
+ }
199
+ else {
200
+ // Default size
201
+ br = {
202
+ col: tl.col + 2,
203
+ colOff: DEFAULT_END_COL_OFF,
204
+ row: tl.row + 1,
205
+ rowOff: DEFAULT_END_ROW_OFF
206
+ };
207
+ }
208
+ }
209
+ return { tl, br };
210
+ }
211
+ // =========================================================================
212
+ // Instance methods - delegate to static methods
213
+ // =========================================================================
214
+ /**
215
+ * Convert anchor to VML anchor string format
216
+ * Format: "fromCol, fromColOff, fromRow, fromRowOff, toCol, toColOff, toRow, toRowOff"
217
+ * VML uses pixels for offsets
218
+ */
219
+ getVmlAnchor() {
220
+ return FormCheckbox.getVmlAnchor(this.model);
221
+ }
222
+ /**
223
+ * Get VML style string for positioning
224
+ */
225
+ getVmlStyle() {
226
+ return FormCheckbox.getVmlStyle(this.model);
227
+ }
228
+ /**
229
+ * Get the numeric checked value for VML (0, 1, or 2)
230
+ */
231
+ getVmlCheckedValue() {
232
+ return FormCheckbox.getVmlCheckedValue(this.model);
233
+ }
234
+ // =========================================================================
235
+ // Static utility methods - can be used with FormCheckboxModel directly
236
+ // =========================================================================
237
+ /**
238
+ * Convert anchor to VML anchor string format from model
239
+ */
240
+ static getVmlAnchor(model) {
241
+ const { tl, br } = model;
242
+ const tlColOff = Math.round(tl.colOff / exports.EMU_PER_PIXEL);
243
+ const tlRowOff = Math.round(tl.rowOff / exports.EMU_PER_PIXEL);
244
+ const brColOff = Math.round(br.colOff / exports.EMU_PER_PIXEL);
245
+ const brRowOff = Math.round(br.rowOff / exports.EMU_PER_PIXEL);
246
+ return `${tl.col}, ${tlColOff}, ${tl.row}, ${tlRowOff}, ${br.col}, ${brColOff}, ${br.row}, ${brRowOff}`;
247
+ }
248
+ /**
249
+ * Get VML style string for positioning from model
250
+ */
251
+ static getVmlStyle(model) {
252
+ const marginLeft = Math.round(model.tl.colOff / exports.EMU_PER_POINT);
253
+ const marginTop = Math.round(model.tl.rowOff / exports.EMU_PER_POINT);
254
+ return `position:absolute;margin-left:${marginLeft}pt;margin-top:${marginTop}pt;width:96pt;height:18pt;z-index:1;visibility:visible`;
255
+ }
256
+ /**
257
+ * Get the numeric checked value for VML from model (0, 1, or 2)
258
+ */
259
+ static getVmlCheckedValue(model) {
260
+ switch (model.checked) {
261
+ case "Checked":
262
+ return 1;
263
+ case "Mixed":
264
+ return 2;
265
+ default:
266
+ return 0;
267
+ }
268
+ }
269
+ }
270
+ exports.FormCheckbox = FormCheckbox;
@@ -49,6 +49,8 @@ exports.pivotTableRelTargetFromWorksheetName = pivotTableRelTargetFromWorksheetN
49
49
  exports.tableRelTargetFromWorksheet = tableRelTargetFromWorksheet;
50
50
  exports.tableRelTargetFromWorksheetName = tableRelTargetFromWorksheetName;
51
51
  exports.mediaRelTargetFromRels = mediaRelTargetFromRels;
52
+ exports.ctrlPropPath = ctrlPropPath;
53
+ exports.ctrlPropRelTargetFromWorksheet = ctrlPropRelTargetFromWorksheet;
52
54
  exports.OOXML_PATHS = {
53
55
  contentTypes: "[Content_Types].xml",
54
56
  rootRels: "_rels/.rels",
@@ -260,3 +262,11 @@ function mediaRelTargetFromRels(filename) {
260
262
  // Target from a rels file located under xl/*/_rels (base is one level deeper than xl/)
261
263
  return `../media/${filename}`;
262
264
  }
265
+ // Form Control (ctrlProps) path functions
266
+ function ctrlPropPath(id) {
267
+ return `xl/ctrlProps/ctrlProp${id}.xml`;
268
+ }
269
+ function ctrlPropRelTargetFromWorksheet(id) {
270
+ // Target inside xl/worksheets/_rels/sheetN.xml.rels (base: xl/worksheets/)
271
+ return `../ctrlProps/ctrlProp${id}.xml`;
272
+ }
@@ -9,6 +9,7 @@ const enums_1 = require("./enums.js");
9
9
  const image_1 = require("./image.js");
10
10
  const table_1 = require("./table.js");
11
11
  const data_validations_1 = require("./data-validations.js");
12
+ const form_control_1 = require("./form-control.js");
12
13
  const encryptor_1 = require("./utils/encryptor.js");
13
14
  const utils_1 = require("../../utils/utils.js");
14
15
  const pivot_table_1 = require("./pivot-table.js");
@@ -95,6 +96,8 @@ class Worksheet {
95
96
  this.tables = {};
96
97
  this.pivotTables = [];
97
98
  this.conditionalFormattings = [];
99
+ // for form controls (legacy checkboxes, etc.)
100
+ this.formControls = [];
98
101
  }
99
102
  get name() {
100
103
  return this._name;
@@ -748,6 +751,43 @@ class Worksheet {
748
751
  return image && image.imageId;
749
752
  }
750
753
  // =========================================================================
754
+ // Form Controls (Legacy Checkboxes)
755
+ /**
756
+ * Add a form control checkbox to the worksheet.
757
+ *
758
+ * Form control checkboxes are the legacy style that work in Office 2007+,
759
+ * WPS Office, LibreOffice, and other spreadsheet applications.
760
+ *
761
+ * Unlike modern in-cell checkboxes (which only work in Microsoft 365),
762
+ * form control checkboxes are floating controls positioned over cells.
763
+ *
764
+ * @param range - Cell reference (e.g., "B2") or range (e.g., "B2:D3") for positioning
765
+ * @param options - Checkbox options
766
+ * @returns The created FormCheckbox instance
767
+ *
768
+ * @example
769
+ * // Simple checkbox at B2
770
+ * ws.addFormCheckbox("B2");
771
+ *
772
+ * // Checkbox with label and linked cell
773
+ * ws.addFormCheckbox("B2:D3", {
774
+ * text: "Accept terms",
775
+ * link: "A2",
776
+ * checked: false
777
+ * });
778
+ */
779
+ addFormCheckbox(range, options) {
780
+ const checkbox = new form_control_1.FormCheckbox(this, range, options);
781
+ this.formControls.push(checkbox);
782
+ return checkbox;
783
+ }
784
+ /**
785
+ * Get all form control checkboxes in the worksheet
786
+ */
787
+ getFormCheckboxes() {
788
+ return this.formControls;
789
+ }
790
+ // =========================================================================
751
791
  // Worksheet Protection
752
792
  /**
753
793
  * Protect the worksheet with optional password and options
@@ -856,7 +896,8 @@ class Worksheet {
856
896
  sheetProtection: this.sheetProtection,
857
897
  tables: Object.values(this.tables).map(table => table.model),
858
898
  pivotTables: this.pivotTables,
859
- conditionalFormattings: this.conditionalFormattings
899
+ conditionalFormattings: this.conditionalFormattings,
900
+ formControls: this.formControls.map(fc => fc.model)
860
901
  };
861
902
  // =================================================
862
903
  // columns
@@ -922,6 +963,8 @@ class Worksheet {
922
963
  }, {});
923
964
  this.pivotTables = value.pivotTables;
924
965
  this.conditionalFormattings = value.conditionalFormattings;
966
+ // Form controls are currently write-only (not parsed from XLSX)
967
+ this.formControls = [];
925
968
  }
926
969
  }
927
970
  exports.Worksheet = Worksheet;
@@ -18,6 +18,7 @@ const RelType = {
18
18
  PivotCacheDefinition: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition",
19
19
  PivotCacheRecords: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords",
20
20
  PivotTable: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable",
21
- FeaturePropertyBag: "http://schemas.microsoft.com/office/2022/11/relationships/FeaturePropertyBag"
21
+ FeaturePropertyBag: "http://schemas.microsoft.com/office/2022/11/relationships/FeaturePropertyBag",
22
+ CtrlProp: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp"
22
23
  };
23
24
  exports.RelType = RelType;
@@ -108,6 +108,22 @@ class ContentTypesXform extends base_xform_1.BaseXform {
108
108
  });
109
109
  });
110
110
  }
111
+ // Add form control (ctrlProps) content types
112
+ if (model.formControlRefs) {
113
+ // Ensure vml extension is declared (may already be declared for comments)
114
+ if (!model.commentRefs) {
115
+ xmlStream.leafNode("Default", {
116
+ Extension: "vml",
117
+ ContentType: "application/vnd.openxmlformats-officedocument.vmlDrawing"
118
+ });
119
+ }
120
+ for (const ctrlPropId of model.formControlRefs) {
121
+ xmlStream.leafNode("Override", {
122
+ PartName: (0, ooxml_paths_1.toContentTypesPartName)((0, ooxml_paths_1.ctrlPropPath)(ctrlPropId)),
123
+ ContentType: "application/vnd.ms-excel.controlproperties+xml"
124
+ });
125
+ }
126
+ }
111
127
  xmlStream.leafNode("Override", {
112
128
  PartName: (0, ooxml_paths_1.toContentTypesPartName)(ooxml_paths_1.OOXML_PATHS.docPropsCore),
113
129
  ContentType: "application/vnd.openxmlformats-package.core-properties+xml"
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CtrlPropXform = void 0;
4
+ const xml_stream_1 = require("../../../utils/xml-stream.js");
5
+ const base_xform_1 = require("../base-xform.js");
6
+ /**
7
+ * Control Properties Xform - Generates ctrlProp*.xml for form controls
8
+ *
9
+ * Each form control (checkbox, button, etc.) has an associated ctrlProp file
10
+ * that stores its properties like objectType, checked state, and linked cell.
11
+ */
12
+ class CtrlPropXform extends base_xform_1.BaseXform {
13
+ get tag() {
14
+ return "formControlPr";
15
+ }
16
+ render(xmlStream, model) {
17
+ const renderModel = model || this.model;
18
+ const attrs = {
19
+ xmlns: "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main",
20
+ objectType: "CheckBox",
21
+ checked: renderModel.checked,
22
+ lockText: "1"
23
+ };
24
+ // Add linked cell reference
25
+ if (renderModel.link) {
26
+ attrs.fmlaLink = renderModel.link;
27
+ }
28
+ // Add noThreeD for flat appearance
29
+ if (renderModel.noThreeD) {
30
+ attrs.noThreeD = "1";
31
+ }
32
+ xmlStream.openXml({ version: "1.0", encoding: "UTF-8", standalone: "yes" });
33
+ xmlStream.leafNode(this.tag, attrs);
34
+ }
35
+ /**
36
+ * Generate XML string directly (convenience method)
37
+ * Uses render() internally to ensure consistency
38
+ */
39
+ toXml(model) {
40
+ const xmlStream = new xml_stream_1.XmlStream();
41
+ this.render(xmlStream, model);
42
+ return xmlStream.xml;
43
+ }
44
+ // Parsing not implemented - form controls are write-only for now
45
+ parseOpen() {
46
+ return true;
47
+ }
48
+ parseText() {
49
+ // Not implemented
50
+ }
51
+ parseClose() {
52
+ return false;
53
+ }
54
+ }
55
+ exports.CtrlPropXform = CtrlPropXform;
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VmlDrawingXform = void 0;
4
+ const xml_stream_1 = require("../../../utils/xml-stream.js");
5
+ const base_xform_1 = require("../base-xform.js");
6
+ const vml_shape_xform_1 = require("../comment/vml-shape-xform.js");
7
+ const form_control_1 = require("../../../form-control.js");
8
+ class VmlDrawingXform extends base_xform_1.BaseXform {
9
+ constructor() {
10
+ super();
11
+ this.map = {
12
+ "v:shape": new vml_shape_xform_1.VmlShapeXform()
13
+ };
14
+ this.model = { comments: [], formControls: [] };
15
+ }
16
+ get tag() {
17
+ return "xml";
18
+ }
19
+ /**
20
+ * Render VML drawing containing both notes and form controls
21
+ */
22
+ render(xmlStream, model) {
23
+ const renderModel = model || this.model;
24
+ const hasComments = renderModel.comments && renderModel.comments.length > 0;
25
+ const hasFormControls = renderModel.formControls && renderModel.formControls.length > 0;
26
+ xmlStream.openXml(xml_stream_1.XmlStream.StdDocAttributes);
27
+ xmlStream.openNode(this.tag, VmlDrawingXform.DRAWING_ATTRIBUTES);
28
+ // Shape layout - shared by both notes and form controls
29
+ xmlStream.openNode("o:shapelayout", { "v:ext": "edit" });
30
+ xmlStream.leafNode("o:idmap", { "v:ext": "edit", data: 1 });
31
+ xmlStream.closeNode();
32
+ // Shapetype 202 for notes/comments
33
+ if (hasComments) {
34
+ xmlStream.openNode("v:shapetype", {
35
+ id: "_x0000_t202",
36
+ coordsize: "21600,21600",
37
+ "o:spt": 202,
38
+ path: "m,l,21600r21600,l21600,xe"
39
+ });
40
+ xmlStream.leafNode("v:stroke", { joinstyle: "miter" });
41
+ xmlStream.leafNode("v:path", { gradientshapeok: "t", "o:connecttype": "rect" });
42
+ xmlStream.closeNode();
43
+ }
44
+ // Shapetype 201 for form control checkboxes
45
+ if (hasFormControls) {
46
+ xmlStream.openNode("v:shapetype", {
47
+ id: "_x0000_t201",
48
+ coordsize: "21600,21600",
49
+ "o:spt": "201",
50
+ path: "m,l,21600r21600,l21600,xe"
51
+ });
52
+ xmlStream.leafNode("v:stroke", { joinstyle: "miter" });
53
+ xmlStream.leafNode("v:path", {
54
+ shadowok: "f",
55
+ "o:extrusionok": "f",
56
+ strokeok: "f",
57
+ fillok: "f",
58
+ "o:connecttype": "rect"
59
+ });
60
+ xmlStream.leafNode("o:lock", { "v:ext": "edit", shapetype: "t" });
61
+ xmlStream.closeNode();
62
+ }
63
+ // Render comment shapes
64
+ if (hasComments) {
65
+ const comments = renderModel.comments;
66
+ for (let i = 0; i < comments.length; i++) {
67
+ this.map["v:shape"].render(xmlStream, comments[i], i);
68
+ }
69
+ }
70
+ // Render form control shapes
71
+ if (hasFormControls) {
72
+ for (const control of renderModel.formControls) {
73
+ this._renderCheckboxShape(xmlStream, control);
74
+ }
75
+ }
76
+ xmlStream.closeNode();
77
+ }
78
+ /**
79
+ * Render a checkbox form control shape
80
+ */
81
+ _renderCheckboxShape(xmlStream, control) {
82
+ const shapeAttrs = {
83
+ id: `_x0000_s${control.shapeId}`,
84
+ type: "#_x0000_t201",
85
+ style: form_control_1.FormCheckbox.getVmlStyle(control),
86
+ "o:insetmode": "auto",
87
+ fillcolor: "buttonFace [67]",
88
+ strokecolor: "windowText [64]",
89
+ "o:preferrelative": "t",
90
+ filled: "f",
91
+ stroked: "f"
92
+ };
93
+ xmlStream.openNode("v:shape", shapeAttrs);
94
+ // Fill element
95
+ xmlStream.leafNode("v:fill", { "o:detectmouseclick": "t" });
96
+ // Lock element
97
+ xmlStream.leafNode("o:lock", { "v:ext": "edit", text: "t" });
98
+ // Textbox for label
99
+ if (control.text) {
100
+ xmlStream.openNode("v:textbox", {
101
+ style: "mso-direction-alt:auto",
102
+ "o:singleclick": "t"
103
+ });
104
+ xmlStream.openNode("div", { style: "text-align:left" });
105
+ xmlStream.openNode("font", { face: "Tahoma", size: "160", color: "auto" });
106
+ xmlStream.writeText(control.text);
107
+ xmlStream.closeNode(); // font
108
+ xmlStream.closeNode(); // div
109
+ xmlStream.closeNode(); // v:textbox
110
+ }
111
+ // ClientData - the core of the checkbox control
112
+ xmlStream.openNode("x:ClientData", { ObjectType: "Checkbox" });
113
+ // Anchor position
114
+ xmlStream.openNode("x:Anchor");
115
+ xmlStream.writeText(form_control_1.FormCheckbox.getVmlAnchor(control));
116
+ xmlStream.closeNode();
117
+ // Print settings
118
+ xmlStream.leafNode("x:PrintObject", undefined, control.print ? "True" : "False");
119
+ xmlStream.leafNode("x:AutoFill", undefined, "False");
120
+ xmlStream.leafNode("x:AutoLine", undefined, "False");
121
+ xmlStream.leafNode("x:TextVAlign", undefined, "Center");
122
+ // Linked cell
123
+ if (control.link) {
124
+ xmlStream.leafNode("x:FmlaLink", undefined, control.link);
125
+ }
126
+ // 3D appearance
127
+ if (control.noThreeD) {
128
+ xmlStream.leafNode("x:NoThreeD");
129
+ }
130
+ // Checked state (0 = unchecked, 1 = checked, 2 = mixed)
131
+ xmlStream.leafNode("x:Checked", undefined, String(form_control_1.FormCheckbox.getVmlCheckedValue(control)));
132
+ xmlStream.closeNode(); // x:ClientData
133
+ xmlStream.closeNode(); // v:shape
134
+ }
135
+ // Parsing - delegate to VmlShapeXform for notes
136
+ parseOpen(node) {
137
+ if (this.parser) {
138
+ this.parser.parseOpen(node);
139
+ return true;
140
+ }
141
+ switch (node.name) {
142
+ case this.tag:
143
+ this.reset();
144
+ this.model = {
145
+ comments: [],
146
+ formControls: []
147
+ };
148
+ break;
149
+ default:
150
+ this.parser = this.map[node.name];
151
+ if (this.parser) {
152
+ this.parser.parseOpen(node);
153
+ }
154
+ break;
155
+ }
156
+ return true;
157
+ }
158
+ parseText(text) {
159
+ if (this.parser) {
160
+ this.parser.parseText(text);
161
+ }
162
+ }
163
+ parseClose(name) {
164
+ if (this.parser) {
165
+ if (!this.parser.parseClose(name)) {
166
+ this.model.comments.push(this.parser.model);
167
+ this.parser = undefined;
168
+ }
169
+ return true;
170
+ }
171
+ switch (name) {
172
+ case this.tag:
173
+ return false;
174
+ default:
175
+ return true;
176
+ }
177
+ }
178
+ }
179
+ exports.VmlDrawingXform = VmlDrawingXform;
180
+ VmlDrawingXform.DRAWING_ATTRIBUTES = {
181
+ "xmlns:v": "urn:schemas-microsoft-com:vml",
182
+ "xmlns:o": "urn:schemas-microsoft-com:office:office",
183
+ "xmlns:x": "urn:schemas-microsoft-com:office:excel"
184
+ };
@@ -278,6 +278,29 @@ class WorkSheetXform extends base_xform_1.BaseXform {
278
278
  Target: (0, ooxml_paths_1.pivotTableRelTargetFromWorksheet)(pivotTable.tableNumber)
279
279
  });
280
280
  });
281
+ // prepare form controls (legacy checkboxes)
282
+ // Form controls share the VML file with comments, but need separate ctrlProp relationships
283
+ if (model.formControls && model.formControls.length > 0) {
284
+ // If no comments, we need to add the VML drawing relationship for form controls
285
+ if (model.comments.length === 0) {
286
+ rels.push({
287
+ Id: nextRid(rels),
288
+ Type: rel_type_1.RelType.VmlDrawing,
289
+ Target: (0, ooxml_paths_1.vmlDrawingRelTargetFromWorksheet)(model.id)
290
+ });
291
+ }
292
+ // Add ctrlProp relationships for each form control
293
+ for (const control of model.formControls) {
294
+ const globalCtrlPropId = options.formControlRefs.length + 1;
295
+ control.ctrlPropId = globalCtrlPropId;
296
+ rels.push({
297
+ Id: nextRid(rels),
298
+ Type: rel_type_1.RelType.CtrlProp,
299
+ Target: (0, ooxml_paths_1.ctrlPropRelTargetFromWorksheet)(globalCtrlPropId)
300
+ });
301
+ options.formControlRefs.push(globalCtrlPropId);
302
+ }
303
+ }
281
304
  // prepare ext items
282
305
  this.map.extLst.prepare(model, options);
283
306
  }