ts-visio 1.4.0 → 1.6.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 +90 -1
- package/dist/Page.d.ts +2 -2
- package/dist/Page.js +2 -2
- package/dist/Shape.d.ts +7 -2
- package/dist/Shape.js +9 -2
- package/dist/ShapeModifier.d.ts +27 -2
- package/dist/ShapeModifier.js +71 -7
- package/dist/shapes/ConnectionPointBuilder.d.ts +40 -0
- package/dist/shapes/ConnectionPointBuilder.js +131 -0
- package/dist/shapes/ConnectorBuilder.d.ts +12 -3
- package/dist/shapes/ConnectorBuilder.js +75 -12
- package/dist/shapes/ShapeBuilder.js +39 -3
- package/dist/types/VisioTypes.d.ts +68 -0
- package/dist/types/VisioTypes.js +20 -1
- package/dist/utils/StyleHelpers.d.ts +38 -1
- package/dist/utils/StyleHelpers.js +40 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,13 +18,14 @@ Built using specific schema-level abstractions to handle the complex internal st
|
|
|
18
18
|
- **Modify Content**: Update text content of shapes.
|
|
19
19
|
- **Create Shapes**: Rectangles, ellipses, diamonds, rounded rectangles, triangles, parallelograms.
|
|
20
20
|
- **Connect Shapes**: Dynamic connectors with arrow styles, line styling, and routing (straight / orthogonal / curved).
|
|
21
|
-
- **Text Styling**: Font size, font family, bold, color,
|
|
21
|
+
- **Text Styling**: Font size, font family, bold, italic, underline, strikethrough, color, alignment, paragraph spacing, and text margins.
|
|
22
22
|
- **Shape Transformations**: Rotate, flip (X/Y), and resize shapes via a fluent API.
|
|
23
23
|
- **Deletion**: Remove shapes and pages cleanly (including orphaned connectors and relationships).
|
|
24
24
|
- **Lookup API**: Find shapes by ID, predicate, or look up pages by name.
|
|
25
25
|
- **Read-Back API**: Read custom properties, hyperlinks, and layer assignments from existing shapes.
|
|
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
|
+
- **Named Connection Points**: Define specific ports on shapes (`Top`, `Right`, etc.) and connect to them precisely using `fromPort`/`toPort` on any connector API.
|
|
28
29
|
|
|
29
30
|
Feature gaps are being tracked in [FEATURES.md](./FEATURES.md).
|
|
30
31
|
|
|
@@ -521,6 +522,94 @@ Fields map to OPC parts: `title`, `author`, `description`, `keywords`, `lastModi
|
|
|
521
522
|
|
|
522
523
|
---
|
|
523
524
|
|
|
525
|
+
#### 27. Rich Text Formatting
|
|
526
|
+
Italic, underline, strikethrough, paragraph spacing, and text block margins are available both at shape-creation time and via `shape.setStyle()`.
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
529
|
+
// At creation time
|
|
530
|
+
const shape = await page.addShape({
|
|
531
|
+
text: 'Important',
|
|
532
|
+
x: 2, y: 3, width: 3, height: 1,
|
|
533
|
+
bold: true,
|
|
534
|
+
italic: true,
|
|
535
|
+
underline: true,
|
|
536
|
+
strikethrough: false,
|
|
537
|
+
// Paragraph spacing (in points)
|
|
538
|
+
spaceBefore: 6,
|
|
539
|
+
spaceAfter: 6,
|
|
540
|
+
lineSpacing: 1.5, // 1.5× line height
|
|
541
|
+
// Text block margins (in inches)
|
|
542
|
+
textMarginTop: 0.1,
|
|
543
|
+
textMarginBottom: 0.1,
|
|
544
|
+
textMarginLeft: 0.1,
|
|
545
|
+
textMarginRight: 0.1,
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
// Post-creation via setStyle()
|
|
549
|
+
await shape.setStyle({
|
|
550
|
+
italic: true,
|
|
551
|
+
lineSpacing: 2.0, // double spacing
|
|
552
|
+
textMarginTop: 0.15,
|
|
553
|
+
textMarginLeft: 0.05,
|
|
554
|
+
});
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
`lineSpacing` is a multiplier: `1.0` = single, `1.5` = 1.5×, `2.0` = double.
|
|
558
|
+
`spaceBefore` / `spaceAfter` are in **points**. Text margins are in **inches**.
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
#### 28. Named Connection Points
|
|
563
|
+
Define specific ports on shapes and connect to them precisely instead of relying on edge-intersection.
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
import { StandardConnectionPoints } from 'ts-visio';
|
|
567
|
+
|
|
568
|
+
// 1. Add connection points at shape-creation time
|
|
569
|
+
const nodeA = await page.addShape({
|
|
570
|
+
text: 'A', x: 2, y: 3, width: 2, height: 1,
|
|
571
|
+
connectionPoints: StandardConnectionPoints.cardinal, // Top, Right, Bottom, Left
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
const nodeB = await page.addShape({
|
|
575
|
+
text: 'B', x: 6, y: 3, width: 2, height: 1,
|
|
576
|
+
connectionPoints: StandardConnectionPoints.cardinal,
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// 2. Connect using named ports (Right of A → Left of B)
|
|
580
|
+
await page.connectShapes(nodeA, nodeB, undefined, undefined, undefined,
|
|
581
|
+
{ name: 'Right' }, // fromPort
|
|
582
|
+
{ name: 'Left' }, // toPort
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
// 3. Fluent Shape API
|
|
586
|
+
await nodeA.connectTo(nodeB, undefined, undefined, undefined,
|
|
587
|
+
{ name: 'Right' }, { name: 'Left' });
|
|
588
|
+
|
|
589
|
+
// 4. Add a point to an existing shape by index
|
|
590
|
+
const ix = nodeA.addConnectionPoint({
|
|
591
|
+
name: 'Center',
|
|
592
|
+
xFraction: 0.5, yFraction: 0.5,
|
|
593
|
+
type: 'both',
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// 5. Connect by zero-based index instead of name
|
|
597
|
+
await page.connectShapes(nodeA, nodeB, undefined, undefined, undefined,
|
|
598
|
+
{ index: 1 }, // Right (IX=1 in cardinal preset)
|
|
599
|
+
{ index: 3 }, // Left (IX=3 in cardinal preset)
|
|
600
|
+
);
|
|
601
|
+
|
|
602
|
+
// 6. 'center' target (default behaviour) works alongside named ports
|
|
603
|
+
await page.connectShapes(nodeA, nodeB, undefined, undefined, undefined,
|
|
604
|
+
'center', { name: 'Left' });
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
`StandardConnectionPoints.cardinal` — 4 points: `Top`, `Right`, `Bottom`, `Left`.
|
|
608
|
+
`StandardConnectionPoints.full` — 8 points: cardinal + `TopLeft`, `TopRight`, `BottomRight`, `BottomLeft`.
|
|
609
|
+
Unknown port names fall back gracefully to edge-intersection routing without throwing.
|
|
610
|
+
|
|
611
|
+
---
|
|
612
|
+
|
|
524
613
|
## Examples
|
|
525
614
|
|
|
526
615
|
Check out the [examples](./examples) directory for complete scripts.
|
package/dist/Page.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { VisioPage, ConnectorStyle, PageOrientation, PageSizeName } from './types/VisioTypes';
|
|
1
|
+
import { VisioPage, ConnectorStyle, PageOrientation, PageSizeName, ConnectionTarget } from './types/VisioTypes';
|
|
2
2
|
import { VisioPackage } from './VisioPackage';
|
|
3
3
|
import { ShapeModifier } from './ShapeModifier';
|
|
4
4
|
import { NewShapeProps } from './types/VisioTypes';
|
|
@@ -50,7 +50,7 @@ export declare class Page {
|
|
|
50
50
|
*/
|
|
51
51
|
findShapes(predicate: (shape: Shape) => boolean): Shape[];
|
|
52
52
|
addShape(props: NewShapeProps, parentId?: string): Promise<Shape>;
|
|
53
|
-
connectShapes(fromShape: Shape, toShape: Shape, beginArrow?: string, endArrow?: string, style?: ConnectorStyle): Promise<void>;
|
|
53
|
+
connectShapes(fromShape: Shape, toShape: Shape, beginArrow?: string, endArrow?: string, style?: ConnectorStyle, fromPort?: ConnectionTarget, toPort?: ConnectionTarget): Promise<void>;
|
|
54
54
|
addImage(data: Buffer, name: string, x: number, y: number, width: number, height: number): Promise<Shape>;
|
|
55
55
|
addContainer(props: NewShapeProps): Promise<Shape>;
|
|
56
56
|
addList(props: NewShapeProps, direction?: 'vertical' | 'horizontal'): Promise<Shape>;
|
package/dist/Page.js
CHANGED
|
@@ -124,8 +124,8 @@ class Page {
|
|
|
124
124
|
});
|
|
125
125
|
return new Shape_1.Shape(internalStub, this.id, this.pkg, this.modifier);
|
|
126
126
|
}
|
|
127
|
-
async connectShapes(fromShape, toShape, beginArrow, endArrow, style) {
|
|
128
|
-
await this.modifier.addConnector(this.id, fromShape.id, toShape.id, beginArrow, endArrow, style);
|
|
127
|
+
async connectShapes(fromShape, toShape, beginArrow, endArrow, style, fromPort, toPort) {
|
|
128
|
+
await this.modifier.addConnector(this.id, fromShape.id, toShape.id, beginArrow, endArrow, style, fromPort, toPort);
|
|
129
129
|
}
|
|
130
130
|
async addImage(data, name, x, y, width, height) {
|
|
131
131
|
// 1. Upload Media
|
package/dist/Shape.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { VisioShape, ConnectorStyle } from './types/VisioTypes';
|
|
1
|
+
import { VisioShape, ConnectorStyle, ConnectionTarget, ConnectionPointDef } from './types/VisioTypes';
|
|
2
2
|
import { VisioPackage } from './VisioPackage';
|
|
3
3
|
import { ShapeModifier, ShapeStyle } from './ShapeModifier';
|
|
4
4
|
import { VisioPropType } from './types/VisioTypes';
|
|
@@ -30,7 +30,12 @@ export declare class Shape {
|
|
|
30
30
|
get x(): number;
|
|
31
31
|
get y(): number;
|
|
32
32
|
delete(): Promise<void>;
|
|
33
|
-
connectTo(targetShape: Shape, beginArrow?: string, endArrow?: string, style?: ConnectorStyle): Promise<this>;
|
|
33
|
+
connectTo(targetShape: Shape, beginArrow?: string, endArrow?: string, style?: ConnectorStyle, fromPort?: ConnectionTarget, toPort?: ConnectionTarget): Promise<this>;
|
|
34
|
+
/**
|
|
35
|
+
* Add a connection point to this shape.
|
|
36
|
+
* Returns the zero-based index (IX) of the newly added point.
|
|
37
|
+
*/
|
|
38
|
+
addConnectionPoint(point: ConnectionPointDef): number;
|
|
34
39
|
setStyle(style: ShapeStyle): Promise<this>;
|
|
35
40
|
placeRightOf(targetShape: Shape, options?: {
|
|
36
41
|
gap: number;
|
package/dist/Shape.js
CHANGED
|
@@ -42,10 +42,17 @@ class Shape {
|
|
|
42
42
|
async delete() {
|
|
43
43
|
await this.modifier.deleteShape(this.pageId, this.id);
|
|
44
44
|
}
|
|
45
|
-
async connectTo(targetShape, beginArrow, endArrow, style) {
|
|
46
|
-
await this.modifier.addConnector(this.pageId, this.id, targetShape.id, beginArrow, endArrow, style);
|
|
45
|
+
async connectTo(targetShape, beginArrow, endArrow, style, fromPort, toPort) {
|
|
46
|
+
await this.modifier.addConnector(this.pageId, this.id, targetShape.id, beginArrow, endArrow, style, fromPort, toPort);
|
|
47
47
|
return this;
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Add a connection point to this shape.
|
|
51
|
+
* Returns the zero-based index (IX) of the newly added point.
|
|
52
|
+
*/
|
|
53
|
+
addConnectionPoint(point) {
|
|
54
|
+
return this.modifier.addConnectionPoint(this.pageId, this.id, point);
|
|
55
|
+
}
|
|
49
56
|
async setStyle(style) {
|
|
50
57
|
await this.modifier.updateShapeStyle(this.pageId, this.id, style);
|
|
51
58
|
return this;
|
package/dist/ShapeModifier.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { VisioPackage } from './VisioPackage';
|
|
2
2
|
import { HorzAlign, VertAlign } from './utils/StyleHelpers';
|
|
3
|
-
import { NewShapeProps, ConnectorStyle } from './types/VisioTypes';
|
|
3
|
+
import { NewShapeProps, ConnectorStyle, ConnectionTarget, ConnectionPointDef } from './types/VisioTypes';
|
|
4
4
|
import type { ShapeData, ShapeHyperlink } from './Shape';
|
|
5
5
|
export declare class ShapeModifier {
|
|
6
6
|
private pkg;
|
|
@@ -31,7 +31,12 @@ export declare class ShapeModifier {
|
|
|
31
31
|
private saveParsed;
|
|
32
32
|
private performSave;
|
|
33
33
|
flush(): void;
|
|
34
|
-
addConnector(pageId: string, fromShapeId: string, toShapeId: string, beginArrow?: string, endArrow?: string, style?: ConnectorStyle): Promise<string>;
|
|
34
|
+
addConnector(pageId: string, fromShapeId: string, toShapeId: string, beginArrow?: string, endArrow?: string, style?: ConnectorStyle, fromPort?: ConnectionTarget, toPort?: ConnectionTarget): Promise<string>;
|
|
35
|
+
/**
|
|
36
|
+
* Add a single connection point to an existing shape.
|
|
37
|
+
* Returns the zero-based IX (row index) of the newly added point.
|
|
38
|
+
*/
|
|
39
|
+
addConnectionPoint(pageId: string, shapeId: string, point: ConnectionPointDef): number;
|
|
35
40
|
addShape(pageId: string, props: NewShapeProps, parentId?: string): Promise<string>;
|
|
36
41
|
deleteShape(pageId: string, shapeId: string): Promise<void>;
|
|
37
42
|
private removeShapeFromTree;
|
|
@@ -121,6 +126,12 @@ export interface ShapeStyle {
|
|
|
121
126
|
fillColor?: string;
|
|
122
127
|
fontColor?: string;
|
|
123
128
|
bold?: boolean;
|
|
129
|
+
/** Italic text. */
|
|
130
|
+
italic?: boolean;
|
|
131
|
+
/** Underline text. */
|
|
132
|
+
underline?: boolean;
|
|
133
|
+
/** Strikethrough text. */
|
|
134
|
+
strikethrough?: boolean;
|
|
124
135
|
/** Font size in points (e.g. 14 for 14pt). */
|
|
125
136
|
fontSize?: number;
|
|
126
137
|
/** Font family name (e.g. "Arial"). */
|
|
@@ -129,4 +140,18 @@ export interface ShapeStyle {
|
|
|
129
140
|
horzAlign?: HorzAlign;
|
|
130
141
|
/** Vertical text alignment. */
|
|
131
142
|
verticalAlign?: VertAlign;
|
|
143
|
+
/** Space before each paragraph in **points**. */
|
|
144
|
+
spaceBefore?: number;
|
|
145
|
+
/** Space after each paragraph in **points**. */
|
|
146
|
+
spaceAfter?: number;
|
|
147
|
+
/** Line-height multiplier (1.0 = single, 1.5 = 1.5×, 2.0 = double). */
|
|
148
|
+
lineSpacing?: number;
|
|
149
|
+
/** Top text margin in inches. */
|
|
150
|
+
textMarginTop?: number;
|
|
151
|
+
/** Bottom text margin in inches. */
|
|
152
|
+
textMarginBottom?: number;
|
|
153
|
+
/** Left text margin in inches. */
|
|
154
|
+
textMarginLeft?: number;
|
|
155
|
+
/** Right text margin in inches. */
|
|
156
|
+
textMarginRight?: number;
|
|
132
157
|
}
|
package/dist/ShapeModifier.js
CHANGED
|
@@ -5,6 +5,7 @@ const RelsManager_1 = require("./core/RelsManager");
|
|
|
5
5
|
const StyleHelpers_1 = require("./utils/StyleHelpers");
|
|
6
6
|
const VisioConstants_1 = require("./core/VisioConstants");
|
|
7
7
|
const VisioTypes_1 = require("./types/VisioTypes");
|
|
8
|
+
const ConnectionPointBuilder_1 = require("./shapes/ConnectionPointBuilder");
|
|
8
9
|
const ForeignShapeBuilder_1 = require("./shapes/ForeignShapeBuilder");
|
|
9
10
|
const ShapeBuilder_1 = require("./shapes/ShapeBuilder");
|
|
10
11
|
const ConnectorBuilder_1 = require("./shapes/ConnectorBuilder");
|
|
@@ -184,7 +185,7 @@ class ShapeModifier {
|
|
|
184
185
|
}
|
|
185
186
|
this.dirtyPages.clear();
|
|
186
187
|
}
|
|
187
|
-
async addConnector(pageId, fromShapeId, toShapeId, beginArrow, endArrow, style) {
|
|
188
|
+
async addConnector(pageId, fromShapeId, toShapeId, beginArrow, endArrow, style, fromPort, toPort) {
|
|
188
189
|
const parsed = this.getParsed(pageId);
|
|
189
190
|
// Ensure Shapes collection exists
|
|
190
191
|
if (!parsed.PageContents.Shapes) {
|
|
@@ -204,15 +205,45 @@ class ShapeModifier {
|
|
|
204
205
|
return '0';
|
|
205
206
|
return val;
|
|
206
207
|
};
|
|
207
|
-
const layout = ConnectorBuilder_1.ConnectorBuilder.calculateConnectorLayout(fromShapeId, toShapeId, shapeHierarchy);
|
|
208
|
+
const layout = ConnectorBuilder_1.ConnectorBuilder.calculateConnectorLayout(fromShapeId, toShapeId, shapeHierarchy, fromPort, toPort);
|
|
208
209
|
const connectorShape = ConnectorBuilder_1.ConnectorBuilder.createConnectorShapeObject(newId, layout, validateArrow(beginArrow), validateArrow(endArrow), style);
|
|
209
210
|
const topLevelShapes = parsed.PageContents.Shapes.Shape;
|
|
210
211
|
topLevelShapes.push(connectorShape);
|
|
211
212
|
this.getShapeMap(parsed).set(newId, connectorShape);
|
|
212
|
-
ConnectorBuilder_1.ConnectorBuilder.addConnectorToConnects(parsed, newId, fromShapeId, toShapeId);
|
|
213
|
+
ConnectorBuilder_1.ConnectorBuilder.addConnectorToConnects(parsed, newId, fromShapeId, toShapeId, shapeHierarchy, fromPort, toPort);
|
|
213
214
|
this.saveParsed(pageId, parsed);
|
|
214
215
|
return newId;
|
|
215
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Add a single connection point to an existing shape.
|
|
219
|
+
* Returns the zero-based IX (row index) of the newly added point.
|
|
220
|
+
*/
|
|
221
|
+
addConnectionPoint(pageId, shapeId, point) {
|
|
222
|
+
const parsed = this.getParsed(pageId);
|
|
223
|
+
const shapeMap = this.getShapeMap(parsed);
|
|
224
|
+
const shape = shapeMap.get(shapeId);
|
|
225
|
+
if (!shape)
|
|
226
|
+
throw new Error(`Shape ${shapeId} not found on page ${pageId}`);
|
|
227
|
+
// Ensure Section array exists
|
|
228
|
+
if (!shape.Section)
|
|
229
|
+
shape.Section = [];
|
|
230
|
+
if (!Array.isArray(shape.Section))
|
|
231
|
+
shape.Section = [shape.Section];
|
|
232
|
+
// Find or create Connection section
|
|
233
|
+
let connSection = shape.Section.find((s) => s['@_N'] === 'Connection');
|
|
234
|
+
if (!connSection) {
|
|
235
|
+
connSection = { '@_N': 'Connection', Row: [] };
|
|
236
|
+
shape.Section.push(connSection);
|
|
237
|
+
}
|
|
238
|
+
if (!connSection.Row)
|
|
239
|
+
connSection.Row = [];
|
|
240
|
+
if (!Array.isArray(connSection.Row))
|
|
241
|
+
connSection.Row = [connSection.Row];
|
|
242
|
+
const ix = connSection.Row.length;
|
|
243
|
+
connSection.Row.push(ConnectionPointBuilder_1.ConnectionPointBuilder.buildRow(point, ix));
|
|
244
|
+
this.saveParsed(pageId, parsed);
|
|
245
|
+
return ix;
|
|
246
|
+
}
|
|
216
247
|
async addShape(pageId, props, parentId) {
|
|
217
248
|
const parsed = this.getParsed(pageId);
|
|
218
249
|
// Ensure Shapes container exists
|
|
@@ -349,19 +380,52 @@ class ShapeModifier {
|
|
|
349
380
|
shape.Section.push((0, StyleHelpers_1.createFillSection)(style.fillColor));
|
|
350
381
|
}
|
|
351
382
|
// Update/Add Character (Font/Text Style)
|
|
352
|
-
|
|
383
|
+
const hasCharProps = style.fontColor !== undefined
|
|
384
|
+
|| style.bold !== undefined
|
|
385
|
+
|| style.italic !== undefined
|
|
386
|
+
|| style.underline !== undefined
|
|
387
|
+
|| style.strikethrough !== undefined
|
|
388
|
+
|| style.fontSize !== undefined
|
|
389
|
+
|| style.fontFamily !== undefined;
|
|
390
|
+
if (hasCharProps) {
|
|
353
391
|
shape.Section = shape.Section.filter((s) => s['@_N'] !== 'Character');
|
|
354
392
|
shape.Section.push((0, StyleHelpers_1.createCharacterSection)({
|
|
355
393
|
bold: style.bold,
|
|
394
|
+
italic: style.italic,
|
|
395
|
+
underline: style.underline,
|
|
396
|
+
strikethrough: style.strikethrough,
|
|
356
397
|
color: style.fontColor,
|
|
357
398
|
fontSize: style.fontSize,
|
|
358
399
|
fontFamily: style.fontFamily,
|
|
359
400
|
}));
|
|
360
401
|
}
|
|
361
|
-
// Update/Add Paragraph
|
|
362
|
-
|
|
402
|
+
// Update/Add Paragraph
|
|
403
|
+
const hasParagraphProps = style.horzAlign !== undefined
|
|
404
|
+
|| style.spaceBefore !== undefined
|
|
405
|
+
|| style.spaceAfter !== undefined
|
|
406
|
+
|| style.lineSpacing !== undefined;
|
|
407
|
+
if (hasParagraphProps) {
|
|
363
408
|
shape.Section = shape.Section.filter((s) => s['@_N'] !== 'Paragraph');
|
|
364
|
-
shape.Section.push((0, StyleHelpers_1.createParagraphSection)(
|
|
409
|
+
shape.Section.push((0, StyleHelpers_1.createParagraphSection)({
|
|
410
|
+
horzAlign: style.horzAlign,
|
|
411
|
+
spaceBefore: style.spaceBefore,
|
|
412
|
+
spaceAfter: style.spaceAfter,
|
|
413
|
+
lineSpacing: style.lineSpacing,
|
|
414
|
+
}));
|
|
415
|
+
}
|
|
416
|
+
// Update/Add TextBlock (text margins)
|
|
417
|
+
const hasTextBlockProps = style.textMarginTop !== undefined
|
|
418
|
+
|| style.textMarginBottom !== undefined
|
|
419
|
+
|| style.textMarginLeft !== undefined
|
|
420
|
+
|| style.textMarginRight !== undefined;
|
|
421
|
+
if (hasTextBlockProps) {
|
|
422
|
+
shape.Section = shape.Section.filter((s) => s['@_N'] !== 'TextBlock');
|
|
423
|
+
shape.Section.push((0, StyleHelpers_1.createTextBlockSection)({
|
|
424
|
+
topMargin: style.textMarginTop,
|
|
425
|
+
bottomMargin: style.textMarginBottom,
|
|
426
|
+
leftMargin: style.textMarginLeft,
|
|
427
|
+
rightMargin: style.textMarginRight,
|
|
428
|
+
}));
|
|
365
429
|
}
|
|
366
430
|
// Update/Add VerticalAlign (top-level shape Cell)
|
|
367
431
|
if (style.verticalAlign !== undefined) {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ConnectionPointDef, ConnectionTarget } from '../types/VisioTypes';
|
|
2
|
+
/**
|
|
3
|
+
* Builds and resolves Visio Connection section XML structures.
|
|
4
|
+
*
|
|
5
|
+
* Connection points in Visio are stored as rows in a `<Section N="Connection">` element.
|
|
6
|
+
* Each row has an IX attribute (0-based) and an optional N (name) attribute.
|
|
7
|
+
* X and Y cells store fractions via formula: `Width*{xFraction}` / `Height*{yFraction}`.
|
|
8
|
+
*
|
|
9
|
+
* When connecting to a named point, the Connect element uses:
|
|
10
|
+
* ToPart = 100 + IX (instead of 3 for shape centre)
|
|
11
|
+
* ToCell = "Connections.X{IX+1}" (1-based)
|
|
12
|
+
*/
|
|
13
|
+
export declare class ConnectionPointBuilder {
|
|
14
|
+
/** Build the raw XML object for a Connection section from a list of point definitions. */
|
|
15
|
+
static buildConnectionSection(points: ConnectionPointDef[]): any;
|
|
16
|
+
/** Build a single Connection row XML object. */
|
|
17
|
+
static buildRow(point: ConnectionPointDef, ix: number): any;
|
|
18
|
+
/**
|
|
19
|
+
* Resolve a `ConnectionTarget` against a raw shape XML object.
|
|
20
|
+
*
|
|
21
|
+
* Returns the `ToPart` / `ToCell` strings for the Connect element, plus the
|
|
22
|
+
* fractional X/Y position of the connection point (undefined when targeting
|
|
23
|
+
* the shape centre, which falls back to edge-intersection logic).
|
|
24
|
+
*/
|
|
25
|
+
static resolveTarget(target: ConnectionTarget, shape: any): {
|
|
26
|
+
toPart: string;
|
|
27
|
+
toCell: string;
|
|
28
|
+
xFraction?: number;
|
|
29
|
+
yFraction?: number;
|
|
30
|
+
};
|
|
31
|
+
private static getConnectionSection;
|
|
32
|
+
private static getRows;
|
|
33
|
+
private static findIxByName;
|
|
34
|
+
private static getPointByIx;
|
|
35
|
+
/**
|
|
36
|
+
* Extract the numeric multiplier from a formula of the form `Width*0.5` or `Height*1`.
|
|
37
|
+
* Returns `undefined` if the formula does not match that pattern.
|
|
38
|
+
*/
|
|
39
|
+
private static parseFraction;
|
|
40
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConnectionPointBuilder = void 0;
|
|
4
|
+
const TYPE_VALUES = {
|
|
5
|
+
inward: '0',
|
|
6
|
+
outward: '1',
|
|
7
|
+
both: '2',
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Builds and resolves Visio Connection section XML structures.
|
|
11
|
+
*
|
|
12
|
+
* Connection points in Visio are stored as rows in a `<Section N="Connection">` element.
|
|
13
|
+
* Each row has an IX attribute (0-based) and an optional N (name) attribute.
|
|
14
|
+
* X and Y cells store fractions via formula: `Width*{xFraction}` / `Height*{yFraction}`.
|
|
15
|
+
*
|
|
16
|
+
* When connecting to a named point, the Connect element uses:
|
|
17
|
+
* ToPart = 100 + IX (instead of 3 for shape centre)
|
|
18
|
+
* ToCell = "Connections.X{IX+1}" (1-based)
|
|
19
|
+
*/
|
|
20
|
+
class ConnectionPointBuilder {
|
|
21
|
+
/** Build the raw XML object for a Connection section from a list of point definitions. */
|
|
22
|
+
static buildConnectionSection(points) {
|
|
23
|
+
return {
|
|
24
|
+
'@_N': 'Connection',
|
|
25
|
+
Row: points.map((pt, ix) => this.buildRow(pt, ix)),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Build a single Connection row XML object. */
|
|
29
|
+
static buildRow(point, ix) {
|
|
30
|
+
const row = {
|
|
31
|
+
'@_IX': ix.toString(),
|
|
32
|
+
Cell: [
|
|
33
|
+
{ '@_N': 'X', '@_V': '0', '@_F': `Width*${point.xFraction}` },
|
|
34
|
+
{ '@_N': 'Y', '@_V': '0', '@_F': `Height*${point.yFraction}` },
|
|
35
|
+
{ '@_N': 'DirX', '@_V': point.direction ? point.direction.x.toString() : '0' },
|
|
36
|
+
{ '@_N': 'DirY', '@_V': point.direction ? point.direction.y.toString() : '0' },
|
|
37
|
+
{ '@_N': 'Type', '@_V': point.type ? TYPE_VALUES[point.type] : '0' },
|
|
38
|
+
{ '@_N': 'AutoGen', '@_V': '0' },
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
if (point.name)
|
|
42
|
+
row['@_N'] = point.name;
|
|
43
|
+
if (point.prompt)
|
|
44
|
+
row.Cell.push({ '@_N': 'Prompt', '@_V': point.prompt });
|
|
45
|
+
return row;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Resolve a `ConnectionTarget` against a raw shape XML object.
|
|
49
|
+
*
|
|
50
|
+
* Returns the `ToPart` / `ToCell` strings for the Connect element, plus the
|
|
51
|
+
* fractional X/Y position of the connection point (undefined when targeting
|
|
52
|
+
* the shape centre, which falls back to edge-intersection logic).
|
|
53
|
+
*/
|
|
54
|
+
static resolveTarget(target, shape) {
|
|
55
|
+
if (target === 'center') {
|
|
56
|
+
return { toPart: '3', toCell: 'PinX' };
|
|
57
|
+
}
|
|
58
|
+
if ('index' in target) {
|
|
59
|
+
const ix = target.index;
|
|
60
|
+
const pt = this.getPointByIx(shape, ix);
|
|
61
|
+
return {
|
|
62
|
+
toPart: (100 + ix).toString(),
|
|
63
|
+
toCell: `Connections.X${ix + 1}`,
|
|
64
|
+
xFraction: pt?.xFraction,
|
|
65
|
+
yFraction: pt?.yFraction,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// name lookup
|
|
69
|
+
const ix = this.findIxByName(shape, target.name);
|
|
70
|
+
if (ix === -1) {
|
|
71
|
+
// Named point not found — fall back to centre
|
|
72
|
+
return { toPart: '3', toCell: 'PinX' };
|
|
73
|
+
}
|
|
74
|
+
const pt = this.getPointByIx(shape, ix);
|
|
75
|
+
return {
|
|
76
|
+
toPart: (100 + ix).toString(),
|
|
77
|
+
toCell: `Connections.X${ix + 1}`,
|
|
78
|
+
xFraction: pt?.xFraction,
|
|
79
|
+
yFraction: pt?.yFraction,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
// ── private helpers ────────────────────────────────────────────────────────
|
|
83
|
+
static getConnectionSection(shape) {
|
|
84
|
+
const sections = shape?.Section;
|
|
85
|
+
if (!sections)
|
|
86
|
+
return null;
|
|
87
|
+
const arr = Array.isArray(sections) ? sections : [sections];
|
|
88
|
+
return arr.find((s) => s['@_N'] === 'Connection') ?? null;
|
|
89
|
+
}
|
|
90
|
+
static getRows(section) {
|
|
91
|
+
if (!section?.Row)
|
|
92
|
+
return [];
|
|
93
|
+
return Array.isArray(section.Row) ? section.Row : [section.Row];
|
|
94
|
+
}
|
|
95
|
+
static findIxByName(shape, name) {
|
|
96
|
+
const section = this.getConnectionSection(shape);
|
|
97
|
+
if (!section)
|
|
98
|
+
return -1;
|
|
99
|
+
const row = this.getRows(section).find((r) => r['@_N'] === name);
|
|
100
|
+
if (!row)
|
|
101
|
+
return -1;
|
|
102
|
+
return parseInt(row['@_IX'], 10);
|
|
103
|
+
}
|
|
104
|
+
static getPointByIx(shape, ix) {
|
|
105
|
+
const section = this.getConnectionSection(shape);
|
|
106
|
+
if (!section)
|
|
107
|
+
return undefined;
|
|
108
|
+
const row = this.getRows(section).find((r) => parseInt(r['@_IX'], 10) === ix);
|
|
109
|
+
if (!row)
|
|
110
|
+
return undefined;
|
|
111
|
+
const cells = Array.isArray(row.Cell) ? row.Cell : row.Cell ? [row.Cell] : [];
|
|
112
|
+
const xCell = cells.find((c) => c['@_N'] === 'X');
|
|
113
|
+
const yCell = cells.find((c) => c['@_N'] === 'Y');
|
|
114
|
+
// Prefer formula parsing (works for both freshly-created and loaded shapes).
|
|
115
|
+
// Fall back to @_V if no formula is present.
|
|
116
|
+
const xFraction = this.parseFraction(xCell?.['@_F']) ?? parseFloat(xCell?.['@_V'] ?? '0');
|
|
117
|
+
const yFraction = this.parseFraction(yCell?.['@_F']) ?? parseFloat(yCell?.['@_V'] ?? '0');
|
|
118
|
+
return { xFraction, yFraction };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Extract the numeric multiplier from a formula of the form `Width*0.5` or `Height*1`.
|
|
122
|
+
* Returns `undefined` if the formula does not match that pattern.
|
|
123
|
+
*/
|
|
124
|
+
static parseFraction(formula) {
|
|
125
|
+
if (!formula)
|
|
126
|
+
return undefined;
|
|
127
|
+
const m = formula.match(/^(?:Width|Height)\*([0-9.]+)$/);
|
|
128
|
+
return m ? parseFloat(m[1]) : undefined;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
exports.ConnectionPointBuilder = ConnectionPointBuilder;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConnectorStyle } from '../types/VisioTypes';
|
|
1
|
+
import { ConnectorStyle, ConnectionTarget } from '../types/VisioTypes';
|
|
2
2
|
export declare class ConnectorBuilder {
|
|
3
3
|
private static getCellVal;
|
|
4
4
|
private static getAbsolutePos;
|
|
@@ -10,7 +10,7 @@ export declare class ConnectorBuilder {
|
|
|
10
10
|
static calculateConnectorLayout(fromShapeId: string, toShapeId: string, shapeHierarchy: Map<string, {
|
|
11
11
|
shape: any;
|
|
12
12
|
parent: any;
|
|
13
|
-
}
|
|
13
|
+
}>, fromPort?: ConnectionTarget, toPort?: ConnectionTarget): {
|
|
14
14
|
beginX: number;
|
|
15
15
|
beginY: number;
|
|
16
16
|
endX: number;
|
|
@@ -18,6 +18,12 @@ export declare class ConnectorBuilder {
|
|
|
18
18
|
width: number;
|
|
19
19
|
angle: number;
|
|
20
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* Resolve a ConnectionTarget to an absolute page position using the shape hierarchy.
|
|
23
|
+
* Returns null when the target is 'center' or the named/indexed point is not found,
|
|
24
|
+
* signalling the caller to fall back to edge-intersection logic.
|
|
25
|
+
*/
|
|
26
|
+
private static resolveConnectionPointPos;
|
|
21
27
|
static createConnectorShapeObject(id: string, layout: any, beginArrow?: string, endArrow?: string, style?: ConnectorStyle): {
|
|
22
28
|
'@_ID': string;
|
|
23
29
|
'@_NameU': string;
|
|
@@ -34,5 +40,8 @@ export declare class ConnectorBuilder {
|
|
|
34
40
|
})[];
|
|
35
41
|
Section: import("../utils/StyleHelpers").VisioSection[];
|
|
36
42
|
};
|
|
37
|
-
static addConnectorToConnects(parsed: any, connectorId: string, fromShapeId: string, toShapeId: string
|
|
43
|
+
static addConnectorToConnects(parsed: any, connectorId: string, fromShapeId: string, toShapeId: string, shapeHierarchy?: Map<string, {
|
|
44
|
+
shape: any;
|
|
45
|
+
parent: any;
|
|
46
|
+
}>, fromPort?: ConnectionTarget, toPort?: ConnectionTarget): void;
|
|
38
47
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ConnectorBuilder = void 0;
|
|
4
4
|
const StyleHelpers_1 = require("../utils/StyleHelpers");
|
|
5
|
+
const ConnectionPointBuilder_1 = require("./ConnectionPointBuilder");
|
|
5
6
|
const ROUTING_VALUES = {
|
|
6
7
|
straight: '2',
|
|
7
8
|
orthogonal: '1',
|
|
@@ -65,7 +66,7 @@ class ConnectorBuilder {
|
|
|
65
66
|
mapHierarchy(topShapes, null);
|
|
66
67
|
return shapeHierarchy;
|
|
67
68
|
}
|
|
68
|
-
static calculateConnectorLayout(fromShapeId, toShapeId, shapeHierarchy) {
|
|
69
|
+
static calculateConnectorLayout(fromShapeId, toShapeId, shapeHierarchy, fromPort, toPort) {
|
|
69
70
|
let beginX = 0, beginY = 0, endX = 0, endY = 0;
|
|
70
71
|
let sourceGeom = null;
|
|
71
72
|
let targetGeom = null;
|
|
@@ -88,12 +89,32 @@ class ConnectorBuilder {
|
|
|
88
89
|
endY = abs.y;
|
|
89
90
|
}
|
|
90
91
|
if (sourceGeom && targetGeom) {
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
// Compute begin (from-side) endpoint
|
|
93
|
+
const beginPt = fromPort && fromPort !== 'center'
|
|
94
|
+
? this.resolveConnectionPointPos(fromShapeId, fromPort, shapeHierarchy, sourceGeom)
|
|
95
|
+
: null;
|
|
96
|
+
if (beginPt) {
|
|
97
|
+
beginX = beginPt.x;
|
|
98
|
+
beginY = beginPt.y;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
const sp = this.getEdgePoint(sourceGeom.x, sourceGeom.y, sourceGeom.w, sourceGeom.h, targetGeom.x, targetGeom.y);
|
|
102
|
+
beginX = sp.x;
|
|
103
|
+
beginY = sp.y;
|
|
104
|
+
}
|
|
105
|
+
// Compute end (to-side) endpoint
|
|
106
|
+
const endPt = toPort && toPort !== 'center'
|
|
107
|
+
? this.resolveConnectionPointPos(toShapeId, toPort, shapeHierarchy, targetGeom)
|
|
108
|
+
: null;
|
|
109
|
+
if (endPt) {
|
|
110
|
+
endX = endPt.x;
|
|
111
|
+
endY = endPt.y;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const ep = this.getEdgePoint(targetGeom.x, targetGeom.y, targetGeom.w, targetGeom.h, sourceGeom.x, sourceGeom.y);
|
|
115
|
+
endX = ep.x;
|
|
116
|
+
endY = ep.y;
|
|
117
|
+
}
|
|
97
118
|
}
|
|
98
119
|
const dx = endX - beginX;
|
|
99
120
|
const dy = endY - beginY;
|
|
@@ -101,6 +122,26 @@ class ConnectorBuilder {
|
|
|
101
122
|
const angle = Math.atan2(dy, dx);
|
|
102
123
|
return { beginX, beginY, endX, endY, width, angle };
|
|
103
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Resolve a ConnectionTarget to an absolute page position using the shape hierarchy.
|
|
127
|
+
* Returns null when the target is 'center' or the named/indexed point is not found,
|
|
128
|
+
* signalling the caller to fall back to edge-intersection logic.
|
|
129
|
+
*/
|
|
130
|
+
static resolveConnectionPointPos(shapeId, target, shapeHierarchy, geom) {
|
|
131
|
+
const entry = shapeHierarchy.get(shapeId);
|
|
132
|
+
if (!entry)
|
|
133
|
+
return null;
|
|
134
|
+
const resolved = ConnectionPointBuilder_1.ConnectionPointBuilder.resolveTarget(target, entry.shape);
|
|
135
|
+
if (resolved.xFraction === undefined || resolved.yFraction === undefined)
|
|
136
|
+
return null;
|
|
137
|
+
const locPinX = parseFloat(this.getCellVal(entry.shape, 'LocPinX'));
|
|
138
|
+
const locPinY = parseFloat(this.getCellVal(entry.shape, 'LocPinY'));
|
|
139
|
+
const abs = this.getAbsolutePos(shapeId, shapeHierarchy);
|
|
140
|
+
return {
|
|
141
|
+
x: (abs.x - locPinX) + geom.w * resolved.xFraction,
|
|
142
|
+
y: (abs.y - locPinY) + geom.h * resolved.yFraction,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
104
145
|
static createConnectorShapeObject(id, layout, beginArrow, endArrow, style) {
|
|
105
146
|
const { beginX, beginY, endX, endY, width, angle } = layout;
|
|
106
147
|
const routeStyle = style?.routing ? (ROUTING_VALUES[style.routing] ?? '1') : '1';
|
|
@@ -151,7 +192,7 @@ class ConnectorBuilder {
|
|
|
151
192
|
]
|
|
152
193
|
};
|
|
153
194
|
}
|
|
154
|
-
static addConnectorToConnects(parsed, connectorId, fromShapeId, toShapeId) {
|
|
195
|
+
static addConnectorToConnects(parsed, connectorId, fromShapeId, toShapeId, shapeHierarchy, fromPort, toPort) {
|
|
155
196
|
if (!parsed.PageContents.Connects) {
|
|
156
197
|
parsed.PageContents.Connects = { Connect: [] };
|
|
157
198
|
}
|
|
@@ -161,21 +202,43 @@ class ConnectorBuilder {
|
|
|
161
202
|
connectCollection = connectCollection ? [connectCollection] : [];
|
|
162
203
|
parsed.PageContents.Connects.Connect = connectCollection;
|
|
163
204
|
}
|
|
205
|
+
// Resolve from-side ToPart/ToCell
|
|
206
|
+
let fromToCell = 'PinX';
|
|
207
|
+
let fromToPart = '3';
|
|
208
|
+
if (fromPort && fromPort !== 'center' && shapeHierarchy) {
|
|
209
|
+
const fromEntry = shapeHierarchy.get(fromShapeId);
|
|
210
|
+
if (fromEntry) {
|
|
211
|
+
const r = ConnectionPointBuilder_1.ConnectionPointBuilder.resolveTarget(fromPort, fromEntry.shape);
|
|
212
|
+
fromToCell = r.toCell;
|
|
213
|
+
fromToPart = r.toPart;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Resolve to-side ToPart/ToCell
|
|
217
|
+
let toToCell = 'PinX';
|
|
218
|
+
let toToPart = '3';
|
|
219
|
+
if (toPort && toPort !== 'center' && shapeHierarchy) {
|
|
220
|
+
const toEntry = shapeHierarchy.get(toShapeId);
|
|
221
|
+
if (toEntry) {
|
|
222
|
+
const r = ConnectionPointBuilder_1.ConnectionPointBuilder.resolveTarget(toPort, toEntry.shape);
|
|
223
|
+
toToCell = r.toCell;
|
|
224
|
+
toToPart = r.toPart;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
164
227
|
connectCollection.push({
|
|
165
228
|
'@_FromSheet': connectorId,
|
|
166
229
|
'@_FromCell': 'BeginX',
|
|
167
230
|
'@_FromPart': '9',
|
|
168
231
|
'@_ToSheet': fromShapeId,
|
|
169
|
-
'@_ToCell':
|
|
170
|
-
'@_ToPart':
|
|
232
|
+
'@_ToCell': fromToCell,
|
|
233
|
+
'@_ToPart': fromToPart,
|
|
171
234
|
});
|
|
172
235
|
connectCollection.push({
|
|
173
236
|
'@_FromSheet': connectorId,
|
|
174
237
|
'@_FromCell': 'EndX',
|
|
175
238
|
'@_FromPart': '12',
|
|
176
239
|
'@_ToSheet': toShapeId,
|
|
177
|
-
'@_ToCell':
|
|
178
|
-
'@_ToPart':
|
|
240
|
+
'@_ToCell': toToCell,
|
|
241
|
+
'@_ToPart': toToPart,
|
|
179
242
|
});
|
|
180
243
|
}
|
|
181
244
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ShapeBuilder = void 0;
|
|
4
4
|
const StyleHelpers_1 = require("../utils/StyleHelpers");
|
|
5
5
|
const GeometryBuilder_1 = require("./GeometryBuilder");
|
|
6
|
+
const ConnectionPointBuilder_1 = require("./ConnectionPointBuilder");
|
|
6
7
|
class ShapeBuilder {
|
|
7
8
|
static createStandardShape(id, props) {
|
|
8
9
|
// Validate dimensions
|
|
@@ -37,20 +38,55 @@ class ShapeBuilder {
|
|
|
37
38
|
weight: '0.01'
|
|
38
39
|
}));
|
|
39
40
|
}
|
|
40
|
-
|
|
41
|
+
const hasCharProps = props.fontColor !== undefined
|
|
42
|
+
|| props.bold !== undefined
|
|
43
|
+
|| props.italic !== undefined
|
|
44
|
+
|| props.underline !== undefined
|
|
45
|
+
|| props.strikethrough !== undefined
|
|
46
|
+
|| props.fontSize !== undefined
|
|
47
|
+
|| props.fontFamily !== undefined;
|
|
48
|
+
if (hasCharProps) {
|
|
41
49
|
shape.Section.push((0, StyleHelpers_1.createCharacterSection)({
|
|
42
50
|
bold: props.bold,
|
|
51
|
+
italic: props.italic,
|
|
52
|
+
underline: props.underline,
|
|
53
|
+
strikethrough: props.strikethrough,
|
|
43
54
|
color: props.fontColor,
|
|
44
55
|
fontSize: props.fontSize,
|
|
45
56
|
fontFamily: props.fontFamily,
|
|
46
57
|
}));
|
|
47
58
|
}
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
const hasParagraphProps = props.horzAlign !== undefined
|
|
60
|
+
|| props.spaceBefore !== undefined
|
|
61
|
+
|| props.spaceAfter !== undefined
|
|
62
|
+
|| props.lineSpacing !== undefined;
|
|
63
|
+
if (hasParagraphProps) {
|
|
64
|
+
shape.Section.push((0, StyleHelpers_1.createParagraphSection)({
|
|
65
|
+
horzAlign: props.horzAlign,
|
|
66
|
+
spaceBefore: props.spaceBefore,
|
|
67
|
+
spaceAfter: props.spaceAfter,
|
|
68
|
+
lineSpacing: props.lineSpacing,
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
const hasTextBlockProps = props.textMarginTop !== undefined
|
|
72
|
+
|| props.textMarginBottom !== undefined
|
|
73
|
+
|| props.textMarginLeft !== undefined
|
|
74
|
+
|| props.textMarginRight !== undefined;
|
|
75
|
+
if (hasTextBlockProps) {
|
|
76
|
+
shape.Section.push((0, StyleHelpers_1.createTextBlockSection)({
|
|
77
|
+
topMargin: props.textMarginTop,
|
|
78
|
+
bottomMargin: props.textMarginBottom,
|
|
79
|
+
leftMargin: props.textMarginLeft,
|
|
80
|
+
rightMargin: props.textMarginRight,
|
|
81
|
+
}));
|
|
50
82
|
}
|
|
51
83
|
if (props.verticalAlign !== undefined) {
|
|
52
84
|
shape.Cell.push({ '@_N': 'VerticalAlign', '@_V': (0, StyleHelpers_1.vertAlignValue)(props.verticalAlign) });
|
|
53
85
|
}
|
|
86
|
+
// Connection points
|
|
87
|
+
if (props.connectionPoints && props.connectionPoints.length > 0) {
|
|
88
|
+
shape.Section.push(ConnectionPointBuilder_1.ConnectionPointBuilder.buildConnectionSection(props.connectionPoints));
|
|
89
|
+
}
|
|
54
90
|
// Add Geometry
|
|
55
91
|
// Only if NOT a Group AND NOT a Master Instance
|
|
56
92
|
if (props.type !== 'Group' && !props.masterId) {
|
|
@@ -146,6 +146,48 @@ 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
|
+
/** Whether a connection point accepts incoming glue, outgoing glue, or both. */
|
|
150
|
+
export type ConnectionPointType = 'inward' | 'outward' | 'both';
|
|
151
|
+
/**
|
|
152
|
+
* Definition of a single connection point on a shape.
|
|
153
|
+
* Positions are expressed as fractions of the shape's width/height
|
|
154
|
+
* (0.0 = left/bottom, 1.0 = right/top in Visio's coordinate system).
|
|
155
|
+
*/
|
|
156
|
+
export interface ConnectionPointDef {
|
|
157
|
+
/** Optional display name (e.g. `'Top'`, `'Right'`). Referenced when connecting by name. */
|
|
158
|
+
name?: string;
|
|
159
|
+
/** Horizontal position as a fraction of shape width. 0 = left edge, 1 = right edge. */
|
|
160
|
+
xFraction: number;
|
|
161
|
+
/** Vertical position as a fraction of shape height. 0 = bottom edge, 1 = top edge. */
|
|
162
|
+
yFraction: number;
|
|
163
|
+
/** Glue direction vector. Defaults to `{ x: 0, y: 0 }` (no preferred direction). */
|
|
164
|
+
direction?: {
|
|
165
|
+
x: number;
|
|
166
|
+
y: number;
|
|
167
|
+
};
|
|
168
|
+
/** Connection point type. Defaults to `'inward'`. */
|
|
169
|
+
type?: ConnectionPointType;
|
|
170
|
+
/** Optional tooltip shown in the Visio UI. */
|
|
171
|
+
prompt?: string;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Specifies which connection point to use when attaching a connector endpoint.
|
|
175
|
+
* - `'center'` → shape centre (default behaviour, `ToPart=3`)
|
|
176
|
+
* - `{ name }` → named connection point (e.g. `{ name: 'Top' }`)
|
|
177
|
+
* - `{ index }` → connection point by zero-based row index
|
|
178
|
+
*/
|
|
179
|
+
export type ConnectionTarget = 'center' | {
|
|
180
|
+
name: string;
|
|
181
|
+
} | {
|
|
182
|
+
index: number;
|
|
183
|
+
};
|
|
184
|
+
/** Ready-made connection-point presets. */
|
|
185
|
+
export declare const StandardConnectionPoints: {
|
|
186
|
+
/** Four cardinal points: Top, Right, Bottom, Left. */
|
|
187
|
+
cardinal: ConnectionPointDef[];
|
|
188
|
+
/** Eight points: four cardinal + four corners. */
|
|
189
|
+
full: ConnectionPointDef[];
|
|
190
|
+
};
|
|
149
191
|
export interface NewShapeProps {
|
|
150
192
|
text: string;
|
|
151
193
|
x: number;
|
|
@@ -172,4 +214,30 @@ export interface NewShapeProps {
|
|
|
172
214
|
masterId?: string;
|
|
173
215
|
imgRelId?: string;
|
|
174
216
|
lineColor?: string;
|
|
217
|
+
/** Italic text. */
|
|
218
|
+
italic?: boolean;
|
|
219
|
+
/** Underline text. */
|
|
220
|
+
underline?: boolean;
|
|
221
|
+
/** Strikethrough text. */
|
|
222
|
+
strikethrough?: boolean;
|
|
223
|
+
/** Space before each paragraph in **points**. */
|
|
224
|
+
spaceBefore?: number;
|
|
225
|
+
/** Space after each paragraph in **points**. */
|
|
226
|
+
spaceAfter?: number;
|
|
227
|
+
/** Line-height multiplier (1.0 = single, 1.5 = 1.5×, 2.0 = double). */
|
|
228
|
+
lineSpacing?: number;
|
|
229
|
+
/** Top text margin in inches. */
|
|
230
|
+
textMarginTop?: number;
|
|
231
|
+
/** Bottom text margin in inches. */
|
|
232
|
+
textMarginBottom?: number;
|
|
233
|
+
/** Left text margin in inches. */
|
|
234
|
+
textMarginLeft?: number;
|
|
235
|
+
/** Right text margin in inches. */
|
|
236
|
+
textMarginRight?: number;
|
|
237
|
+
/**
|
|
238
|
+
* Connection points to add to the shape.
|
|
239
|
+
* Use `StandardConnectionPoints.cardinal` for the four cardinal points,
|
|
240
|
+
* or `StandardConnectionPoints.full` for eight points.
|
|
241
|
+
*/
|
|
242
|
+
connectionPoints?: ConnectionPointDef[];
|
|
175
243
|
}
|
package/dist/types/VisioTypes.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PageSizes = exports.VisioPropType = void 0;
|
|
3
|
+
exports.StandardConnectionPoints = exports.PageSizes = exports.VisioPropType = void 0;
|
|
4
4
|
var VisioPropType;
|
|
5
5
|
(function (VisioPropType) {
|
|
6
6
|
VisioPropType[VisioPropType["String"] = 0] = "String";
|
|
@@ -21,3 +21,22 @@ exports.PageSizes = {
|
|
|
21
21
|
A4: { width: 8.268, height: 11.693 },
|
|
22
22
|
A5: { width: 5.827, height: 8.268 },
|
|
23
23
|
};
|
|
24
|
+
/** Ready-made connection-point presets. */
|
|
25
|
+
exports.StandardConnectionPoints = {
|
|
26
|
+
cardinal: [
|
|
27
|
+
{ name: 'Top', xFraction: 0.5, yFraction: 1.0, direction: { x: 0, y: 1 } },
|
|
28
|
+
{ name: 'Right', xFraction: 1.0, yFraction: 0.5, direction: { x: 1, y: 0 } },
|
|
29
|
+
{ name: 'Bottom', xFraction: 0.5, yFraction: 0.0, direction: { x: 0, y: -1 } },
|
|
30
|
+
{ name: 'Left', xFraction: 0.0, yFraction: 0.5, direction: { x: -1, y: 0 } },
|
|
31
|
+
],
|
|
32
|
+
full: [
|
|
33
|
+
{ name: 'Top', xFraction: 0.5, yFraction: 1.0, direction: { x: 0, y: 1 } },
|
|
34
|
+
{ name: 'Right', xFraction: 1.0, yFraction: 0.5, direction: { x: 1, y: 0 } },
|
|
35
|
+
{ name: 'Bottom', xFraction: 0.5, yFraction: 0.0, direction: { x: 0, y: -1 } },
|
|
36
|
+
{ name: 'Left', xFraction: 0.0, yFraction: 0.5, direction: { x: -1, y: 0 } },
|
|
37
|
+
{ name: 'TopLeft', xFraction: 0.0, yFraction: 1.0, direction: { x: -1, y: 1 } },
|
|
38
|
+
{ name: 'TopRight', xFraction: 1.0, yFraction: 1.0, direction: { x: 1, y: 1 } },
|
|
39
|
+
{ name: 'BottomRight', xFraction: 1.0, yFraction: 0.0, direction: { x: 1, y: -1 } },
|
|
40
|
+
{ name: 'BottomLeft', xFraction: 0.0, yFraction: 0.0, direction: { x: -1, y: -1 } },
|
|
41
|
+
],
|
|
42
|
+
};
|
|
@@ -32,13 +32,50 @@ export declare function horzAlignValue(align: HorzAlign): string;
|
|
|
32
32
|
export declare function vertAlignValue(align: VertAlign): string;
|
|
33
33
|
export declare function createCharacterSection(props: {
|
|
34
34
|
bold?: boolean;
|
|
35
|
+
/** Italic text (Style bit 2). */
|
|
36
|
+
italic?: boolean;
|
|
37
|
+
/** Underline text (Style bit 4). */
|
|
38
|
+
underline?: boolean;
|
|
39
|
+
/** Strikethrough text (Style bit 8). */
|
|
40
|
+
strikethrough?: boolean;
|
|
35
41
|
color?: string;
|
|
36
42
|
/** Font size in points (e.g. 12 for 12pt). Stored internally as inches (pt / 72). */
|
|
37
43
|
fontSize?: number;
|
|
38
44
|
/** Font family name (e.g. "Arial"). Uses FONT() formula for portability. */
|
|
39
45
|
fontFamily?: string;
|
|
40
46
|
}): VisioSection;
|
|
41
|
-
export
|
|
47
|
+
export interface ParagraphProps {
|
|
48
|
+
/** Horizontal text alignment within the paragraph. */
|
|
49
|
+
horzAlign?: HorzAlign;
|
|
50
|
+
/**
|
|
51
|
+
* Space before each paragraph in **points**.
|
|
52
|
+
* Converted to inches internally (pt / 72).
|
|
53
|
+
*/
|
|
54
|
+
spaceBefore?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Space after each paragraph in **points**.
|
|
57
|
+
* Converted to inches internally (pt / 72).
|
|
58
|
+
*/
|
|
59
|
+
spaceAfter?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Line-height multiplier (1.0 = single, 1.5 = 1.5×, 2.0 = double).
|
|
62
|
+
* Stored as a negative value in Visio's `SpLine` cell (negative means
|
|
63
|
+
* proportional; positive means absolute in inches).
|
|
64
|
+
*/
|
|
65
|
+
lineSpacing?: number;
|
|
66
|
+
}
|
|
67
|
+
export declare function createParagraphSection(props: ParagraphProps): VisioSection;
|
|
68
|
+
export interface TextBlockProps {
|
|
69
|
+
/** Top text margin in inches. */
|
|
70
|
+
topMargin?: number;
|
|
71
|
+
/** Bottom text margin in inches. */
|
|
72
|
+
bottomMargin?: number;
|
|
73
|
+
/** Left text margin in inches. */
|
|
74
|
+
leftMargin?: number;
|
|
75
|
+
/** Right text margin in inches. */
|
|
76
|
+
rightMargin?: number;
|
|
77
|
+
}
|
|
78
|
+
export declare function createTextBlockSection(props: TextBlockProps): VisioSection;
|
|
42
79
|
export declare function createLineSection(props: {
|
|
43
80
|
color?: string;
|
|
44
81
|
pattern?: string;
|
|
@@ -6,6 +6,7 @@ exports.horzAlignValue = horzAlignValue;
|
|
|
6
6
|
exports.vertAlignValue = vertAlignValue;
|
|
7
7
|
exports.createCharacterSection = createCharacterSection;
|
|
8
8
|
exports.createParagraphSection = createParagraphSection;
|
|
9
|
+
exports.createTextBlockSection = createTextBlockSection;
|
|
9
10
|
exports.createLineSection = createLineSection;
|
|
10
11
|
const hexToRgb = (hex) => {
|
|
11
12
|
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
|
|
@@ -57,9 +58,14 @@ function vertAlignValue(align) {
|
|
|
57
58
|
}
|
|
58
59
|
function createCharacterSection(props) {
|
|
59
60
|
let styleVal = 0;
|
|
60
|
-
if (props.bold)
|
|
61
|
-
styleVal
|
|
62
|
-
|
|
61
|
+
if (props.bold)
|
|
62
|
+
styleVal |= 1;
|
|
63
|
+
if (props.italic)
|
|
64
|
+
styleVal |= 2;
|
|
65
|
+
if (props.underline)
|
|
66
|
+
styleVal |= 4;
|
|
67
|
+
if (props.strikethrough)
|
|
68
|
+
styleVal |= 8;
|
|
63
69
|
const colorVal = props.color || '#000000';
|
|
64
70
|
const cells = [
|
|
65
71
|
{ '@_N': 'Color', '@_V': colorVal, '@_F': hexToRgb(colorVal) },
|
|
@@ -89,20 +95,47 @@ function createCharacterSection(props) {
|
|
|
89
95
|
]
|
|
90
96
|
};
|
|
91
97
|
}
|
|
92
|
-
function createParagraphSection(
|
|
98
|
+
function createParagraphSection(props) {
|
|
99
|
+
const cells = [];
|
|
100
|
+
if (props.horzAlign !== undefined) {
|
|
101
|
+
cells.push({ '@_N': 'HorzAlign', '@_V': HORZ_ALIGN_VALUES[props.horzAlign] });
|
|
102
|
+
}
|
|
103
|
+
if (props.spaceBefore !== undefined) {
|
|
104
|
+
cells.push({ '@_N': 'SpBefore', '@_V': (props.spaceBefore / 72).toString(), '@_U': 'PT' });
|
|
105
|
+
}
|
|
106
|
+
if (props.spaceAfter !== undefined) {
|
|
107
|
+
cells.push({ '@_N': 'SpAfter', '@_V': (props.spaceAfter / 72).toString(), '@_U': 'PT' });
|
|
108
|
+
}
|
|
109
|
+
if (props.lineSpacing !== undefined) {
|
|
110
|
+
// Negative value = proportional multiplier; positive = absolute (inches)
|
|
111
|
+
cells.push({ '@_N': 'SpLine', '@_V': (-props.lineSpacing).toString() });
|
|
112
|
+
}
|
|
93
113
|
return {
|
|
94
114
|
'@_N': 'Paragraph',
|
|
95
115
|
Row: [
|
|
96
116
|
{
|
|
97
117
|
'@_T': 'Paragraph',
|
|
98
118
|
'@_IX': '0',
|
|
99
|
-
Cell:
|
|
100
|
-
{ '@_N': 'HorzAlign', '@_V': HORZ_ALIGN_VALUES[horzAlign] },
|
|
101
|
-
]
|
|
119
|
+
Cell: cells,
|
|
102
120
|
}
|
|
103
121
|
]
|
|
104
122
|
};
|
|
105
123
|
}
|
|
124
|
+
function createTextBlockSection(props) {
|
|
125
|
+
const cells = [];
|
|
126
|
+
if (props.topMargin !== undefined)
|
|
127
|
+
cells.push({ '@_N': 'TopMargin', '@_V': props.topMargin.toString(), '@_U': 'IN' });
|
|
128
|
+
if (props.bottomMargin !== undefined)
|
|
129
|
+
cells.push({ '@_N': 'BottomMargin', '@_V': props.bottomMargin.toString(), '@_U': 'IN' });
|
|
130
|
+
if (props.leftMargin !== undefined)
|
|
131
|
+
cells.push({ '@_N': 'LeftMargin', '@_V': props.leftMargin.toString(), '@_U': 'IN' });
|
|
132
|
+
if (props.rightMargin !== undefined)
|
|
133
|
+
cells.push({ '@_N': 'RightMargin', '@_V': props.rightMargin.toString(), '@_U': 'IN' });
|
|
134
|
+
return {
|
|
135
|
+
'@_N': 'TextBlock',
|
|
136
|
+
Cell: cells,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
106
139
|
function createLineSection(props) {
|
|
107
140
|
const cells = [
|
|
108
141
|
{ '@_N': 'LineColor', '@_V': props.color || '#000000' },
|