ts-visio 1.6.0 → 1.9.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 +84 -0
- package/dist/Connector.d.ts +53 -0
- package/dist/Connector.js +28 -0
- package/dist/Page.d.ts +7 -0
- package/dist/Page.js +17 -0
- package/dist/Shape.d.ts +9 -0
- package/dist/Shape.js +12 -0
- package/dist/ShapeModifier.d.ts +13 -0
- package/dist/ShapeModifier.js +33 -0
- package/dist/ShapeReader.d.ts +9 -0
- package/dist/ShapeReader.js +119 -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/index.d.ts +2 -0
- package/dist/index.js +3 -1
- 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,89 @@ 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
|
+
#### 30. Reading Connectors Back
|
|
668
|
+
Enumerate connectors on a page — including those loaded from an existing `.vsdx` file — and inspect or delete them.
|
|
669
|
+
|
|
670
|
+
```typescript
|
|
671
|
+
import { VisioDocument } from 'ts-visio';
|
|
672
|
+
|
|
673
|
+
const doc = await VisioDocument.load(buffer); // or VisioDocument.create()
|
|
674
|
+
const page = doc.pages[0];
|
|
675
|
+
|
|
676
|
+
// Read all connector shapes
|
|
677
|
+
const connectors = page.getConnectors();
|
|
678
|
+
|
|
679
|
+
for (const conn of connectors) {
|
|
680
|
+
console.log(`Connector ${conn.id}: ${conn.fromShapeId} → ${conn.toShapeId}`);
|
|
681
|
+
console.log(' fromPort:', conn.fromPort); // 'center' | { name } | { index }
|
|
682
|
+
console.log(' toPort:', conn.toPort);
|
|
683
|
+
console.log(' style:', conn.style); // lineColor, lineWeight, linePattern, routing
|
|
684
|
+
console.log(' arrows:', conn.beginArrow, conn.endArrow);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Delete a specific connector
|
|
688
|
+
await connectors[0].delete();
|
|
689
|
+
|
|
690
|
+
// After delete the connector is gone but the shapes remain
|
|
691
|
+
console.log(page.getConnectors().length); // 0
|
|
692
|
+
console.log(page.getShapes().length); // shapes still exist
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
613
697
|
## Examples
|
|
614
698
|
|
|
615
699
|
Check out the [examples](./examples) directory for complete scripts.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ConnectorStyle, ConnectionTarget } from './types/VisioTypes';
|
|
2
|
+
import { ShapeModifier } from './ShapeModifier';
|
|
3
|
+
/**
|
|
4
|
+
* Raw data extracted from the page XML for a single connector shape.
|
|
5
|
+
* Returned by `ShapeReader.readConnectors()` and wrapped by the `Connector` class.
|
|
6
|
+
*/
|
|
7
|
+
export interface ConnectorData {
|
|
8
|
+
/** Visio shape ID of the connector (1D shape). */
|
|
9
|
+
id: string;
|
|
10
|
+
/** ID of the shape at the connector's begin-point (BeginX). */
|
|
11
|
+
fromShapeId: string;
|
|
12
|
+
/** ID of the shape at the connector's end-point (EndX). */
|
|
13
|
+
toShapeId: string;
|
|
14
|
+
/** Connection point used on the from-shape. */
|
|
15
|
+
fromPort: ConnectionTarget;
|
|
16
|
+
/** Connection point used on the to-shape. */
|
|
17
|
+
toPort: ConnectionTarget;
|
|
18
|
+
/** Line style extracted from the connector's Line section. */
|
|
19
|
+
style: ConnectorStyle;
|
|
20
|
+
/** Begin-arrow head value (Visio ArrowHeads integer string). */
|
|
21
|
+
beginArrow: string;
|
|
22
|
+
/** End-arrow head value (Visio ArrowHeads integer string). */
|
|
23
|
+
endArrow: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A read/delete wrapper around a connector shape found on a page.
|
|
27
|
+
* Obtain instances via `page.getConnectors()`.
|
|
28
|
+
*/
|
|
29
|
+
export declare class Connector {
|
|
30
|
+
private readonly pageId;
|
|
31
|
+
private readonly modifier;
|
|
32
|
+
/** Visio shape ID of this connector. */
|
|
33
|
+
readonly id: string;
|
|
34
|
+
/** ID of the shape this connector starts from. */
|
|
35
|
+
readonly fromShapeId: string;
|
|
36
|
+
/** ID of the shape this connector ends at. */
|
|
37
|
+
readonly toShapeId: string;
|
|
38
|
+
/** Connection point used on the from-shape ('center', `{ name }`, or `{ index }`). */
|
|
39
|
+
readonly fromPort: ConnectionTarget;
|
|
40
|
+
/** Connection point used on the to-shape ('center', `{ name }`, or `{ index }`). */
|
|
41
|
+
readonly toPort: ConnectionTarget;
|
|
42
|
+
/** Line style (color, weight, pattern, routing) for this connector. */
|
|
43
|
+
readonly style: ConnectorStyle;
|
|
44
|
+
/** Begin-arrow head value string (e.g. `'0'` = none, `'1'` = standard). */
|
|
45
|
+
readonly beginArrow: string;
|
|
46
|
+
/** End-arrow head value string (e.g. `'0'` = none, `'1'` = standard). */
|
|
47
|
+
readonly endArrow: string;
|
|
48
|
+
constructor(data: ConnectorData, pageId: string, modifier: ShapeModifier);
|
|
49
|
+
/**
|
|
50
|
+
* Delete this connector from the page (removes the shape and its Connect entries).
|
|
51
|
+
*/
|
|
52
|
+
delete(): Promise<void>;
|
|
53
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Connector = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* A read/delete wrapper around a connector shape found on a page.
|
|
6
|
+
* Obtain instances via `page.getConnectors()`.
|
|
7
|
+
*/
|
|
8
|
+
class Connector {
|
|
9
|
+
constructor(data, pageId, modifier) {
|
|
10
|
+
this.pageId = pageId;
|
|
11
|
+
this.modifier = modifier;
|
|
12
|
+
this.id = data.id;
|
|
13
|
+
this.fromShapeId = data.fromShapeId;
|
|
14
|
+
this.toShapeId = data.toShapeId;
|
|
15
|
+
this.fromPort = data.fromPort;
|
|
16
|
+
this.toPort = data.toPort;
|
|
17
|
+
this.style = data.style;
|
|
18
|
+
this.beginArrow = data.beginArrow;
|
|
19
|
+
this.endArrow = data.endArrow;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Delete this connector from the page (removes the shape and its Connect entries).
|
|
23
|
+
*/
|
|
24
|
+
async delete() {
|
|
25
|
+
await this.modifier.deleteShape(this.pageId, this.id);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.Connector = Connector;
|
package/dist/Page.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { VisioPage, ConnectorStyle, PageOrientation, PageSizeName, ConnectionTarget } from './types/VisioTypes';
|
|
2
|
+
import { Connector } from './Connector';
|
|
2
3
|
import { VisioPackage } from './VisioPackage';
|
|
3
4
|
import { ShapeModifier } from './ShapeModifier';
|
|
4
5
|
import { NewShapeProps } from './types/VisioTypes';
|
|
@@ -50,6 +51,12 @@ export declare class Page {
|
|
|
50
51
|
*/
|
|
51
52
|
findShapes(predicate: (shape: Shape) => boolean): Shape[];
|
|
52
53
|
addShape(props: NewShapeProps, parentId?: string): Promise<Shape>;
|
|
54
|
+
/**
|
|
55
|
+
* Return all connector shapes on the page.
|
|
56
|
+
* Each `Connector` exposes the from/to shape IDs, port targets, line style, arrows,
|
|
57
|
+
* and a `delete()` method to remove the connector.
|
|
58
|
+
*/
|
|
59
|
+
getConnectors(): Connector[];
|
|
53
60
|
connectShapes(fromShape: Shape, toShape: Shape, beginArrow?: string, endArrow?: string, style?: ConnectorStyle, fromPort?: ConnectionTarget, toPort?: ConnectionTarget): Promise<void>;
|
|
54
61
|
addImage(data: Buffer, name: string, x: number, y: number, width: number, height: number): Promise<Shape>;
|
|
55
62
|
addContainer(props: NewShapeProps): Promise<Shape>;
|
package/dist/Page.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Page = void 0;
|
|
4
4
|
const VisioTypes_1 = require("./types/VisioTypes");
|
|
5
|
+
const Connector_1 = require("./Connector");
|
|
5
6
|
const ShapeReader_1 = require("./ShapeReader");
|
|
6
7
|
const ShapeModifier_1 = require("./ShapeModifier");
|
|
7
8
|
const Shape_1 = require("./Shape");
|
|
@@ -124,6 +125,22 @@ class Page {
|
|
|
124
125
|
});
|
|
125
126
|
return new Shape_1.Shape(internalStub, this.id, this.pkg, this.modifier);
|
|
126
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Return all connector shapes on the page.
|
|
130
|
+
* Each `Connector` exposes the from/to shape IDs, port targets, line style, arrows,
|
|
131
|
+
* and a `delete()` method to remove the connector.
|
|
132
|
+
*/
|
|
133
|
+
getConnectors() {
|
|
134
|
+
const reader = new ShapeReader_1.ShapeReader(this.pkg);
|
|
135
|
+
try {
|
|
136
|
+
const data = reader.readConnectors(this.pagePath);
|
|
137
|
+
return data.map(d => new Connector_1.Connector(d, this.id, this.modifier));
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
console.warn(`Could not read connectors for page ${this.id}:`, e);
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
127
144
|
async connectShapes(fromShape, toShape, beginArrow, endArrow, style, fromPort, toPort) {
|
|
128
145
|
await this.modifier.addConnector(this.id, fromShape.id, toShape.id, beginArrow, endArrow, style, fromPort, toPort);
|
|
129
146
|
}
|
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;
|
|
@@ -124,6 +131,12 @@ export declare class ShapeModifier {
|
|
|
124
131
|
}
|
|
125
132
|
export interface ShapeStyle {
|
|
126
133
|
fillColor?: string;
|
|
134
|
+
/** Border/stroke colour as a CSS hex string (e.g. `'#cc0000'`). */
|
|
135
|
+
lineColor?: string;
|
|
136
|
+
/** Stroke weight in **points**. Stored internally as inches (pt / 72). */
|
|
137
|
+
lineWeight?: number;
|
|
138
|
+
/** Line pattern. 0 = none, 1 = solid (default), 2 = dash, 3 = dot, 4 = dash-dot. */
|
|
139
|
+
linePattern?: number;
|
|
127
140
|
fontColor?: string;
|
|
128
141
|
bold?: boolean;
|
|
129
142
|
/** Italic text. */
|
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
|
|
@@ -379,6 +400,18 @@ class ShapeModifier {
|
|
|
379
400
|
shape.Section = shape.Section.filter((s) => s['@_N'] !== 'Fill');
|
|
380
401
|
shape.Section.push((0, StyleHelpers_1.createFillSection)(style.fillColor));
|
|
381
402
|
}
|
|
403
|
+
// Update/Add Line
|
|
404
|
+
const hasLineProps = style.lineColor !== undefined
|
|
405
|
+
|| style.lineWeight !== undefined
|
|
406
|
+
|| style.linePattern !== undefined;
|
|
407
|
+
if (hasLineProps) {
|
|
408
|
+
shape.Section = shape.Section.filter((s) => s['@_N'] !== 'Line');
|
|
409
|
+
shape.Section.push((0, StyleHelpers_1.createLineSection)({
|
|
410
|
+
color: style.lineColor,
|
|
411
|
+
weight: style.lineWeight !== undefined ? (style.lineWeight / 72).toString() : undefined,
|
|
412
|
+
pattern: style.linePattern !== undefined ? style.linePattern.toString() : undefined,
|
|
413
|
+
}));
|
|
414
|
+
}
|
|
382
415
|
// Update/Add Character (Font/Text Style)
|
|
383
416
|
const hasCharProps = style.fontColor !== undefined
|
|
384
417
|
|| style.bold !== undefined
|
package/dist/ShapeReader.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { VisioPackage } from './VisioPackage';
|
|
2
2
|
import { VisioShape } from './types/VisioTypes';
|
|
3
|
+
import { ConnectorData } from './Connector';
|
|
3
4
|
export declare class ShapeReader {
|
|
4
5
|
private pkg;
|
|
5
6
|
private parser;
|
|
@@ -15,6 +16,14 @@ export declare class ShapeReader {
|
|
|
15
16
|
* Returns undefined if not found.
|
|
16
17
|
*/
|
|
17
18
|
readShapeById(path: string, shapeId: string): VisioShape | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Read all connector shapes from a page XML file.
|
|
21
|
+
* A shape is considered a connector if it has `ObjType=2` or a `BeginX` cell,
|
|
22
|
+
* and is referenced in the page's `<Connects>` section.
|
|
23
|
+
*/
|
|
24
|
+
readConnectors(path: string): ConnectorData[];
|
|
25
|
+
/** Decode a Visio ToPart integer string to a ConnectionTarget. */
|
|
26
|
+
private decodeToPart;
|
|
18
27
|
private gatherShapes;
|
|
19
28
|
private findShapeById;
|
|
20
29
|
private parseShape;
|
package/dist/ShapeReader.js
CHANGED
|
@@ -63,6 +63,125 @@ class ShapeReader {
|
|
|
63
63
|
return undefined;
|
|
64
64
|
return this.findShapeById((0, VisioParsers_1.asArray)(shapesData), shapeId);
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Read all connector shapes from a page XML file.
|
|
68
|
+
* A shape is considered a connector if it has `ObjType=2` or a `BeginX` cell,
|
|
69
|
+
* and is referenced in the page's `<Connects>` section.
|
|
70
|
+
*/
|
|
71
|
+
readConnectors(path) {
|
|
72
|
+
let content;
|
|
73
|
+
try {
|
|
74
|
+
content = this.pkg.getFileText(path);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
const parsed = this.parser.parse(content);
|
|
80
|
+
const shapesData = parsed.PageContents?.Shapes?.Shape;
|
|
81
|
+
if (!shapesData)
|
|
82
|
+
return [];
|
|
83
|
+
// Build a flat map of all shapes by ID (including nested)
|
|
84
|
+
const shapeMap = new Map();
|
|
85
|
+
const collectShapes = (list) => {
|
|
86
|
+
for (const s of list) {
|
|
87
|
+
shapeMap.set(s['@_ID'], s);
|
|
88
|
+
if (s.Shapes?.Shape)
|
|
89
|
+
collectShapes((0, VisioParsers_1.asArray)(s.Shapes.Shape));
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
collectShapes((0, VisioParsers_1.asArray)(shapesData));
|
|
93
|
+
// Identify connector shapes by ObjType=2 or presence of BeginX cell
|
|
94
|
+
const connectorShapes = [];
|
|
95
|
+
for (const [, shape] of shapeMap) {
|
|
96
|
+
const cells = (0, VisioParsers_1.parseCells)(shape);
|
|
97
|
+
if (cells['ObjType']?.V === '2' || cells['BeginX'] !== undefined) {
|
|
98
|
+
connectorShapes.push(shape);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (connectorShapes.length === 0)
|
|
102
|
+
return [];
|
|
103
|
+
// Group <Connect> elements by connector ID (FromSheet)
|
|
104
|
+
const connectsRaw = parsed.PageContents?.Connects?.Connect;
|
|
105
|
+
const connects = (0, VisioParsers_1.asArray)(connectsRaw);
|
|
106
|
+
const connectsByConnector = new Map();
|
|
107
|
+
for (const c of connects) {
|
|
108
|
+
const fromSheet = c['@_FromSheet'];
|
|
109
|
+
const fromCell = c['@_FromCell'];
|
|
110
|
+
if (!connectsByConnector.has(fromSheet)) {
|
|
111
|
+
connectsByConnector.set(fromSheet, {});
|
|
112
|
+
}
|
|
113
|
+
const entry = connectsByConnector.get(fromSheet);
|
|
114
|
+
if (fromCell === 'BeginX')
|
|
115
|
+
entry.beginConnect = c;
|
|
116
|
+
else if (fromCell === 'EndX')
|
|
117
|
+
entry.endConnect = c;
|
|
118
|
+
}
|
|
119
|
+
const ROUTING_BY_VALUE = {
|
|
120
|
+
'1': 'orthogonal',
|
|
121
|
+
'2': 'straight',
|
|
122
|
+
'16': 'curved',
|
|
123
|
+
};
|
|
124
|
+
const result = [];
|
|
125
|
+
for (const connShape of connectorShapes) {
|
|
126
|
+
const connId = connShape['@_ID'];
|
|
127
|
+
const entry = connectsByConnector.get(connId);
|
|
128
|
+
if (!entry?.beginConnect || !entry?.endConnect)
|
|
129
|
+
continue;
|
|
130
|
+
const { beginConnect, endConnect } = entry;
|
|
131
|
+
const fromShapeId = beginConnect['@_ToSheet'];
|
|
132
|
+
const toShapeId = endConnect['@_ToSheet'];
|
|
133
|
+
const fromPort = this.decodeToPart(beginConnect['@_ToPart']);
|
|
134
|
+
const toPort = this.decodeToPart(endConnect['@_ToPart']);
|
|
135
|
+
// Extract line style from the connector's Line section
|
|
136
|
+
const sections = (0, VisioParsers_1.asArray)(connShape.Section);
|
|
137
|
+
let lineColor;
|
|
138
|
+
let lineWeight;
|
|
139
|
+
let linePattern;
|
|
140
|
+
for (const sec of sections) {
|
|
141
|
+
if (sec['@_N'] === 'Line') {
|
|
142
|
+
const lineCells = (0, VisioParsers_1.parseCells)(sec);
|
|
143
|
+
if (lineCells['LineColor']?.V)
|
|
144
|
+
lineColor = lineCells['LineColor'].V;
|
|
145
|
+
if (lineCells['LineWeight']?.V)
|
|
146
|
+
lineWeight = parseFloat(lineCells['LineWeight'].V) * 72; // in→pt
|
|
147
|
+
if (lineCells['LinePattern']?.V)
|
|
148
|
+
linePattern = parseInt(lineCells['LinePattern'].V, 10);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const cells = (0, VisioParsers_1.parseCells)(connShape);
|
|
152
|
+
const routeStyleVal = cells['ShapeRouteStyle']?.V;
|
|
153
|
+
const routing = routeStyleVal ? ROUTING_BY_VALUE[routeStyleVal] : undefined;
|
|
154
|
+
const style = {};
|
|
155
|
+
if (lineColor !== undefined)
|
|
156
|
+
style.lineColor = lineColor;
|
|
157
|
+
if (lineWeight !== undefined)
|
|
158
|
+
style.lineWeight = lineWeight;
|
|
159
|
+
if (linePattern !== undefined)
|
|
160
|
+
style.linePattern = linePattern;
|
|
161
|
+
if (routing !== undefined)
|
|
162
|
+
style.routing = routing;
|
|
163
|
+
result.push({
|
|
164
|
+
id: connId,
|
|
165
|
+
fromShapeId,
|
|
166
|
+
toShapeId,
|
|
167
|
+
fromPort,
|
|
168
|
+
toPort,
|
|
169
|
+
style,
|
|
170
|
+
beginArrow: cells['BeginArrow']?.V ?? '0',
|
|
171
|
+
endArrow: cells['EndArrow']?.V ?? '0',
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
/** Decode a Visio ToPart integer string to a ConnectionTarget. */
|
|
177
|
+
decodeToPart(toPart) {
|
|
178
|
+
const part = toPart !== undefined ? parseInt(toPart, 10) : 3;
|
|
179
|
+
if (isNaN(part) || part === 3)
|
|
180
|
+
return 'center';
|
|
181
|
+
if (part >= 100)
|
|
182
|
+
return { index: part - 100 };
|
|
183
|
+
return 'center';
|
|
184
|
+
}
|
|
66
185
|
gatherShapes(rawShapes, result) {
|
|
67
186
|
for (const s of rawShapes) {
|
|
68
187
|
result.push(this.parseShape(s));
|
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;
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export { ShapeModifier } from './ShapeModifier';
|
|
|
6
6
|
export { VisioDocument } from './VisioDocument';
|
|
7
7
|
export { Page } from './Page';
|
|
8
8
|
export { Shape } from './Shape';
|
|
9
|
+
export { Connector } from './Connector';
|
|
10
|
+
export type { ConnectorData } from './Connector';
|
|
9
11
|
export type { ShapeData, ShapeHyperlink } from './Shape';
|
|
10
12
|
export { Layer } from './Layer';
|
|
11
13
|
export { SchemaDiagram } from './SchemaDiagram';
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.VisioValidator = exports.SchemaDiagram = exports.Layer = exports.Shape = exports.Page = exports.VisioDocument = exports.ShapeModifier = exports.ShapeReader = exports.PageManager = exports.VisioPackage = void 0;
|
|
17
|
+
exports.VisioValidator = exports.SchemaDiagram = exports.Layer = exports.Connector = exports.Shape = exports.Page = exports.VisioDocument = exports.ShapeModifier = exports.ShapeReader = exports.PageManager = exports.VisioPackage = void 0;
|
|
18
18
|
var VisioPackage_1 = require("./VisioPackage");
|
|
19
19
|
Object.defineProperty(exports, "VisioPackage", { enumerable: true, get: function () { return VisioPackage_1.VisioPackage; } });
|
|
20
20
|
var PageManager_1 = require("./core/PageManager");
|
|
@@ -29,6 +29,8 @@ var Page_1 = require("./Page");
|
|
|
29
29
|
Object.defineProperty(exports, "Page", { enumerable: true, get: function () { return Page_1.Page; } });
|
|
30
30
|
var Shape_1 = require("./Shape");
|
|
31
31
|
Object.defineProperty(exports, "Shape", { enumerable: true, get: function () { return Shape_1.Shape; } });
|
|
32
|
+
var Connector_1 = require("./Connector");
|
|
33
|
+
Object.defineProperty(exports, "Connector", { enumerable: true, get: function () { return Connector_1.Connector; } });
|
|
32
34
|
var Layer_1 = require("./Layer");
|
|
33
35
|
Object.defineProperty(exports, "Layer", { enumerable: true, get: function () { return Layer_1.Layer; } });
|
|
34
36
|
var SchemaDiagram_1 = require("./SchemaDiagram");
|
|
@@ -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',
|