ts-visio 1.6.0 → 1.7.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 +56 -0
- package/dist/Shape.d.ts +9 -0
- package/dist/Shape.js +12 -0
- package/dist/ShapeModifier.d.ts +7 -0
- package/dist/ShapeModifier.js +21 -0
- package/dist/VisioDocument.d.ts +15 -1
- package/dist/VisioDocument.js +19 -0
- package/dist/core/StyleSheetManager.d.ts +37 -0
- package/dist/core/StyleSheetManager.js +216 -0
- package/dist/shapes/ShapeBuilder.js +14 -0
- package/dist/templates/MinimalVsdx.js +9 -1
- package/dist/types/VisioTypes.d.ts +71 -0
- package/dist/utils/StyleHelpers.d.ts +1 -0
- package/dist/utils/StyleHelpers.js +5 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,6 +26,7 @@ Built using specific schema-level abstractions to handle the complex internal st
|
|
|
26
26
|
- **Page Size & Orientation**: Set canvas dimensions with named sizes (`Letter`, `A4`, …) or raw inches; rotate between portrait and landscape.
|
|
27
27
|
- **Document Metadata**: Read and write document properties (title, author, description, keywords, company, dates) via `doc.getMetadata()` / `doc.setMetadata()`.
|
|
28
28
|
- **Named Connection Points**: Define specific ports on shapes (`Top`, `Right`, etc.) and connect to them precisely using `fromPort`/`toPort` on any connector API.
|
|
29
|
+
- **StyleSheets**: Create document-level named styles with fill, line, and text properties via `doc.createStyle()` and apply them to shapes at creation time (`styleId`) or post-creation (`shape.applyStyle()`).
|
|
29
30
|
|
|
30
31
|
Feature gaps are being tracked in [FEATURES.md](./FEATURES.md).
|
|
31
32
|
|
|
@@ -610,6 +611,61 @@ Unknown port names fall back gracefully to edge-intersection routing without thr
|
|
|
610
611
|
|
|
611
612
|
---
|
|
612
613
|
|
|
614
|
+
#### 29. StyleSheets (Document-Level Styles)
|
|
615
|
+
Define reusable named styles at the document level and apply them to shapes so they inherit line, fill, and text properties without repeating the same values on every shape.
|
|
616
|
+
|
|
617
|
+
```typescript
|
|
618
|
+
// 1. Create a document-level style
|
|
619
|
+
const headerStyle = doc.createStyle('Header', {
|
|
620
|
+
fillColor: '#4472C4', // Blue fill
|
|
621
|
+
lineColor: '#2F5597', // Dark-blue border
|
|
622
|
+
lineWeight: 1.5, // 1.5 pt stroke
|
|
623
|
+
fontColor: '#FFFFFF', // White text
|
|
624
|
+
fontSize: 14, // 14 pt
|
|
625
|
+
bold: true,
|
|
626
|
+
fontFamily: 'Calibri',
|
|
627
|
+
horzAlign: 'center',
|
|
628
|
+
verticalAlign: 'middle',
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
const bodyStyle = doc.createStyle('Body', {
|
|
632
|
+
fillColor: '#DEEAF1',
|
|
633
|
+
lineColor: '#2F5597',
|
|
634
|
+
fontSize: 11,
|
|
635
|
+
horzAlign: 'left',
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
// 2. Apply at shape-creation time
|
|
639
|
+
const title = await page.addShape({
|
|
640
|
+
text: 'System Architecture',
|
|
641
|
+
x: 1, y: 8, width: 8, height: 1,
|
|
642
|
+
styleId: headerStyle.id, // sets LineStyle, FillStyle, TextStyle
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
// Fine-grained: apply only the fill from one style, line from another
|
|
646
|
+
const hybrid = await page.addShape({
|
|
647
|
+
text: 'Hybrid',
|
|
648
|
+
x: 1, y: 6, width: 3, height: 1,
|
|
649
|
+
fillStyleId: headerStyle.id,
|
|
650
|
+
lineStyleId: bodyStyle.id,
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
// 3. Apply (or change) style post-creation
|
|
654
|
+
const box = await page.addShape({ text: 'Server', x: 4, y: 4, width: 2, height: 1 });
|
|
655
|
+
box.applyStyle(bodyStyle.id); // all three categories
|
|
656
|
+
box.applyStyle(headerStyle.id, 'fill'); // fill only — leaves line & text unchanged
|
|
657
|
+
box.applyStyle(headerStyle.id, 'text'); // text only
|
|
658
|
+
|
|
659
|
+
// 4. List all styles in the document
|
|
660
|
+
const styles = doc.getStyles();
|
|
661
|
+
// [ { id: 0, name: 'No Style' }, { id: 1, name: 'Normal' }, { id: 2, name: 'Header' }, … ]
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
`StyleProps` supports: `fillColor`, `lineColor`, `lineWeight` (pt), `linePattern`, `fontColor`, `fontSize` (pt), `bold`, `italic`, `underline`, `strikethrough`, `fontFamily`, `horzAlign`, `verticalAlign`, `spaceBefore`, `spaceAfter`, `lineSpacing`, `textMarginTop/Bottom/Left/Right` (in).
|
|
665
|
+
Local shape properties always override inherited stylesheet values.
|
|
666
|
+
|
|
667
|
+
---
|
|
668
|
+
|
|
613
669
|
## Examples
|
|
614
670
|
|
|
615
671
|
Check out the [examples](./examples) directory for complete scripts.
|
package/dist/Shape.d.ts
CHANGED
|
@@ -36,6 +36,15 @@ export declare class Shape {
|
|
|
36
36
|
* Returns the zero-based index (IX) of the newly added point.
|
|
37
37
|
*/
|
|
38
38
|
addConnectionPoint(point: ConnectionPointDef): number;
|
|
39
|
+
/**
|
|
40
|
+
* Apply a document-level stylesheet to this shape.
|
|
41
|
+
* Create styles via `doc.createStyle()` and pass the returned `id`.
|
|
42
|
+
*
|
|
43
|
+
* @param styleId The stylesheet ID to apply.
|
|
44
|
+
* @param which `'all'` (default) applies to line, fill, and text;
|
|
45
|
+
* `'line'`, `'fill'`, or `'text'` applies to only that category.
|
|
46
|
+
*/
|
|
47
|
+
applyStyle(styleId: number, which?: 'all' | 'line' | 'fill' | 'text'): this;
|
|
39
48
|
setStyle(style: ShapeStyle): Promise<this>;
|
|
40
49
|
placeRightOf(targetShape: Shape, options?: {
|
|
41
50
|
gap: number;
|
package/dist/Shape.js
CHANGED
|
@@ -53,6 +53,18 @@ class Shape {
|
|
|
53
53
|
addConnectionPoint(point) {
|
|
54
54
|
return this.modifier.addConnectionPoint(this.pageId, this.id, point);
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Apply a document-level stylesheet to this shape.
|
|
58
|
+
* Create styles via `doc.createStyle()` and pass the returned `id`.
|
|
59
|
+
*
|
|
60
|
+
* @param styleId The stylesheet ID to apply.
|
|
61
|
+
* @param which `'all'` (default) applies to line, fill, and text;
|
|
62
|
+
* `'line'`, `'fill'`, or `'text'` applies to only that category.
|
|
63
|
+
*/
|
|
64
|
+
applyStyle(styleId, which = 'all') {
|
|
65
|
+
this.modifier.applyStyle(this.pageId, this.id, styleId, which);
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
56
68
|
async setStyle(style) {
|
|
57
69
|
await this.modifier.updateShapeStyle(this.pageId, this.id, style);
|
|
58
70
|
return this;
|
package/dist/ShapeModifier.d.ts
CHANGED
|
@@ -37,6 +37,13 @@ export declare class ShapeModifier {
|
|
|
37
37
|
* Returns the zero-based IX (row index) of the newly added point.
|
|
38
38
|
*/
|
|
39
39
|
addConnectionPoint(pageId: string, shapeId: string, point: ConnectionPointDef): number;
|
|
40
|
+
/**
|
|
41
|
+
* Apply a document-level stylesheet to an existing shape by setting its
|
|
42
|
+
* `LineStyle`, `FillStyle`, and/or `TextStyle` attributes.
|
|
43
|
+
*
|
|
44
|
+
* @param which `'all'` (default) sets all three; `'line'`, `'fill'`, or `'text'` sets only one.
|
|
45
|
+
*/
|
|
46
|
+
applyStyle(pageId: string, shapeId: string, styleId: number, which?: 'all' | 'line' | 'fill' | 'text'): void;
|
|
40
47
|
addShape(pageId: string, props: NewShapeProps, parentId?: string): Promise<string>;
|
|
41
48
|
deleteShape(pageId: string, shapeId: string): Promise<void>;
|
|
42
49
|
private removeShapeFromTree;
|
package/dist/ShapeModifier.js
CHANGED
|
@@ -244,6 +244,27 @@ class ShapeModifier {
|
|
|
244
244
|
this.saveParsed(pageId, parsed);
|
|
245
245
|
return ix;
|
|
246
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Apply a document-level stylesheet to an existing shape by setting its
|
|
249
|
+
* `LineStyle`, `FillStyle`, and/or `TextStyle` attributes.
|
|
250
|
+
*
|
|
251
|
+
* @param which `'all'` (default) sets all three; `'line'`, `'fill'`, or `'text'` sets only one.
|
|
252
|
+
*/
|
|
253
|
+
applyStyle(pageId, shapeId, styleId, which = 'all') {
|
|
254
|
+
const parsed = this.getParsed(pageId);
|
|
255
|
+
const shapeMap = this.getShapeMap(parsed);
|
|
256
|
+
const shape = shapeMap.get(shapeId);
|
|
257
|
+
if (!shape)
|
|
258
|
+
throw new Error(`Shape ${shapeId} not found on page ${pageId}`);
|
|
259
|
+
const sid = styleId.toString();
|
|
260
|
+
if (which === 'all' || which === 'line')
|
|
261
|
+
shape['@_LineStyle'] = sid;
|
|
262
|
+
if (which === 'all' || which === 'fill')
|
|
263
|
+
shape['@_FillStyle'] = sid;
|
|
264
|
+
if (which === 'all' || which === 'text')
|
|
265
|
+
shape['@_TextStyle'] = sid;
|
|
266
|
+
this.saveParsed(pageId, parsed);
|
|
267
|
+
}
|
|
247
268
|
async addShape(pageId, props, parentId) {
|
|
248
269
|
const parsed = this.getParsed(pageId);
|
|
249
270
|
// Ensure Shapes container exists
|
package/dist/VisioDocument.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Page } from './Page';
|
|
2
|
-
import { DocumentMetadata } from './types/VisioTypes';
|
|
2
|
+
import { DocumentMetadata, StyleProps, StyleRecord } from './types/VisioTypes';
|
|
3
3
|
export declare class VisioDocument {
|
|
4
4
|
private pkg;
|
|
5
5
|
private pageManager;
|
|
6
6
|
private _pageCache;
|
|
7
7
|
private mediaManager;
|
|
8
8
|
private metadataManager;
|
|
9
|
+
private styleSheetManager;
|
|
9
10
|
private constructor();
|
|
10
11
|
static create(): Promise<VisioDocument>;
|
|
11
12
|
static load(pathOrBuffer: string | Buffer | ArrayBuffer | Uint8Array): Promise<VisioDocument>;
|
|
@@ -42,5 +43,18 @@ export declare class VisioDocument {
|
|
|
42
43
|
* doc.setMetadata({ title: 'My Diagram', author: 'Alice', company: 'ACME' });
|
|
43
44
|
*/
|
|
44
45
|
setMetadata(props: Partial<DocumentMetadata>): void;
|
|
46
|
+
/**
|
|
47
|
+
* Create a named document-level stylesheet and return its record.
|
|
48
|
+
* The returned `id` can be passed to `addShape({ styleId })` or `shape.applyStyle()`.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* const s = doc.createStyle('Header', { fillColor: '#4472C4', fontColor: '#ffffff', bold: true });
|
|
52
|
+
* const shape = await page.addShape({ text: 'Title', x: 1, y: 1, width: 3, height: 1, styleId: s.id });
|
|
53
|
+
*/
|
|
54
|
+
createStyle(name: string, props?: StyleProps): StyleRecord;
|
|
55
|
+
/**
|
|
56
|
+
* Return all stylesheets defined in the document (including built-in styles).
|
|
57
|
+
*/
|
|
58
|
+
getStyles(): StyleRecord[];
|
|
45
59
|
save(filename?: string): Promise<Buffer>;
|
|
46
60
|
}
|
package/dist/VisioDocument.js
CHANGED
|
@@ -39,6 +39,7 @@ const PageManager_1 = require("./core/PageManager");
|
|
|
39
39
|
const Page_1 = require("./Page");
|
|
40
40
|
const MediaManager_1 = require("./core/MediaManager");
|
|
41
41
|
const MetadataManager_1 = require("./core/MetadataManager");
|
|
42
|
+
const StyleSheetManager_1 = require("./core/StyleSheetManager");
|
|
42
43
|
class VisioDocument {
|
|
43
44
|
constructor(pkg) {
|
|
44
45
|
this.pkg = pkg;
|
|
@@ -46,6 +47,7 @@ class VisioDocument {
|
|
|
46
47
|
this.pageManager = new PageManager_1.PageManager(pkg);
|
|
47
48
|
this.mediaManager = new MediaManager_1.MediaManager(pkg);
|
|
48
49
|
this.metadataManager = new MetadataManager_1.MetadataManager(pkg);
|
|
50
|
+
this.styleSheetManager = new StyleSheetManager_1.StyleSheetManager(pkg);
|
|
49
51
|
}
|
|
50
52
|
static async create() {
|
|
51
53
|
const pkg = await VisioPackage_1.VisioPackage.create();
|
|
@@ -150,6 +152,23 @@ class VisioDocument {
|
|
|
150
152
|
setMetadata(props) {
|
|
151
153
|
this.metadataManager.setMetadata(props);
|
|
152
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Create a named document-level stylesheet and return its record.
|
|
157
|
+
* The returned `id` can be passed to `addShape({ styleId })` or `shape.applyStyle()`.
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* const s = doc.createStyle('Header', { fillColor: '#4472C4', fontColor: '#ffffff', bold: true });
|
|
161
|
+
* const shape = await page.addShape({ text: 'Title', x: 1, y: 1, width: 3, height: 1, styleId: s.id });
|
|
162
|
+
*/
|
|
163
|
+
createStyle(name, props = {}) {
|
|
164
|
+
return this.styleSheetManager.createStyle(name, props);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Return all stylesheets defined in the document (including built-in styles).
|
|
168
|
+
*/
|
|
169
|
+
getStyles() {
|
|
170
|
+
return this.styleSheetManager.getStyles();
|
|
171
|
+
}
|
|
153
172
|
async save(filename) {
|
|
154
173
|
return this.pkg.save(filename);
|
|
155
174
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { VisioPackage } from '../VisioPackage';
|
|
2
|
+
import { StyleProps, StyleRecord } from '../types/VisioTypes';
|
|
3
|
+
/**
|
|
4
|
+
* Manages document-level stylesheets stored in `visio/document.xml`.
|
|
5
|
+
*
|
|
6
|
+
* Stylesheets define reusable sets of line, fill, and text properties that
|
|
7
|
+
* shapes inherit via the `LineStyle`, `FillStyle`, and `TextStyle` attributes.
|
|
8
|
+
*
|
|
9
|
+
* The minimal document template ships with:
|
|
10
|
+
* - ID 0 "No Style" — the inheritance root (all shapes fall back here)
|
|
11
|
+
* - ID 1 "Normal" — empty style inheriting everything from ID 0
|
|
12
|
+
*
|
|
13
|
+
* User-created styles receive IDs starting at 2.
|
|
14
|
+
*/
|
|
15
|
+
export declare class StyleSheetManager {
|
|
16
|
+
private pkg;
|
|
17
|
+
private parser;
|
|
18
|
+
private builder;
|
|
19
|
+
constructor(pkg: VisioPackage);
|
|
20
|
+
/**
|
|
21
|
+
* Create a new named document-level stylesheet and return its record.
|
|
22
|
+
* The returned `id` can be passed to `addShape({ styleId })` or `shape.applyStyle()`.
|
|
23
|
+
*/
|
|
24
|
+
createStyle(name: string, props?: StyleProps): StyleRecord;
|
|
25
|
+
/**
|
|
26
|
+
* Return all stylesheets defined in the document (including the built-in ones).
|
|
27
|
+
*/
|
|
28
|
+
getStyles(): StyleRecord[];
|
|
29
|
+
private getParsedDoc;
|
|
30
|
+
private saveParsedDoc;
|
|
31
|
+
/** Ensure `<StyleSheets>` contains at least Style 0 and Style 1. */
|
|
32
|
+
private ensureStyleSheets;
|
|
33
|
+
private nextId;
|
|
34
|
+
/** Normalise a fast-xml-parser value that may be undefined, a single object, or an array. */
|
|
35
|
+
private normalizeArray;
|
|
36
|
+
private buildStyleSheetXml;
|
|
37
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StyleSheetManager = void 0;
|
|
4
|
+
const StyleHelpers_1 = require("../utils/StyleHelpers");
|
|
5
|
+
const XmlHelper_1 = require("../utils/XmlHelper");
|
|
6
|
+
const HORZ_ALIGN = {
|
|
7
|
+
left: '0', center: '1', right: '2', justify: '3',
|
|
8
|
+
};
|
|
9
|
+
const VERT_ALIGN = {
|
|
10
|
+
top: '0', middle: '1', bottom: '2',
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Manages document-level stylesheets stored in `visio/document.xml`.
|
|
14
|
+
*
|
|
15
|
+
* Stylesheets define reusable sets of line, fill, and text properties that
|
|
16
|
+
* shapes inherit via the `LineStyle`, `FillStyle`, and `TextStyle` attributes.
|
|
17
|
+
*
|
|
18
|
+
* The minimal document template ships with:
|
|
19
|
+
* - ID 0 "No Style" — the inheritance root (all shapes fall back here)
|
|
20
|
+
* - ID 1 "Normal" — empty style inheriting everything from ID 0
|
|
21
|
+
*
|
|
22
|
+
* User-created styles receive IDs starting at 2.
|
|
23
|
+
*/
|
|
24
|
+
class StyleSheetManager {
|
|
25
|
+
constructor(pkg) {
|
|
26
|
+
this.pkg = pkg;
|
|
27
|
+
this.parser = (0, XmlHelper_1.createXmlParser)();
|
|
28
|
+
this.builder = (0, XmlHelper_1.createXmlBuilder)();
|
|
29
|
+
}
|
|
30
|
+
// ── Public API ────────────────────────────────────────────────────────────
|
|
31
|
+
/**
|
|
32
|
+
* Create a new named document-level stylesheet and return its record.
|
|
33
|
+
* The returned `id` can be passed to `addShape({ styleId })` or `shape.applyStyle()`.
|
|
34
|
+
*/
|
|
35
|
+
createStyle(name, props = {}) {
|
|
36
|
+
const parsed = this.getParsedDoc();
|
|
37
|
+
const doc = parsed['VisioDocument'];
|
|
38
|
+
this.ensureStyleSheets(doc);
|
|
39
|
+
const styleSheets = doc['StyleSheets'];
|
|
40
|
+
const existing = this.normalizeArray(styleSheets['StyleSheet']);
|
|
41
|
+
const newId = this.nextId(existing);
|
|
42
|
+
existing.push(this.buildStyleSheetXml(newId, name, props));
|
|
43
|
+
styleSheets['StyleSheet'] = existing;
|
|
44
|
+
this.saveParsedDoc(parsed);
|
|
45
|
+
return { id: newId, name };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Return all stylesheets defined in the document (including the built-in ones).
|
|
49
|
+
*/
|
|
50
|
+
getStyles() {
|
|
51
|
+
const parsed = this.getParsedDoc();
|
|
52
|
+
const doc = parsed['VisioDocument'];
|
|
53
|
+
const styleSheets = doc?.['StyleSheets'];
|
|
54
|
+
if (!styleSheets)
|
|
55
|
+
return [];
|
|
56
|
+
return this.normalizeArray(styleSheets['StyleSheet']).map((s) => ({
|
|
57
|
+
id: parseInt(s['@_ID'], 10),
|
|
58
|
+
name: s['@_Name'] ?? s['@_NameU'] ?? `Style ${s['@_ID']}`,
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
// ── Private helpers ───────────────────────────────────────────────────────
|
|
62
|
+
getParsedDoc() {
|
|
63
|
+
const xml = this.pkg.getFileText('visio/document.xml');
|
|
64
|
+
return this.parser.parse(xml);
|
|
65
|
+
}
|
|
66
|
+
saveParsedDoc(parsed) {
|
|
67
|
+
this.pkg.updateFile('visio/document.xml', (0, XmlHelper_1.buildXml)(this.builder, parsed));
|
|
68
|
+
}
|
|
69
|
+
/** Ensure `<StyleSheets>` contains at least Style 0 and Style 1. */
|
|
70
|
+
ensureStyleSheets(doc) {
|
|
71
|
+
if (!doc['StyleSheets']) {
|
|
72
|
+
doc['StyleSheets'] = {};
|
|
73
|
+
}
|
|
74
|
+
const ss = doc['StyleSheets'];
|
|
75
|
+
const existing = this.normalizeArray(ss['StyleSheet']);
|
|
76
|
+
const hasStyle0 = existing.some((s) => s['@_ID'] === '0');
|
|
77
|
+
if (!hasStyle0) {
|
|
78
|
+
existing.unshift({
|
|
79
|
+
'@_ID': '0',
|
|
80
|
+
'@_Name': 'No Style',
|
|
81
|
+
'@_NameU': 'No Style',
|
|
82
|
+
'@_IsCustomName': '0',
|
|
83
|
+
'@_IsCustomNameU': '0',
|
|
84
|
+
Cell: [
|
|
85
|
+
{ '@_N': 'EnableLineProps', '@_V': '1' },
|
|
86
|
+
{ '@_N': 'EnableFillProps', '@_V': '1' },
|
|
87
|
+
{ '@_N': 'EnableTextProps', '@_V': '1' },
|
|
88
|
+
{ '@_N': 'HideForApply', '@_V': '0' },
|
|
89
|
+
],
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const hasStyle1 = existing.some((s) => s['@_ID'] === '1');
|
|
93
|
+
if (!hasStyle1) {
|
|
94
|
+
existing.push({
|
|
95
|
+
'@_ID': '1',
|
|
96
|
+
'@_Name': 'Normal',
|
|
97
|
+
'@_NameU': 'Normal',
|
|
98
|
+
'@_IsCustomName': '0',
|
|
99
|
+
'@_IsCustomNameU': '0',
|
|
100
|
+
'@_LineStyle': '0',
|
|
101
|
+
'@_FillStyle': '0',
|
|
102
|
+
'@_TextStyle': '0',
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
ss['StyleSheet'] = existing;
|
|
106
|
+
}
|
|
107
|
+
nextId(existing) {
|
|
108
|
+
if (existing.length === 0)
|
|
109
|
+
return 2;
|
|
110
|
+
const max = existing.reduce((m, s) => {
|
|
111
|
+
const id = parseInt(s['@_ID'], 10);
|
|
112
|
+
return isNaN(id) ? m : Math.max(m, id);
|
|
113
|
+
}, 0);
|
|
114
|
+
return Math.max(max + 1, 2); // user styles always start at ≥ 2
|
|
115
|
+
}
|
|
116
|
+
/** Normalise a fast-xml-parser value that may be undefined, a single object, or an array. */
|
|
117
|
+
normalizeArray(val) {
|
|
118
|
+
if (!val)
|
|
119
|
+
return [];
|
|
120
|
+
return Array.isArray(val) ? val : [val];
|
|
121
|
+
}
|
|
122
|
+
buildStyleSheetXml(id, name, props) {
|
|
123
|
+
const sheet = {
|
|
124
|
+
'@_ID': id.toString(),
|
|
125
|
+
'@_Name': name,
|
|
126
|
+
'@_NameU': name,
|
|
127
|
+
'@_IsCustomName': '1',
|
|
128
|
+
'@_IsCustomNameU': '1',
|
|
129
|
+
'@_LineStyle': (props.parentLineStyleId ?? 0).toString(),
|
|
130
|
+
'@_FillStyle': (props.parentFillStyleId ?? 0).toString(),
|
|
131
|
+
'@_TextStyle': (props.parentTextStyleId ?? 0).toString(),
|
|
132
|
+
};
|
|
133
|
+
const cells = [];
|
|
134
|
+
const sections = [];
|
|
135
|
+
// ── Fill ────────────────────────────────────────────────────────────
|
|
136
|
+
if (props.fillColor !== undefined) {
|
|
137
|
+
cells.push({ '@_N': 'FillForegnd', '@_V': props.fillColor, '@_F': (0, StyleHelpers_1.hexToRgb)(props.fillColor) });
|
|
138
|
+
cells.push({ '@_N': 'FillPattern', '@_V': '1' });
|
|
139
|
+
}
|
|
140
|
+
// ── Line ────────────────────────────────────────────────────────────
|
|
141
|
+
if (props.lineColor !== undefined) {
|
|
142
|
+
cells.push({ '@_N': 'LineColor', '@_V': props.lineColor, '@_F': (0, StyleHelpers_1.hexToRgb)(props.lineColor) });
|
|
143
|
+
}
|
|
144
|
+
if (props.lineWeight !== undefined) {
|
|
145
|
+
cells.push({ '@_N': 'LineWeight', '@_V': (props.lineWeight / 72).toString(), '@_U': 'PT' });
|
|
146
|
+
}
|
|
147
|
+
if (props.linePattern !== undefined) {
|
|
148
|
+
cells.push({ '@_N': 'LinePattern', '@_V': props.linePattern.toString() });
|
|
149
|
+
}
|
|
150
|
+
// ── TextBlock ───────────────────────────────────────────────────────
|
|
151
|
+
if (props.verticalAlign !== undefined) {
|
|
152
|
+
cells.push({ '@_N': 'VerticalAlign', '@_V': VERT_ALIGN[props.verticalAlign] });
|
|
153
|
+
}
|
|
154
|
+
if (props.textMarginTop !== undefined)
|
|
155
|
+
cells.push({ '@_N': 'TopMargin', '@_V': props.textMarginTop.toString(), '@_U': 'IN' });
|
|
156
|
+
if (props.textMarginBottom !== undefined)
|
|
157
|
+
cells.push({ '@_N': 'BottomMargin', '@_V': props.textMarginBottom.toString(), '@_U': 'IN' });
|
|
158
|
+
if (props.textMarginLeft !== undefined)
|
|
159
|
+
cells.push({ '@_N': 'LeftMargin', '@_V': props.textMarginLeft.toString(), '@_U': 'IN' });
|
|
160
|
+
if (props.textMarginRight !== undefined)
|
|
161
|
+
cells.push({ '@_N': 'RightMargin', '@_V': props.textMarginRight.toString(), '@_U': 'IN' });
|
|
162
|
+
// ── Character section (no @_T on rows — stylesheet convention) ─────
|
|
163
|
+
const charCells = [];
|
|
164
|
+
const colorVal = props.fontColor ?? '#000000';
|
|
165
|
+
if (props.fontColor !== undefined) {
|
|
166
|
+
charCells.push({ '@_N': 'Color', '@_V': colorVal, '@_F': (0, StyleHelpers_1.hexToRgb)(colorVal) });
|
|
167
|
+
}
|
|
168
|
+
let styleVal = 0;
|
|
169
|
+
if (props.bold)
|
|
170
|
+
styleVal |= 1;
|
|
171
|
+
if (props.italic)
|
|
172
|
+
styleVal |= 2;
|
|
173
|
+
if (props.underline)
|
|
174
|
+
styleVal |= 4;
|
|
175
|
+
if (props.strikethrough)
|
|
176
|
+
styleVal |= 8;
|
|
177
|
+
if (styleVal > 0 || props.bold !== undefined || props.italic !== undefined
|
|
178
|
+
|| props.underline !== undefined || props.strikethrough !== undefined) {
|
|
179
|
+
charCells.push({ '@_N': 'Style', '@_V': styleVal.toString() });
|
|
180
|
+
}
|
|
181
|
+
if (props.fontSize !== undefined) {
|
|
182
|
+
charCells.push({ '@_N': 'Size', '@_V': (props.fontSize / 72).toString(), '@_U': 'PT' });
|
|
183
|
+
}
|
|
184
|
+
if (props.fontFamily !== undefined) {
|
|
185
|
+
charCells.push({ '@_N': 'Font', '@_V': '0', '@_F': `FONT("${props.fontFamily}")` });
|
|
186
|
+
}
|
|
187
|
+
if (charCells.length > 0) {
|
|
188
|
+
sections.push({
|
|
189
|
+
'@_N': 'Character',
|
|
190
|
+
Row: { '@_IX': '0', Cell: charCells },
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
// ── Paragraph section ───────────────────────────────────────────────
|
|
194
|
+
const paraCells = [];
|
|
195
|
+
if (props.horzAlign !== undefined)
|
|
196
|
+
paraCells.push({ '@_N': 'HorzAlign', '@_V': HORZ_ALIGN[props.horzAlign] });
|
|
197
|
+
if (props.spaceBefore !== undefined)
|
|
198
|
+
paraCells.push({ '@_N': 'SpBefore', '@_V': (props.spaceBefore / 72).toString(), '@_U': 'PT' });
|
|
199
|
+
if (props.spaceAfter !== undefined)
|
|
200
|
+
paraCells.push({ '@_N': 'SpAfter', '@_V': (props.spaceAfter / 72).toString(), '@_U': 'PT' });
|
|
201
|
+
if (props.lineSpacing !== undefined)
|
|
202
|
+
paraCells.push({ '@_N': 'SpLine', '@_V': (-props.lineSpacing).toString() });
|
|
203
|
+
if (paraCells.length > 0) {
|
|
204
|
+
sections.push({
|
|
205
|
+
'@_N': 'Paragraph',
|
|
206
|
+
Row: { '@_IX': '0', Cell: paraCells },
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (cells.length > 0)
|
|
210
|
+
sheet.Cell = cells;
|
|
211
|
+
if (sections.length > 0)
|
|
212
|
+
sheet.Section = sections;
|
|
213
|
+
return sheet;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
exports.StyleSheetManager = StyleSheetManager;
|
|
@@ -29,6 +29,20 @@ class ShapeBuilder {
|
|
|
29
29
|
if (props.masterId) {
|
|
30
30
|
shape['@_Master'] = props.masterId;
|
|
31
31
|
}
|
|
32
|
+
// Apply document-level stylesheet references
|
|
33
|
+
if (props.styleId !== undefined) {
|
|
34
|
+
shape['@_LineStyle'] = props.styleId.toString();
|
|
35
|
+
shape['@_FillStyle'] = props.styleId.toString();
|
|
36
|
+
shape['@_TextStyle'] = props.styleId.toString();
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
if (props.lineStyleId !== undefined)
|
|
40
|
+
shape['@_LineStyle'] = props.lineStyleId.toString();
|
|
41
|
+
if (props.fillStyleId !== undefined)
|
|
42
|
+
shape['@_FillStyle'] = props.fillStyleId.toString();
|
|
43
|
+
if (props.textStyleId !== undefined)
|
|
44
|
+
shape['@_TextStyle'] = props.textStyleId.toString();
|
|
45
|
+
}
|
|
32
46
|
// Add Styles
|
|
33
47
|
if (props.fillColor) {
|
|
34
48
|
shape.Section.push((0, StyleHelpers_1.createFillSection)(props.fillColor));
|
|
@@ -27,7 +27,15 @@ exports.DOCUMENT_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
|
27
27
|
<ColorEntry IX="1" RGB="#FFFFFF"/>
|
|
28
28
|
</Colors>
|
|
29
29
|
<FaceNames/>
|
|
30
|
-
<StyleSheets
|
|
30
|
+
<StyleSheets>
|
|
31
|
+
<StyleSheet ID="0" Name="No Style" NameU="No Style" IsCustomName="0" IsCustomNameU="0">
|
|
32
|
+
<Cell N="EnableLineProps" V="1"/>
|
|
33
|
+
<Cell N="EnableFillProps" V="1"/>
|
|
34
|
+
<Cell N="EnableTextProps" V="1"/>
|
|
35
|
+
<Cell N="HideForApply" V="0"/>
|
|
36
|
+
</StyleSheet>
|
|
37
|
+
<StyleSheet ID="1" Name="Normal" NameU="Normal" IsCustomName="0" IsCustomNameU="0" LineStyle="0" FillStyle="0" TextStyle="0"/>
|
|
38
|
+
</StyleSheets>
|
|
31
39
|
</VisioDocument>`;
|
|
32
40
|
exports.DOCUMENT_RELS_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
33
41
|
<Relationships xmlns="${VisioConstants_1.XML_NAMESPACES.RELATIONSHIPS}">
|
|
@@ -146,6 +146,65 @@ export interface ConnectorStyle {
|
|
|
146
146
|
}
|
|
147
147
|
/** Non-rectangular geometry variants supported by ShapeBuilder. */
|
|
148
148
|
export type ShapeGeometry = 'rectangle' | 'ellipse' | 'diamond' | 'rounded-rectangle' | 'triangle' | 'parallelogram';
|
|
149
|
+
/** A reference to a document-level stylesheet, returned by `doc.createStyle()`. */
|
|
150
|
+
export interface StyleRecord {
|
|
151
|
+
/** Zero-based integer ID used as `LineStyle` / `FillStyle` / `TextStyle` on shapes. */
|
|
152
|
+
id: number;
|
|
153
|
+
/** Human-readable name given to the style. */
|
|
154
|
+
name: string;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Visual properties for a document-level stylesheet created via `doc.createStyle()`.
|
|
158
|
+
* All fields are optional — omitted properties are inherited from the parent style (default: Style 0 "No Style").
|
|
159
|
+
*/
|
|
160
|
+
export interface StyleProps {
|
|
161
|
+
/** Parent style for line property inheritance. Defaults to 0 ("No Style"). */
|
|
162
|
+
parentLineStyleId?: number;
|
|
163
|
+
/** Parent style for fill property inheritance. Defaults to 0 ("No Style"). */
|
|
164
|
+
parentFillStyleId?: number;
|
|
165
|
+
/** Parent style for text property inheritance. Defaults to 0 ("No Style"). */
|
|
166
|
+
parentTextStyleId?: number;
|
|
167
|
+
/** Stroke colour as a CSS hex string (e.g. `'#cc0000'`). */
|
|
168
|
+
lineColor?: string;
|
|
169
|
+
/** Stroke weight in **points**. Stored internally as inches (pt / 72). */
|
|
170
|
+
lineWeight?: number;
|
|
171
|
+
/** Line pattern. 0 = none, 1 = solid, 2 = dash, 3 = dot, 4 = dash-dot. */
|
|
172
|
+
linePattern?: number;
|
|
173
|
+
/** Background fill colour as a CSS hex string. */
|
|
174
|
+
fillColor?: string;
|
|
175
|
+
/** Text colour as a CSS hex string. */
|
|
176
|
+
fontColor?: string;
|
|
177
|
+
/** Font size in **points**. */
|
|
178
|
+
fontSize?: number;
|
|
179
|
+
/** Bold text. */
|
|
180
|
+
bold?: boolean;
|
|
181
|
+
/** Italic text. */
|
|
182
|
+
italic?: boolean;
|
|
183
|
+
/** Underline text. */
|
|
184
|
+
underline?: boolean;
|
|
185
|
+
/** Strikethrough text. */
|
|
186
|
+
strikethrough?: boolean;
|
|
187
|
+
/** Font family name (e.g. `'Calibri'`). */
|
|
188
|
+
fontFamily?: string;
|
|
189
|
+
/** Horizontal text alignment within the paragraph. */
|
|
190
|
+
horzAlign?: 'left' | 'center' | 'right' | 'justify';
|
|
191
|
+
/** Space before each paragraph in **points**. */
|
|
192
|
+
spaceBefore?: number;
|
|
193
|
+
/** Space after each paragraph in **points**. */
|
|
194
|
+
spaceAfter?: number;
|
|
195
|
+
/** Line-height multiplier (1.0 = single, 1.5 = 1.5×, 2.0 = double). */
|
|
196
|
+
lineSpacing?: number;
|
|
197
|
+
/** Vertical text alignment within the shape. */
|
|
198
|
+
verticalAlign?: 'top' | 'middle' | 'bottom';
|
|
199
|
+
/** Top text margin in inches. */
|
|
200
|
+
textMarginTop?: number;
|
|
201
|
+
/** Bottom text margin in inches. */
|
|
202
|
+
textMarginBottom?: number;
|
|
203
|
+
/** Left text margin in inches. */
|
|
204
|
+
textMarginLeft?: number;
|
|
205
|
+
/** Right text margin in inches. */
|
|
206
|
+
textMarginRight?: number;
|
|
207
|
+
}
|
|
149
208
|
/** Whether a connection point accepts incoming glue, outgoing glue, or both. */
|
|
150
209
|
export type ConnectionPointType = 'inward' | 'outward' | 'both';
|
|
151
210
|
/**
|
|
@@ -240,4 +299,16 @@ export interface NewShapeProps {
|
|
|
240
299
|
* or `StandardConnectionPoints.full` for eight points.
|
|
241
300
|
*/
|
|
242
301
|
connectionPoints?: ConnectionPointDef[];
|
|
302
|
+
/**
|
|
303
|
+
* Apply a document-level stylesheet to this shape (sets `LineStyle`, `FillStyle`,
|
|
304
|
+
* and `TextStyle` all to the same ID). Create styles via `doc.createStyle()`.
|
|
305
|
+
* Takes precedence over `lineStyleId`, `fillStyleId`, and `textStyleId`.
|
|
306
|
+
*/
|
|
307
|
+
styleId?: number;
|
|
308
|
+
/** Apply a stylesheet only for line properties (`LineStyle` attribute). */
|
|
309
|
+
lineStyleId?: number;
|
|
310
|
+
/** Apply a stylesheet only for fill properties (`FillStyle` attribute). */
|
|
311
|
+
fillStyleId?: number;
|
|
312
|
+
/** Apply a stylesheet only for text properties (`TextStyle` attribute). */
|
|
313
|
+
textStyleId?: number;
|
|
243
314
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ArrowHeads = void 0;
|
|
3
|
+
exports.ArrowHeads = exports.hexToRgb = void 0;
|
|
4
4
|
exports.createFillSection = createFillSection;
|
|
5
5
|
exports.horzAlignValue = horzAlignValue;
|
|
6
6
|
exports.vertAlignValue = vertAlignValue;
|
|
@@ -15,10 +15,11 @@ const hexToRgb = (hex) => {
|
|
|
15
15
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
16
16
|
return result ? `RGB(${parseInt(result[1], 16)},${parseInt(result[2], 16)},${parseInt(result[3], 16)})` : 'RGB(0,0,0)';
|
|
17
17
|
};
|
|
18
|
+
exports.hexToRgb = hexToRgb;
|
|
18
19
|
function createFillSection(hexColor) {
|
|
19
20
|
// Visio uses FillForegnd for the main background color.
|
|
20
21
|
// Ideally we should sanitize hexColor to be #RRGGBB.
|
|
21
|
-
const rgbFormula = hexToRgb(hexColor);
|
|
22
|
+
const rgbFormula = (0, exports.hexToRgb)(hexColor);
|
|
22
23
|
return {
|
|
23
24
|
'@_N': 'Fill',
|
|
24
25
|
Cell: [
|
|
@@ -68,7 +69,7 @@ function createCharacterSection(props) {
|
|
|
68
69
|
styleVal |= 8;
|
|
69
70
|
const colorVal = props.color || '#000000';
|
|
70
71
|
const cells = [
|
|
71
|
-
{ '@_N': 'Color', '@_V': colorVal, '@_F': hexToRgb(colorVal) },
|
|
72
|
+
{ '@_N': 'Color', '@_V': colorVal, '@_F': (0, exports.hexToRgb)(colorVal) },
|
|
72
73
|
{ '@_N': 'Style', '@_V': styleVal.toString() },
|
|
73
74
|
];
|
|
74
75
|
if (props.fontSize !== undefined) {
|
|
@@ -144,7 +145,7 @@ function createLineSection(props) {
|
|
|
144
145
|
];
|
|
145
146
|
// Add RGB Formula for custom colors
|
|
146
147
|
if (props.color) {
|
|
147
|
-
cells[0]['@_F'] = hexToRgb(props.color);
|
|
148
|
+
cells[0]['@_F'] = (0, exports.hexToRgb)(props.color);
|
|
148
149
|
}
|
|
149
150
|
return {
|
|
150
151
|
'@_N': 'Line',
|