@cj-tech-master/excelts 9.6.1 → 10.0.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.
- package/README.md +18 -3
- package/README_zh.md +18 -3
- package/dist/browser/modules/excel/cell.d.ts +4 -0
- package/dist/browser/modules/excel/note.js +5 -1
- package/dist/browser/modules/excel/row.js +35 -2
- package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +8 -1
- package/dist/browser/modules/excel/stream/workbook-writer.browser.js +22 -2
- package/dist/browser/modules/excel/types.d.ts +81 -0
- package/dist/browser/modules/excel/utils/drawing-utils.d.ts +8 -0
- package/dist/browser/modules/excel/utils/drawing-utils.js +19 -2
- package/dist/browser/modules/excel/workbook.browser.d.ts +16 -0
- package/dist/browser/modules/excel/workbook.browser.js +32 -2
- package/dist/browser/modules/excel/worksheet.d.ts +31 -1
- package/dist/browser/modules/excel/worksheet.js +83 -0
- package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.d.ts +7 -0
- package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
- package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +6 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
- package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +1 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +2 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/shape-xform.d.ts +47 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/shape-xform.js +109 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
- package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
- package/dist/browser/modules/pdf/builder/document-builder.js +22 -49
- package/dist/browser/modules/pdf/builder/pdf-editor.js +1 -1
- package/dist/browser/modules/pdf/core/pdf-stream.d.ts +28 -1
- package/dist/browser/modules/pdf/core/pdf-stream.js +38 -2
- package/dist/browser/modules/pdf/font/font-manager.d.ts +26 -0
- package/dist/browser/modules/pdf/font/font-manager.js +35 -18
- package/dist/browser/modules/pdf/render/page-renderer.d.ts +51 -3
- package/dist/browser/modules/pdf/render/page-renderer.js +111 -18
- package/dist/browser/modules/word/advanced/field-engine.js +45 -20
- package/dist/browser/modules/word/advanced/glossary.d.ts +10 -36
- package/dist/browser/modules/word/advanced/glossary.js +8 -9
- package/dist/browser/modules/word/advanced/math-convert.js +94 -12
- package/dist/browser/modules/word/advanced/ole-objects.d.ts +28 -0
- package/dist/browser/modules/word/advanced/ole-objects.js +122 -19
- package/dist/browser/modules/word/advanced/style-map.js +31 -10
- package/dist/browser/modules/word/builder/run-builders.d.ts +7 -1
- package/dist/browser/modules/word/builder/run-builders.js +7 -1
- package/dist/browser/modules/word/constants.d.ts +4 -0
- package/dist/browser/modules/word/constants.js +5 -1
- package/dist/browser/modules/word/convert/docx-to-semantic.d.ts +2 -1
- package/dist/browser/modules/word/convert/docx-to-semantic.js +135 -1
- package/dist/browser/modules/word/convert/html/html-import.d.ts +32 -1
- package/dist/browser/modules/word/convert/html/html-import.js +167 -14
- package/dist/browser/modules/word/convert/html/html.d.ts +2 -2
- package/dist/browser/modules/word/convert/html/html.js +1 -1
- package/dist/browser/modules/word/convert/markdown/markdown-import.d.ts +48 -18
- package/dist/browser/modules/word/convert/markdown/markdown-import.js +279 -69
- package/dist/browser/modules/word/convert/markdown/markdown.d.ts +1 -1
- package/dist/browser/modules/word/convert/odt/odt.js +407 -56
- package/dist/browser/modules/word/html.d.ts +2 -2
- package/dist/browser/modules/word/html.js +1 -1
- package/dist/browser/modules/word/index.base.d.ts +3 -3
- package/dist/browser/modules/word/index.base.js +1 -1
- package/dist/browser/modules/word/layout/layout-full.js +326 -19
- package/dist/browser/modules/word/layout/render-page.js +35 -8
- package/dist/browser/modules/word/markdown.d.ts +1 -1
- package/dist/browser/modules/word/query/compat.d.ts +10 -2
- package/dist/browser/modules/word/query/compat.js +29 -21
- package/dist/browser/modules/word/reader/docx-reader.js +105 -2
- package/dist/browser/modules/word/reader/math-parser.js +8 -2
- package/dist/browser/modules/word/security/cfb-reader.js +5 -5
- package/dist/browser/modules/word/types.d.ts +96 -1
- package/dist/browser/modules/word/writer/docx-packager.js +108 -2
- package/dist/browser/modules/word/writer/glossary-writer.d.ts +28 -0
- package/dist/browser/modules/word/writer/glossary-writer.js +121 -0
- package/dist/browser/modules/word/writer/header-footer-writer.js +105 -20
- package/dist/browser/modules/word/writer/math-writer.js +7 -2
- package/dist/browser/utils/font-metrics.d.ts +8 -0
- package/dist/browser/utils/font-metrics.js +43 -0
- package/dist/browser/utils/theme-colors.js +4 -1
- package/dist/cjs/modules/excel/note.js +5 -1
- package/dist/cjs/modules/excel/row.js +35 -2
- package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +22 -2
- package/dist/cjs/modules/excel/utils/drawing-utils.js +19 -2
- package/dist/cjs/modules/excel/workbook.browser.js +31 -1
- package/dist/cjs/modules/excel/worksheet.js +83 -0
- package/dist/cjs/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
- package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
- package/dist/cjs/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/shape-xform.js +112 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
- package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
- package/dist/cjs/modules/pdf/builder/document-builder.js +21 -48
- package/dist/cjs/modules/pdf/builder/pdf-editor.js +1 -1
- package/dist/cjs/modules/pdf/core/pdf-stream.js +38 -2
- package/dist/cjs/modules/pdf/font/font-manager.js +35 -18
- package/dist/cjs/modules/pdf/render/page-renderer.js +112 -18
- package/dist/cjs/modules/word/advanced/field-engine.js +45 -20
- package/dist/cjs/modules/word/advanced/glossary.js +8 -9
- package/dist/cjs/modules/word/advanced/math-convert.js +94 -12
- package/dist/cjs/modules/word/advanced/ole-objects.js +123 -19
- package/dist/cjs/modules/word/advanced/style-map.js +31 -10
- package/dist/cjs/modules/word/builder/run-builders.js +7 -1
- package/dist/cjs/modules/word/constants.js +5 -1
- package/dist/cjs/modules/word/convert/docx-to-semantic.js +135 -1
- package/dist/cjs/modules/word/convert/html/html-import.js +168 -14
- package/dist/cjs/modules/word/convert/html/html.js +2 -1
- package/dist/cjs/modules/word/convert/markdown/markdown-import.js +279 -69
- package/dist/cjs/modules/word/convert/odt/odt.js +407 -56
- package/dist/cjs/modules/word/html.js +2 -1
- package/dist/cjs/modules/word/index.base.js +4 -3
- package/dist/cjs/modules/word/layout/layout-full.js +325 -18
- package/dist/cjs/modules/word/layout/render-page.js +35 -8
- package/dist/cjs/modules/word/query/compat.js +29 -21
- package/dist/cjs/modules/word/reader/docx-reader.js +104 -1
- package/dist/cjs/modules/word/reader/math-parser.js +8 -2
- package/dist/cjs/modules/word/security/cfb-reader.js +5 -5
- package/dist/cjs/modules/word/writer/docx-packager.js +108 -2
- package/dist/cjs/modules/word/writer/glossary-writer.js +124 -0
- package/dist/cjs/modules/word/writer/header-footer-writer.js +105 -20
- package/dist/cjs/modules/word/writer/math-writer.js +7 -2
- package/dist/cjs/utils/font-metrics.js +44 -0
- package/dist/cjs/utils/theme-colors.js +4 -1
- package/dist/esm/modules/excel/note.js +5 -1
- package/dist/esm/modules/excel/row.js +35 -2
- package/dist/esm/modules/excel/stream/workbook-writer.browser.js +22 -2
- package/dist/esm/modules/excel/utils/drawing-utils.js +19 -2
- package/dist/esm/modules/excel/workbook.browser.js +32 -2
- package/dist/esm/modules/excel/worksheet.js +83 -0
- package/dist/esm/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
- package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
- package/dist/esm/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/shape-xform.js +109 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
- package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
- package/dist/esm/modules/pdf/builder/document-builder.js +22 -49
- package/dist/esm/modules/pdf/builder/pdf-editor.js +1 -1
- package/dist/esm/modules/pdf/core/pdf-stream.js +38 -2
- package/dist/esm/modules/pdf/font/font-manager.js +35 -18
- package/dist/esm/modules/pdf/render/page-renderer.js +111 -18
- package/dist/esm/modules/word/advanced/field-engine.js +45 -20
- package/dist/esm/modules/word/advanced/glossary.js +8 -9
- package/dist/esm/modules/word/advanced/math-convert.js +94 -12
- package/dist/esm/modules/word/advanced/ole-objects.js +122 -19
- package/dist/esm/modules/word/advanced/style-map.js +31 -10
- package/dist/esm/modules/word/builder/run-builders.js +7 -1
- package/dist/esm/modules/word/constants.js +5 -1
- package/dist/esm/modules/word/convert/docx-to-semantic.js +135 -1
- package/dist/esm/modules/word/convert/html/html-import.js +167 -14
- package/dist/esm/modules/word/convert/html/html.js +1 -1
- package/dist/esm/modules/word/convert/markdown/markdown-import.js +279 -69
- package/dist/esm/modules/word/convert/odt/odt.js +407 -56
- package/dist/esm/modules/word/html.js +1 -1
- package/dist/esm/modules/word/index.base.js +1 -1
- package/dist/esm/modules/word/layout/layout-full.js +326 -19
- package/dist/esm/modules/word/layout/render-page.js +35 -8
- package/dist/esm/modules/word/query/compat.js +29 -21
- package/dist/esm/modules/word/reader/docx-reader.js +105 -2
- package/dist/esm/modules/word/reader/math-parser.js +8 -2
- package/dist/esm/modules/word/security/cfb-reader.js +5 -5
- package/dist/esm/modules/word/writer/docx-packager.js +108 -2
- package/dist/esm/modules/word/writer/glossary-writer.js +121 -0
- package/dist/esm/modules/word/writer/header-footer-writer.js +105 -20
- package/dist/esm/modules/word/writer/math-writer.js +7 -2
- package/dist/esm/utils/font-metrics.js +43 -0
- package/dist/esm/utils/theme-colors.js +4 -1
- package/dist/iife/excelts.iife.js +496 -59
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +39 -39
- package/dist/types/modules/excel/cell.d.ts +4 -0
- package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +8 -1
- package/dist/types/modules/excel/types.d.ts +81 -0
- package/dist/types/modules/excel/utils/drawing-utils.d.ts +8 -0
- package/dist/types/modules/excel/workbook.browser.d.ts +16 -0
- package/dist/types/modules/excel/worksheet.d.ts +31 -1
- package/dist/types/modules/excel/xlsx/xform/comment/vml-shape-xform.d.ts +7 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +6 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +1 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +2 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/shape-xform.d.ts +47 -0
- package/dist/types/modules/pdf/core/pdf-stream.d.ts +28 -1
- package/dist/types/modules/pdf/font/font-manager.d.ts +26 -0
- package/dist/types/modules/pdf/render/page-renderer.d.ts +51 -3
- package/dist/types/modules/word/advanced/glossary.d.ts +10 -36
- package/dist/types/modules/word/advanced/ole-objects.d.ts +28 -0
- package/dist/types/modules/word/builder/run-builders.d.ts +7 -1
- package/dist/types/modules/word/constants.d.ts +4 -0
- package/dist/types/modules/word/convert/docx-to-semantic.d.ts +2 -1
- package/dist/types/modules/word/convert/html/html-import.d.ts +32 -1
- package/dist/types/modules/word/convert/html/html.d.ts +2 -2
- package/dist/types/modules/word/convert/markdown/markdown-import.d.ts +48 -18
- package/dist/types/modules/word/convert/markdown/markdown.d.ts +1 -1
- package/dist/types/modules/word/html.d.ts +2 -2
- package/dist/types/modules/word/index.base.d.ts +3 -3
- package/dist/types/modules/word/markdown.d.ts +1 -1
- package/dist/types/modules/word/query/compat.d.ts +10 -2
- package/dist/types/modules/word/types.d.ts +96 -1
- package/dist/types/modules/word/writer/glossary-writer.d.ts +28 -0
- package/dist/types/utils/font-metrics.d.ts +8 -0
- package/package.json +3 -1
|
@@ -66,12 +66,17 @@ export function renderWatermarkHeader(xml, watermark, imageRId) {
|
|
|
66
66
|
"xmlns:o": NS_O,
|
|
67
67
|
"xmlns:w10": NS_W10
|
|
68
68
|
});
|
|
69
|
-
// Watermark
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
// for an auto-generated watermark header.
|
|
69
|
+
// Watermark lives in a paragraph styled as a Header, matching what
|
|
70
|
+
// Microsoft Word emits. The run carries <w:noProof/> so the WordArt
|
|
71
|
+
// text is not spell-checked.
|
|
73
72
|
xml.openNode("w:p");
|
|
73
|
+
xml.openNode("w:pPr");
|
|
74
|
+
xml.leafNode("w:pStyle", { "w:val": "Header" });
|
|
75
|
+
xml.closeNode(); // pPr
|
|
74
76
|
xml.openNode("w:r");
|
|
77
|
+
xml.openNode("w:rPr");
|
|
78
|
+
xml.leafNode("w:noProof");
|
|
79
|
+
xml.closeNode(); // rPr
|
|
75
80
|
xml.openNode("w:pict");
|
|
76
81
|
if (watermark.type === "text") {
|
|
77
82
|
renderTextWatermarkVml(xml, watermark);
|
|
@@ -87,49 +92,130 @@ export function renderWatermarkHeader(xml, watermark, imageRId) {
|
|
|
87
92
|
function renderTextWatermarkVml(xml, wm) {
|
|
88
93
|
const color = wm.color ?? "C0C0C0";
|
|
89
94
|
const font = wm.font ?? "Calibri";
|
|
90
|
-
const fontSize = wm.fontSize ?? 1; // half-points; Word uses pt string in style
|
|
91
|
-
const fontPt = fontSize / 2;
|
|
92
95
|
const rotation = wm.rotation ?? -45;
|
|
93
|
-
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
+
// Full WordArt shapetype definition (t136) exactly as Microsoft Word
|
|
97
|
+
// emits it. Word for Mac will NOT render the text path unless the
|
|
98
|
+
// shapetype carries the formula/path/textpath/handles/lock children —
|
|
99
|
+
// an empty <v:shapetype/> produces an invisible watermark.
|
|
100
|
+
xml.openNode("v:shapetype", {
|
|
96
101
|
id: "_x0000_t136",
|
|
97
102
|
coordsize: "21600,21600",
|
|
98
103
|
"o:spt": "136",
|
|
104
|
+
adj: "10800",
|
|
99
105
|
path: "m@7,l@8,m@5,21600l@6,21600e"
|
|
100
106
|
});
|
|
107
|
+
xml.openNode("v:formulas");
|
|
108
|
+
for (const eqn of [
|
|
109
|
+
"sum #0 0 10800",
|
|
110
|
+
"prod #0 2 1",
|
|
111
|
+
"sum 21600 0 @1",
|
|
112
|
+
"sum 0 0 @2",
|
|
113
|
+
"sum 21600 0 @3",
|
|
114
|
+
"if @0 @3 0",
|
|
115
|
+
"if @0 21600 @1",
|
|
116
|
+
"if @0 0 @2",
|
|
117
|
+
"if @0 @4 21600",
|
|
118
|
+
"mid @5 @6",
|
|
119
|
+
"mid @8 @5",
|
|
120
|
+
"mid @7 @8",
|
|
121
|
+
"mid @6 @7",
|
|
122
|
+
"sum @6 0 @5"
|
|
123
|
+
]) {
|
|
124
|
+
xml.leafNode("v:f", { eqn });
|
|
125
|
+
}
|
|
126
|
+
xml.closeNode(); // formulas
|
|
127
|
+
xml.leafNode("v:path", {
|
|
128
|
+
textpathok: "t",
|
|
129
|
+
"o:connecttype": "custom",
|
|
130
|
+
"o:connectlocs": "@9,0;@10,10800;@11,21600;@12,10800",
|
|
131
|
+
"o:connectangles": "270,180,90,0"
|
|
132
|
+
});
|
|
133
|
+
xml.leafNode("v:textpath", { on: "t", fitshape: "t" });
|
|
134
|
+
xml.openNode("v:handles");
|
|
135
|
+
xml.leafNode("v:h", { position: "#0,bottomRight", xrange: "6629,14971" });
|
|
136
|
+
xml.closeNode(); // handles
|
|
137
|
+
xml.leafNode("o:lock", { "v:ext": "edit", text: "t", shapetype: "t" });
|
|
138
|
+
xml.closeNode(); // shapetype
|
|
139
|
+
// The watermark shape itself. Word fixes font-size at 1pt and relies on
|
|
140
|
+
// fitshape="t" to scale the text to fill the shape box; rotation is
|
|
141
|
+
// applied on the shape via the style string.
|
|
101
142
|
const style = `position:absolute;margin-left:0;margin-top:0;width:468pt;height:234pt;` +
|
|
102
|
-
`rotation:${rotation}
|
|
103
|
-
`mso-position-horizontal
|
|
104
|
-
`mso-position-vertical-relative:margin`;
|
|
143
|
+
`${rotation ? `rotation:${rotation};` : ""}z-index:-251658752;` +
|
|
144
|
+
`mso-position-horizontal:center;mso-position-horizontal-relative:margin;` +
|
|
145
|
+
`mso-position-vertical:center;mso-position-vertical-relative:margin`;
|
|
105
146
|
xml.openNode("v:shape", {
|
|
106
147
|
id: "PowerPlusWaterMarkObject",
|
|
107
148
|
"o:spid": "_x0000_s2049",
|
|
108
149
|
type: "#_x0000_t136",
|
|
150
|
+
alt: "",
|
|
109
151
|
style,
|
|
110
152
|
"o:allowincell": "f",
|
|
111
153
|
fillcolor: `#${color}`,
|
|
112
154
|
stroked: "f"
|
|
113
155
|
});
|
|
114
|
-
|
|
156
|
+
if (wm.semiTransparent !== false) {
|
|
157
|
+
xml.leafNode("v:fill", { opacity: ".5" });
|
|
158
|
+
}
|
|
115
159
|
xml.leafNode("v:textpath", {
|
|
116
|
-
style: `font-family
|
|
160
|
+
style: `font-family:"${font}";font-size:1pt`,
|
|
117
161
|
string: wm.text
|
|
118
162
|
});
|
|
119
|
-
xml.leafNode("w10:wrap", { anchorx: "margin", anchory: "margin" });
|
|
120
163
|
xml.closeNode(); // v:shape
|
|
121
164
|
}
|
|
122
165
|
function renderImageWatermarkVml(xml, wm, rId) {
|
|
123
|
-
const scale = wm.scale ?? 100;
|
|
124
166
|
const rid = rId ?? wm.rId;
|
|
125
|
-
|
|
167
|
+
// Default to a large area covering most of the body so the picture is
|
|
168
|
+
// actually visible. width:0;height:0 produces an invisible dot.
|
|
169
|
+
const widthPt = wm.widthPt ?? 415.2;
|
|
170
|
+
const heightPt = wm.heightPt ?? 233.5;
|
|
171
|
+
// Picture-frame shapetype (t75) as Microsoft Word emits it. Without a
|
|
172
|
+
// proper shapetype + non-zero size the image watermark will not render.
|
|
173
|
+
xml.openNode("v:shapetype", {
|
|
174
|
+
id: "_x0000_t75",
|
|
175
|
+
coordsize: "21600,21600",
|
|
176
|
+
"o:spt": "75",
|
|
177
|
+
"o:preferrelative": "t",
|
|
178
|
+
path: "m@4@5l@4@11@9@11@9@5xe",
|
|
179
|
+
filled: "f",
|
|
180
|
+
stroked: "f"
|
|
181
|
+
});
|
|
182
|
+
xml.openNode("v:stroke", { joinstyle: "miter" });
|
|
183
|
+
xml.closeNode();
|
|
184
|
+
xml.openNode("v:formulas");
|
|
185
|
+
for (const eqn of [
|
|
186
|
+
"if lineDrawn pixelLineWidth 0",
|
|
187
|
+
"sum @0 1 0",
|
|
188
|
+
"sum 0 0 @1",
|
|
189
|
+
"prod @2 1 2",
|
|
190
|
+
"prod @3 21600 pixelWidth",
|
|
191
|
+
"prod @3 21600 pixelHeight",
|
|
192
|
+
"sum @0 0 1",
|
|
193
|
+
"prod @6 1 2",
|
|
194
|
+
"prod @7 21600 pixelWidth",
|
|
195
|
+
"sum @8 21600 0",
|
|
196
|
+
"prod @7 21600 pixelHeight",
|
|
197
|
+
"sum @10 21600 0"
|
|
198
|
+
]) {
|
|
199
|
+
xml.leafNode("v:f", { eqn });
|
|
200
|
+
}
|
|
201
|
+
xml.closeNode(); // formulas
|
|
202
|
+
xml.leafNode("v:path", {
|
|
203
|
+
"o:extrusionok": "f",
|
|
204
|
+
gradientshapeok: "t",
|
|
205
|
+
"o:connecttype": "rect"
|
|
206
|
+
});
|
|
207
|
+
xml.leafNode("o:lock", { "v:ext": "edit", aspectratio: "t" });
|
|
208
|
+
xml.closeNode(); // shapetype
|
|
209
|
+
const style = `position:absolute;margin-left:0;margin-top:0;` +
|
|
210
|
+
`width:${widthPt}pt;height:${heightPt}pt;` +
|
|
126
211
|
`z-index:-251658752;mso-position-horizontal:center;` +
|
|
127
212
|
`mso-position-horizontal-relative:margin;mso-position-vertical:center;` +
|
|
128
213
|
`mso-position-vertical-relative:margin`;
|
|
129
214
|
xml.openNode("v:shape", {
|
|
130
215
|
id: "PowerPlusWaterMarkObject",
|
|
131
216
|
"o:spid": "_x0000_s2050",
|
|
132
|
-
type: "",
|
|
217
|
+
type: "#_x0000_t75",
|
|
218
|
+
alt: "",
|
|
133
219
|
style,
|
|
134
220
|
"o:allowincell": "f"
|
|
135
221
|
});
|
|
@@ -139,8 +225,7 @@ function renderImageWatermarkVml(xml, wm, rId) {
|
|
|
139
225
|
"r:id": rid,
|
|
140
226
|
"o:title": "",
|
|
141
227
|
gain,
|
|
142
|
-
blacklevel
|
|
143
|
-
...(scale !== 100 ? { "o:detectmouseclick": "t" } : {})
|
|
228
|
+
blacklevel
|
|
144
229
|
});
|
|
145
230
|
xml.leafNode("w10:wrap", { anchorx: "margin", anchory: "margin" });
|
|
146
231
|
xml.closeNode(); // v:shape
|
|
@@ -175,8 +175,13 @@ function renderMathPhantom(xml, p) {
|
|
|
175
175
|
p.transparent !== undefined;
|
|
176
176
|
if (hasProps) {
|
|
177
177
|
xml.openNode("m:phantPr");
|
|
178
|
-
|
|
179
|
-
|
|
178
|
+
// `m:show` defaults to ON in OOXML (the phantom base is still drawn). To
|
|
179
|
+
// make a phantom "occupy space but stay invisible" the producer must
|
|
180
|
+
// emit `<m:show m:val="0"/>` explicitly — simply omitting it leaves the
|
|
181
|
+
// content visible. So serialize `show` whenever it is defined, mapping
|
|
182
|
+
// false → "0" and true → "1".
|
|
183
|
+
if (p.show !== undefined) {
|
|
184
|
+
xml.leafNode("m:show", { "m:val": p.show ? "1" : "0" });
|
|
180
185
|
}
|
|
181
186
|
if (p.zeroWidth) {
|
|
182
187
|
xml.leafNode("m:zeroWid", { "m:val": "1" });
|
|
@@ -61,3 +61,11 @@ export declare function getStandardFontNames(): string[];
|
|
|
61
61
|
* Falls back to "Helvetica" for unknown fonts.
|
|
62
62
|
*/
|
|
63
63
|
export declare function mapToStandardFont(fontName: string): string;
|
|
64
|
+
/**
|
|
65
|
+
* Given a standard PDF base font and bold/italic flags, return the matching
|
|
66
|
+
* metric variant name (e.g. "Helvetica" + bold → "Helvetica-Bold"). This keeps
|
|
67
|
+
* width measurement consistent with the glyphs that are actually drawn, so
|
|
68
|
+
* bold/italic runs are measured with their true (wider) metrics rather than
|
|
69
|
+
* the regular ones. Falls back to the base name when a variant is unknown.
|
|
70
|
+
*/
|
|
71
|
+
export declare function styledFontVariant(baseFont: string, bold?: boolean, italic?: boolean): string;
|
|
@@ -291,3 +291,46 @@ export function mapToStandardFont(fontName) {
|
|
|
291
291
|
const lower = fontName.toLowerCase().trim();
|
|
292
292
|
return FONT_FAMILY_MAP[lower] ?? "Helvetica";
|
|
293
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* Given a standard PDF base font and bold/italic flags, return the matching
|
|
296
|
+
* metric variant name (e.g. "Helvetica" + bold → "Helvetica-Bold"). This keeps
|
|
297
|
+
* width measurement consistent with the glyphs that are actually drawn, so
|
|
298
|
+
* bold/italic runs are measured with their true (wider) metrics rather than
|
|
299
|
+
* the regular ones. Falls back to the base name when a variant is unknown.
|
|
300
|
+
*/
|
|
301
|
+
export function styledFontVariant(baseFont, bold, italic) {
|
|
302
|
+
const std = mapToStandardFont(baseFont);
|
|
303
|
+
if (!bold && !italic) {
|
|
304
|
+
return std;
|
|
305
|
+
}
|
|
306
|
+
// Determine the family from the resolved standard name.
|
|
307
|
+
const isTimes = std.startsWith("Times");
|
|
308
|
+
const isCourier = std.startsWith("Courier");
|
|
309
|
+
let candidate;
|
|
310
|
+
if (isTimes) {
|
|
311
|
+
// Times family uses -Roman / -Bold / -Italic / -BoldItalic.
|
|
312
|
+
if (bold && italic) {
|
|
313
|
+
candidate = "Times-BoldItalic";
|
|
314
|
+
}
|
|
315
|
+
else if (bold) {
|
|
316
|
+
candidate = "Times-Bold";
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
candidate = "Times-Italic";
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
// Helvetica / Courier families use -Bold / -Oblique / -BoldOblique.
|
|
324
|
+
const family = isCourier ? "Courier" : "Helvetica";
|
|
325
|
+
if (bold && italic) {
|
|
326
|
+
candidate = `${family}-BoldOblique`;
|
|
327
|
+
}
|
|
328
|
+
else if (bold) {
|
|
329
|
+
candidate = `${family}-Bold`;
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
candidate = `${family}-Oblique`;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return candidate in FONT_DESCRIPTORS ? candidate : std;
|
|
336
|
+
}
|
|
@@ -115,6 +115,9 @@ export function resolveOoxmlThemeColor(themeColorName, colors, tint, shade) {
|
|
|
115
115
|
// Helpers
|
|
116
116
|
// =============================================================================
|
|
117
117
|
function toHex2(n) {
|
|
118
|
-
|
|
118
|
+
// Emit uppercase hex to match the OOXML ST_HexColorRGB convention and the
|
|
119
|
+
// casing of theme scheme colors (which are stored uppercase), so a tinted /
|
|
120
|
+
// shaded result is consistent with an un-transformed passthrough.
|
|
121
|
+
const h = Math.max(0, Math.min(255, n)).toString(16).toUpperCase();
|
|
119
122
|
return h.length < 2 ? "0" + h : h;
|
|
120
123
|
}
|
|
@@ -39,7 +39,11 @@ class Note {
|
|
|
39
39
|
set model(value) {
|
|
40
40
|
const { note } = value;
|
|
41
41
|
const { texts } = note;
|
|
42
|
-
|
|
42
|
+
// A single, plain text run with no extra box geometry can be flattened
|
|
43
|
+
// back to a simple string. Custom width/height must keep the full config
|
|
44
|
+
// so the sizing survives the model round-trip.
|
|
45
|
+
const hasCustomSize = note.width !== undefined || note.height !== undefined;
|
|
46
|
+
if (texts && texts.length === 1 && Object.keys(texts[0]).length === 1 && !hasCustomSize) {
|
|
43
47
|
this.note = texts[0].text;
|
|
44
48
|
}
|
|
45
49
|
else {
|
|
@@ -216,12 +216,13 @@ class Row {
|
|
|
216
216
|
// (e.g. DB entities). Cell.value setter handles unknown values via Value.getType
|
|
217
217
|
// fallback to JSON type, so this cast is safe at runtime.
|
|
218
218
|
this._worksheet.eachColumnKey((column, key) => {
|
|
219
|
-
|
|
219
|
+
const resolved = resolveColumnKeyValue(value, key);
|
|
220
|
+
if (resolved !== undefined) {
|
|
220
221
|
this.getCellEx({
|
|
221
222
|
address: col_cache_1.colCache.encodeAddress(this._number, column.number),
|
|
222
223
|
row: this._number,
|
|
223
224
|
col: column.number
|
|
224
|
-
}).value =
|
|
225
|
+
}).value = resolved;
|
|
225
226
|
}
|
|
226
227
|
});
|
|
227
228
|
}
|
|
@@ -463,3 +464,35 @@ class Row {
|
|
|
463
464
|
}
|
|
464
465
|
}
|
|
465
466
|
exports.Row = Row;
|
|
467
|
+
/**
|
|
468
|
+
* Resolve a column key against a row object, supporting dotted nested paths.
|
|
469
|
+
*
|
|
470
|
+
* A key without a `.` takes the original fast path (`obj[key]`), preserving
|
|
471
|
+
* exact backward compatibility — including keys that legitimately contain a
|
|
472
|
+
* dot only as a flat property name, which still resolve via the fast path
|
|
473
|
+
* first. A dotted key (e.g. `"address.city"`) is resolved by walking each
|
|
474
|
+
* segment; if any segment is missing or not an object, the result is
|
|
475
|
+
* `undefined` (the same signal the caller already uses to skip a cell).
|
|
476
|
+
*
|
|
477
|
+
* @param obj - The row object supplied to `row.values = {...}` / `addRow({...})`.
|
|
478
|
+
* @param key - The column key, optionally a dotted path.
|
|
479
|
+
* @returns The resolved value, or `undefined` when the path cannot be followed.
|
|
480
|
+
*/
|
|
481
|
+
function resolveColumnKeyValue(obj, key) {
|
|
482
|
+
// Fast path: a flat key (no dot) or an exact flat property match. This keeps
|
|
483
|
+
// existing behaviour identical and also lets a literal "a.b" property win
|
|
484
|
+
// over nested traversal when it is actually present on the object.
|
|
485
|
+
const direct = obj[key];
|
|
486
|
+
if (direct !== undefined || !key.includes(".")) {
|
|
487
|
+
return direct;
|
|
488
|
+
}
|
|
489
|
+
// Dotted path: walk segments, bailing out to undefined on any gap.
|
|
490
|
+
let current = obj;
|
|
491
|
+
for (const segment of key.split(".")) {
|
|
492
|
+
if (current === null || typeof current !== "object") {
|
|
493
|
+
return undefined;
|
|
494
|
+
}
|
|
495
|
+
current = current[segment];
|
|
496
|
+
}
|
|
497
|
+
return current;
|
|
498
|
+
}
|
|
@@ -390,13 +390,33 @@ class WorkbookWriterBase {
|
|
|
390
390
|
* ```
|
|
391
391
|
*/
|
|
392
392
|
addImage(image) {
|
|
393
|
+
const { svg, ...raster } = image;
|
|
394
|
+
if (svg &&
|
|
395
|
+
raster.link &&
|
|
396
|
+
raster.buffer == null &&
|
|
397
|
+
raster.base64 == null &&
|
|
398
|
+
raster.filename == null) {
|
|
399
|
+
throw new errors_1.ImageError("An SVG image requires an embedded raster fallback (buffer/base64/filename); it cannot be combined with an external link.");
|
|
400
|
+
}
|
|
393
401
|
const id = this.media.length;
|
|
394
402
|
const medium = {
|
|
395
|
-
...
|
|
403
|
+
...raster,
|
|
396
404
|
type: "image",
|
|
397
|
-
name: `image${id}.${
|
|
405
|
+
name: `image${id}.${raster.extension}`
|
|
398
406
|
};
|
|
399
407
|
this.media.push(medium);
|
|
408
|
+
if (svg) {
|
|
409
|
+
// Register the SVG companion as a second image medium and link it back to
|
|
410
|
+
// the raster blip so the drawing serializer emits the svgBlip extension.
|
|
411
|
+
const svgId = this.media.length;
|
|
412
|
+
this.media.push({
|
|
413
|
+
...svg,
|
|
414
|
+
type: "image",
|
|
415
|
+
extension: "svg",
|
|
416
|
+
name: `image${svgId}.svg`
|
|
417
|
+
});
|
|
418
|
+
medium.svgMediaId = svgId;
|
|
419
|
+
}
|
|
400
420
|
return id;
|
|
401
421
|
}
|
|
402
422
|
getImage(id) {
|
|
@@ -130,6 +130,23 @@ function buildDrawingAnchorsAndRels(media, existingRels, options) {
|
|
|
130
130
|
},
|
|
131
131
|
range: medium.range
|
|
132
132
|
};
|
|
133
|
+
// SVG companion: allocate (and dedupe) a rel for the vector media, then
|
|
134
|
+
// record its rId so the blip serializer emits the asvg:svgBlip extension.
|
|
135
|
+
if (bookImage.svgMediaId !== undefined) {
|
|
136
|
+
const svgKey = `svg:${bookImage.svgMediaId}`;
|
|
137
|
+
let rIdSvg = imageRIdMap[svgKey];
|
|
138
|
+
if (!rIdSvg) {
|
|
139
|
+
const svgImage = options.getBookImage(bookImage.svgMediaId);
|
|
140
|
+
if (svgImage) {
|
|
141
|
+
rIdSvg = options.nextRId(rels);
|
|
142
|
+
imageRIdMap[svgKey] = rIdSvg;
|
|
143
|
+
rels.push(buildImageRel(rIdSvg, svgImage));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (rIdSvg) {
|
|
147
|
+
anchor.picture.svgRId = rIdSvg;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
133
150
|
// Pass through watermark opacity as alphaModFix
|
|
134
151
|
if (medium.opacity !== undefined) {
|
|
135
152
|
const clamped = Math.max(0, Math.min(1, medium.opacity));
|
|
@@ -180,8 +197,8 @@ function filterDrawingAnchors(anchors) {
|
|
|
180
197
|
if (a.range?.br && a.shape) {
|
|
181
198
|
return true;
|
|
182
199
|
}
|
|
183
|
-
// One-cell anchors need a valid picture
|
|
184
|
-
if (!a.range?.br && !a.picture && !a.graphicFrame) {
|
|
200
|
+
// One-cell anchors need a valid picture, graphicFrame (charts) or shape.
|
|
201
|
+
if (!a.range?.br && !a.picture && !a.graphicFrame && !a.shape) {
|
|
185
202
|
return false;
|
|
186
203
|
}
|
|
187
204
|
// Two-cell anchors need either picture, shape, or graphicFrame (charts)
|
|
@@ -1736,10 +1736,40 @@ class Workbook {
|
|
|
1736
1736
|
* const id = workbook.addImage({ extension: "png", link: "https://example.com/logo.png" });
|
|
1737
1737
|
* worksheet.addImage(id, "B2:D6");
|
|
1738
1738
|
* ```
|
|
1739
|
+
*
|
|
1740
|
+
* @example SVG with raster fallback — crisp in modern Excel, safe everywhere
|
|
1741
|
+
* ```typescript
|
|
1742
|
+
* const id = workbook.addImage({
|
|
1743
|
+
* buffer: pngFallbackBytes, // shown by older Excel / non-SVG consumers
|
|
1744
|
+
* extension: "png",
|
|
1745
|
+
* svg: { buffer: svgBytes } // shown by Excel 2016+
|
|
1746
|
+
* });
|
|
1747
|
+
* worksheet.addImage(id, "B2:D6");
|
|
1748
|
+
* ```
|
|
1739
1749
|
*/
|
|
1740
1750
|
addImage(image) {
|
|
1751
|
+
const { svg, ...raster } = image;
|
|
1752
|
+
if (svg &&
|
|
1753
|
+
raster.link &&
|
|
1754
|
+
raster.buffer == null &&
|
|
1755
|
+
raster.base64 == null &&
|
|
1756
|
+
raster.filename == null) {
|
|
1757
|
+
// An SVG companion needs an embedded raster fallback; a *linked* (external)
|
|
1758
|
+
// raster has no package part to attach the svgBlip extension to.
|
|
1759
|
+
throw new errors_1.ImageError("An SVG image requires an embedded raster fallback (buffer/base64/filename); it cannot be combined with an external link.");
|
|
1760
|
+
}
|
|
1741
1761
|
const id = this.media.length;
|
|
1742
|
-
|
|
1762
|
+
const rasterMedia = { ...raster, type: "image" };
|
|
1763
|
+
this.media.push(rasterMedia);
|
|
1764
|
+
if (svg) {
|
|
1765
|
+
// Register the SVG as a second `type: "image"` media so it flows through
|
|
1766
|
+
// the existing media naming, content-types, and zip-writing paths. Link
|
|
1767
|
+
// it back to the raster blip so the drawing serializer can emit the
|
|
1768
|
+
// asvg:svgBlip extension.
|
|
1769
|
+
const svgId = this.media.length;
|
|
1770
|
+
this.media.push({ ...svg, type: "image", extension: "svg" });
|
|
1771
|
+
rasterMedia.svgMediaId = svgId;
|
|
1772
|
+
}
|
|
1743
1773
|
return id;
|
|
1744
1774
|
}
|
|
1745
1775
|
getImage(id) {
|
|
@@ -108,6 +108,8 @@ class Worksheet {
|
|
|
108
108
|
this.autoFilter = options.autoFilter ?? null;
|
|
109
109
|
// for images, etc
|
|
110
110
|
this._media = [];
|
|
111
|
+
// for user-drawn shapes (rectangles, lines, text boxes, …)
|
|
112
|
+
this._shapes = [];
|
|
111
113
|
// for charts
|
|
112
114
|
this._charts = [];
|
|
113
115
|
this._sparklineGroups = [];
|
|
@@ -1012,6 +1014,85 @@ class Worksheet {
|
|
|
1012
1014
|
};
|
|
1013
1015
|
this._media.push(new image_1.Image(this, model));
|
|
1014
1016
|
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Add a free-form drawing shape (rectangle, ellipse, line, text box, …) to
|
|
1019
|
+
* the worksheet, anchored to a cell range.
|
|
1020
|
+
*
|
|
1021
|
+
* Unlike images, shapes need no media file — the geometry, fill, outline and
|
|
1022
|
+
* optional text label are written directly into the drawing part.
|
|
1023
|
+
*
|
|
1024
|
+
* @example
|
|
1025
|
+
* ```typescript
|
|
1026
|
+
* worksheet.addShape({
|
|
1027
|
+
* type: "rect",
|
|
1028
|
+
* range: "B2:D5",
|
|
1029
|
+
* fillColor: "FFD966",
|
|
1030
|
+
* lineColor: "000000",
|
|
1031
|
+
* lineWidth: 1,
|
|
1032
|
+
* text: "Important"
|
|
1033
|
+
* });
|
|
1034
|
+
* ```
|
|
1035
|
+
*/
|
|
1036
|
+
addShape(options) {
|
|
1037
|
+
const range = options.range;
|
|
1038
|
+
// A shape must cover an area, mirroring images. Reject inputs that resolve
|
|
1039
|
+
// to no size up front, with a clear shape-specific message — otherwise the
|
|
1040
|
+
// failure surfaces much later as a confusing `ImageError` from the internal
|
|
1041
|
+
// range parser when the worksheet is serialized.
|
|
1042
|
+
const hasArea = (typeof range === "string" && range.includes(":")) ||
|
|
1043
|
+
(typeof range === "object" &&
|
|
1044
|
+
range !== null &&
|
|
1045
|
+
("br" in range || "ext" in range || "pos" in range));
|
|
1046
|
+
if (!hasArea) {
|
|
1047
|
+
throw new errors_1.ImageError('addShape requires a range covering an area: a cell range like "B2:D5", or an object with `br`, `ext`, or `pos`.');
|
|
1048
|
+
}
|
|
1049
|
+
this._shapes.push({
|
|
1050
|
+
type: "shape",
|
|
1051
|
+
shapeType: options.type ?? "rect",
|
|
1052
|
+
range,
|
|
1053
|
+
fillColor: options.fillColor,
|
|
1054
|
+
lineColor: options.lineColor,
|
|
1055
|
+
lineWidth: options.lineWidth,
|
|
1056
|
+
text: options.text,
|
|
1057
|
+
name: options.name
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
/** All shapes added to this worksheet. */
|
|
1061
|
+
getShapes() {
|
|
1062
|
+
return this._shapes.slice();
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Resolve a shape's `range` into concrete two-cell anchor coordinates,
|
|
1066
|
+
* reusing the `Image` range parser so cell-address/anchor handling stays in
|
|
1067
|
+
* one place. Returns a serializable ShapeModel for the worksheet xform.
|
|
1068
|
+
*/
|
|
1069
|
+
_resolveShapeModel(shape) {
|
|
1070
|
+
let range;
|
|
1071
|
+
try {
|
|
1072
|
+
const probe = new image_1.Image(this, { type: "image", imageId: "", range: shape.range });
|
|
1073
|
+
// The probe is always an "image" type, so its model carries `range`.
|
|
1074
|
+
range = probe.model.range;
|
|
1075
|
+
}
|
|
1076
|
+
catch {
|
|
1077
|
+
// Range could not be parsed into an anchor (addShape validates the common
|
|
1078
|
+
// cases up front; this guards exotic inputs). Drop the anchor so the
|
|
1079
|
+
// serializer skips this shape rather than failing the whole worksheet.
|
|
1080
|
+
range = undefined;
|
|
1081
|
+
}
|
|
1082
|
+
if (!range) {
|
|
1083
|
+
return { ...shape, anchorRange: undefined };
|
|
1084
|
+
}
|
|
1085
|
+
return {
|
|
1086
|
+
...shape,
|
|
1087
|
+
anchorRange: {
|
|
1088
|
+
tl: range.tl,
|
|
1089
|
+
br: range.br,
|
|
1090
|
+
ext: range.ext,
|
|
1091
|
+
pos: range.pos,
|
|
1092
|
+
editAs: range.editAs
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1015
1096
|
getImages() {
|
|
1016
1097
|
return this._media.filter(m => m.type === "image");
|
|
1017
1098
|
}
|
|
@@ -1757,6 +1838,7 @@ class Worksheet {
|
|
|
1757
1838
|
views: this.views,
|
|
1758
1839
|
autoFilter: this.autoFilter,
|
|
1759
1840
|
media: this._media.map(medium => medium.model),
|
|
1841
|
+
shapes: this._shapes.map(shape => this._resolveShapeModel(shape)),
|
|
1760
1842
|
sheetProtection: this.sheetProtection,
|
|
1761
1843
|
tables: Object.values(this.tables).map(table => table.model),
|
|
1762
1844
|
pivotTables: this.pivotTables,
|
|
@@ -1822,6 +1904,7 @@ class Worksheet {
|
|
|
1822
1904
|
this.views = value.views;
|
|
1823
1905
|
this.autoFilter = value.autoFilter;
|
|
1824
1906
|
this._media = value.media.map(medium => new image_1.Image(this, medium));
|
|
1907
|
+
this._shapes = value.shapes ? value.shapes.slice() : [];
|
|
1825
1908
|
// Restore watermark state from media entries
|
|
1826
1909
|
this._watermark = value.watermark ?? null;
|
|
1827
1910
|
if (!this._watermark) {
|
|
@@ -4,6 +4,9 @@ exports.VmlShapeXform = void 0;
|
|
|
4
4
|
const base_xform_1 = require("../base-xform.js");
|
|
5
5
|
const vml_client_data_xform_1 = require("./vml-client-data-xform.js");
|
|
6
6
|
const vml_textbox_xform_1 = require("./vml-textbox-xform.js");
|
|
7
|
+
/** Default comment box geometry in points (matches legacy Excel notes). */
|
|
8
|
+
const DEFAULT_NOTE_WIDTH_PT = 97.8;
|
|
9
|
+
const DEFAULT_NOTE_HEIGHT_PT = 59.1;
|
|
7
10
|
class VmlShapeXform extends base_xform_1.BaseXform {
|
|
8
11
|
constructor() {
|
|
9
12
|
super();
|
|
@@ -40,6 +43,21 @@ class VmlShapeXform extends base_xform_1.BaseXform {
|
|
|
40
43
|
editAs: "",
|
|
41
44
|
protection: {}
|
|
42
45
|
};
|
|
46
|
+
{
|
|
47
|
+
// Recover the comment box geometry from the VML style string
|
|
48
|
+
// (e.g. "...width:120pt;height:80pt;..."). Only surface width/height
|
|
49
|
+
// when they differ from the legacy defaults, so untouched notes keep
|
|
50
|
+
// a clean model (and stay byte-compatible with prior behaviour).
|
|
51
|
+
const style = node.attributes.style ?? "";
|
|
52
|
+
const width = VmlShapeXform.parseStyleLength(style, "width");
|
|
53
|
+
const height = VmlShapeXform.parseStyleLength(style, "height");
|
|
54
|
+
if (width !== undefined && width !== DEFAULT_NOTE_WIDTH_PT) {
|
|
55
|
+
this.model.width = width;
|
|
56
|
+
}
|
|
57
|
+
if (height !== undefined && height !== DEFAULT_NOTE_HEIGHT_PT) {
|
|
58
|
+
this.model.height = height;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
43
61
|
break;
|
|
44
62
|
default:
|
|
45
63
|
this.parser = this.map[node.name];
|
|
@@ -78,13 +96,29 @@ class VmlShapeXform extends base_xform_1.BaseXform {
|
|
|
78
96
|
return true;
|
|
79
97
|
}
|
|
80
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Extract a points-valued length (e.g. `width:120pt`) from a VML style
|
|
101
|
+
* string. Returns `undefined` when the property is absent or not in `pt`.
|
|
102
|
+
*/
|
|
103
|
+
static parseStyleLength(style, prop) {
|
|
104
|
+
const match = new RegExp(`(?:^|;)\\s*${prop}\\s*:\\s*([0-9.]+)pt`, "i").exec(style);
|
|
105
|
+
if (!match) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
const value = parseFloat(match[1]);
|
|
109
|
+
return Number.isFinite(value) ? value : undefined;
|
|
110
|
+
}
|
|
81
111
|
}
|
|
82
112
|
exports.VmlShapeXform = VmlShapeXform;
|
|
83
|
-
VmlShapeXform.V_SHAPE_ATTRIBUTES = (model, index) =>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
113
|
+
VmlShapeXform.V_SHAPE_ATTRIBUTES = (model, index) => {
|
|
114
|
+
const width = model.note?.width ?? DEFAULT_NOTE_WIDTH_PT;
|
|
115
|
+
const height = model.note?.height ?? DEFAULT_NOTE_HEIGHT_PT;
|
|
116
|
+
return {
|
|
117
|
+
id: `_x0000_s${1025 + index}`,
|
|
118
|
+
type: "#_x0000_t202",
|
|
119
|
+
style: `position:absolute; margin-left:105.3pt;margin-top:10.5pt;width:${width}pt;height:${height}pt;z-index:1;visibility:hidden`,
|
|
120
|
+
fillcolor: "infoBackground [80]",
|
|
121
|
+
strokecolor: "none [81]",
|
|
122
|
+
"o:insetmode": model.note.margins && model.note.margins.insetmode
|
|
123
|
+
};
|
|
124
|
+
};
|
|
@@ -24,7 +24,9 @@ class ContentTypesXform extends base_xform_1.BaseXform {
|
|
|
24
24
|
mediaHash[imageType] = true;
|
|
25
25
|
xmlStream.leafNode("Default", {
|
|
26
26
|
Extension: imageType,
|
|
27
|
-
|
|
27
|
+
// SVG's IANA media type is "image/svg+xml"; everything else follows
|
|
28
|
+
// the "image/<ext>" convention.
|
|
29
|
+
ContentType: imageType === "svg" ? "image/svg+xml" : `image/${imageType}`
|
|
28
30
|
});
|
|
29
31
|
}
|
|
30
32
|
}
|