ts-visio 1.2.0 → 1.4.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 CHANGED
@@ -8,8 +8,6 @@
8
8
  A Node.js library to strict-type interact with Visio (`.vsdx`) files.
9
9
  Built using specific schema-level abstractions to handle the complex internal structure of Visio documents (ShapeSheets, Pages, Masters).
10
10
 
11
- > **Status**: Work In Progress (TDD).
12
-
13
11
  ## Features
14
12
 
15
13
  - **Read VSDX**: Open and parse `.vsdx` files (zipped XML).
@@ -18,13 +16,17 @@ Built using specific schema-level abstractions to handle the complex internal st
18
16
  - **Connections**: Analyze connectivity between shapes.
19
17
  - **Modular Architecture**: Use specialized components for loading, page management, shape reading, and modification.
20
18
  - **Modify Content**: Update text content of shapes.
21
- - **Create Shapes**: Add new rectangular shapes with text to pages.
22
- - **Connect Shapes**: Create dynamic connectors between shapes.
19
+ - **Create Shapes**: Rectangles, ellipses, diamonds, rounded rectangles, triangles, parallelograms.
20
+ - **Connect Shapes**: Dynamic connectors with arrow styles, line styling, and routing (straight / orthogonal / curved).
23
21
  - **Text Styling**: Font size, font family, bold, color, horizontal/vertical alignment.
24
22
  - **Shape Transformations**: Rotate, flip (X/Y), and resize shapes via a fluent API.
25
23
  - **Deletion**: Remove shapes and pages cleanly (including orphaned connectors and relationships).
26
24
  - **Lookup API**: Find shapes by ID, predicate, or look up pages by name.
27
25
  - **Read-Back API**: Read custom properties, hyperlinks, and layer assignments from existing shapes.
26
+ - **Page Size & Orientation**: Set canvas dimensions with named sizes (`Letter`, `A4`, …) or raw inches; rotate between portrait and landscape.
27
+ - **Document Metadata**: Read and write document properties (title, author, description, keywords, company, dates) via `doc.getMetadata()` / `doc.setMetadata()`.
28
+
29
+ Feature gaps are being tracked in [FEATURES.md](./FEATURES.md).
28
30
 
29
31
  ## Installation
30
32
 
@@ -418,6 +420,107 @@ const links = shape.getHyperlinks();
418
420
  const indices = shape.getLayerIndices(); // e.g. [0, 2]
419
421
  ```
420
422
 
423
+ #### 23. Non-Rectangular Geometry
424
+ Use the `geometry` prop on `addShape()` to create common flowchart primitives without touching XML.
425
+
426
+ ```typescript
427
+ // Ellipse / circle
428
+ await page.addShape({ text: "Start", x: 1, y: 5, width: 2, height: 2, geometry: 'ellipse' });
429
+
430
+ // Decision diamond
431
+ await page.addShape({ text: "Yes?", x: 4, y: 5, width: 2, height: 2, geometry: 'diamond' });
432
+
433
+ // Rounded rectangle (optional corner radius in inches)
434
+ await page.addShape({ text: "Process", x: 7, y: 5, width: 3, height: 2,
435
+ geometry: 'rounded-rectangle', cornerRadius: 0.2 });
436
+
437
+ // Right-pointing triangle
438
+ await page.addShape({ text: "Extract", x: 1, y: 2, width: 2, height: 2, geometry: 'triangle' });
439
+
440
+ // Parallelogram (Data / IO shape)
441
+ await page.addShape({ text: "Input", x: 4, y: 2, width: 3, height: 1.5, geometry: 'parallelogram' });
442
+ ```
443
+
444
+ Supported values: `'rectangle'` (default), `'ellipse'`, `'diamond'`, `'rounded-rectangle'`, `'triangle'`, `'parallelogram'`.
445
+
446
+ #### 24. Connector Styling
447
+ Control line appearance and routing algorithm on any connector.
448
+
449
+ ```typescript
450
+ import { ArrowHeads } from 'ts-visio/utils/StyleHelpers';
451
+
452
+ // Styled connector with crow's foot arrow and custom line
453
+ await shape1.connectTo(shape2, ArrowHeads.One, ArrowHeads.CrowsFoot, {
454
+ lineColor: '#cc0000', // Hex stroke color
455
+ lineWeight: 1.5, // Stroke width in points
456
+ linePattern: 2, // 1=solid, 2=dash, 3=dot, 4=dash-dot
457
+ routing: 'curved', // 'straight' | 'orthogonal' (default) | 'curved'
458
+ });
459
+
460
+ // Via page.connectShapes()
461
+ await page.connectShapes(a, b, undefined, undefined, { routing: 'straight' });
462
+ ```
463
+
464
+ #### 25. Page Size & Orientation
465
+ Control the canvas dimensions using named paper sizes or raw inch values.
466
+
467
+ ```typescript
468
+ import { PageSizes } from 'ts-visio';
469
+
470
+ // Use a named paper size (portrait by default)
471
+ page.setNamedSize('A4'); // 8.268" × 11.693"
472
+ page.setNamedSize('Letter', 'landscape'); // 11" × 8.5"
473
+
474
+ // Set arbitrary dimensions in inches
475
+ page.setSize(13.33, 7.5); // 13.33" × 7.5" widescreen
476
+
477
+ // Rotate the existing canvas without changing the paper size
478
+ page.setOrientation('landscape'); // swaps width and height if needed
479
+ page.setOrientation('portrait');
480
+
481
+ // Read current dimensions
482
+ console.log(page.pageWidth); // e.g. 11
483
+ console.log(page.pageHeight); // e.g. 8.5
484
+ console.log(page.orientation); // 'landscape' | 'portrait'
485
+
486
+ // All size methods are chainable
487
+ page.setNamedSize('A3').setOrientation('landscape');
488
+ ```
489
+
490
+ Available named sizes in `PageSizes`: `Letter`, `Legal`, `Tabloid`, `A3`, `A4`, `A5`.
491
+
492
+ ---
493
+
494
+ #### 26. Document Metadata
495
+ Set and read document-level properties that appear in Visio's Document Properties dialog.
496
+
497
+ ```typescript
498
+ // Write metadata (only supplied fields are changed)
499
+ doc.setMetadata({
500
+ title: 'Network Topology',
501
+ author: 'Alice',
502
+ description: 'Data-centre interconnect diagram',
503
+ keywords: 'network datacenter cloud',
504
+ lastModifiedBy: 'CI pipeline',
505
+ company: 'ACME Corp',
506
+ manager: 'Bob',
507
+ created: new Date('2025-01-01T00:00:00Z'),
508
+ modified: new Date(),
509
+ });
510
+
511
+ // Read back all metadata fields
512
+ const meta = doc.getMetadata();
513
+ console.log(meta.title); // 'Network Topology'
514
+ console.log(meta.author); // 'Alice'
515
+ console.log(meta.company); // 'ACME Corp'
516
+ console.log(meta.created); // Date object
517
+ ```
518
+
519
+ Fields map to OPC parts: `title`, `author`, `description`, `keywords`, `lastModifiedBy`,
520
+ `created`, `modified` → `docProps/core.xml`; `company`, `manager` → `docProps/app.xml`.
521
+
522
+ ---
523
+
421
524
  ## Examples
422
525
 
423
526
  Check out the [examples](./examples) directory for complete scripts.
package/dist/Page.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { VisioPage } from './types/VisioTypes';
1
+ import { VisioPage, ConnectorStyle, PageOrientation, PageSizeName } from './types/VisioTypes';
2
2
  import { VisioPackage } from './VisioPackage';
3
3
  import { ShapeModifier } from './ShapeModifier';
4
4
  import { NewShapeProps } from './types/VisioTypes';
@@ -17,6 +17,27 @@ export declare class Page {
17
17
  constructor(internalPage: VisioPage, pkg: VisioPackage, media?: MediaManager, rels?: RelsManager, modifier?: ShapeModifier);
18
18
  get id(): string;
19
19
  get name(): string;
20
+ /** Width of the page canvas in inches. */
21
+ get pageWidth(): number;
22
+ /** Height of the page canvas in inches. */
23
+ get pageHeight(): number;
24
+ /** Current page orientation derived from the canvas dimensions. */
25
+ get orientation(): PageOrientation;
26
+ /**
27
+ * Set the page canvas size in inches.
28
+ * @example page.setSize(11, 8.5) // landscape letter
29
+ */
30
+ setSize(width: number, height: number): this;
31
+ /**
32
+ * Convenience: change the page to a named standard size (portrait by default).
33
+ * @example page.setNamedSize('A4')
34
+ */
35
+ setNamedSize(sizeName: PageSizeName, orientation?: PageOrientation): this;
36
+ /**
37
+ * Rotate the canvas between portrait and landscape without changing the paper size.
38
+ * Swaps width and height when the current orientation does not match the requested one.
39
+ */
40
+ setOrientation(orientation: PageOrientation): this;
20
41
  getShapes(): Shape[];
21
42
  /**
22
43
  * Find a shape by its ID anywhere on the page, including shapes nested inside groups.
@@ -29,7 +50,7 @@ export declare class Page {
29
50
  */
30
51
  findShapes(predicate: (shape: Shape) => boolean): Shape[];
31
52
  addShape(props: NewShapeProps, parentId?: string): Promise<Shape>;
32
- connectShapes(fromShape: Shape, toShape: Shape, beginArrow?: string, endArrow?: string): Promise<void>;
53
+ connectShapes(fromShape: Shape, toShape: Shape, beginArrow?: string, endArrow?: string, style?: ConnectorStyle): Promise<void>;
33
54
  addImage(data: Buffer, name: string, x: number, y: number, width: number, height: number): Promise<Shape>;
34
55
  addContainer(props: NewShapeProps): Promise<Shape>;
35
56
  addList(props: NewShapeProps, direction?: 'vertical' | 'horizontal'): Promise<Shape>;
package/dist/Page.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Page = void 0;
4
+ const VisioTypes_1 = require("./types/VisioTypes");
4
5
  const ShapeReader_1 = require("./ShapeReader");
5
6
  const ShapeModifier_1 = require("./ShapeModifier");
6
7
  const Shape_1 = require("./Shape");
@@ -26,6 +27,51 @@ class Page {
26
27
  get name() {
27
28
  return this.internalPage.Name;
28
29
  }
30
+ /** Width of the page canvas in inches. */
31
+ get pageWidth() {
32
+ return this.modifier.getPageDimensions(this.id).width;
33
+ }
34
+ /** Height of the page canvas in inches. */
35
+ get pageHeight() {
36
+ return this.modifier.getPageDimensions(this.id).height;
37
+ }
38
+ /** Current page orientation derived from the canvas dimensions. */
39
+ get orientation() {
40
+ return this.pageWidth > this.pageHeight ? 'landscape' : 'portrait';
41
+ }
42
+ /**
43
+ * Set the page canvas size in inches.
44
+ * @example page.setSize(11, 8.5) // landscape letter
45
+ */
46
+ setSize(width, height) {
47
+ this.modifier.setPageSize(this.id, width, height);
48
+ return this;
49
+ }
50
+ /**
51
+ * Convenience: change the page to a named standard size (portrait by default).
52
+ * @example page.setNamedSize('A4')
53
+ */
54
+ setNamedSize(sizeName, orientation = 'portrait') {
55
+ const { width, height } = VisioTypes_1.PageSizes[sizeName];
56
+ return orientation === 'landscape'
57
+ ? this.setSize(height, width)
58
+ : this.setSize(width, height);
59
+ }
60
+ /**
61
+ * Rotate the canvas between portrait and landscape without changing the paper size.
62
+ * Swaps width and height when the current orientation does not match the requested one.
63
+ */
64
+ setOrientation(orientation) {
65
+ const w = this.pageWidth;
66
+ const h = this.pageHeight;
67
+ if (orientation === 'landscape' && h > w) {
68
+ this.modifier.setPageSize(this.id, h, w);
69
+ }
70
+ else if (orientation === 'portrait' && w > h) {
71
+ this.modifier.setPageSize(this.id, h, w);
72
+ }
73
+ return this;
74
+ }
29
75
  getShapes() {
30
76
  const reader = new ShapeReader_1.ShapeReader(this.pkg);
31
77
  try {
@@ -78,8 +124,8 @@ class Page {
78
124
  });
79
125
  return new Shape_1.Shape(internalStub, this.id, this.pkg, this.modifier);
80
126
  }
81
- async connectShapes(fromShape, toShape, beginArrow, endArrow) {
82
- await this.modifier.addConnector(this.id, fromShape.id, toShape.id, beginArrow, endArrow);
127
+ async connectShapes(fromShape, toShape, beginArrow, endArrow, style) {
128
+ await this.modifier.addConnector(this.id, fromShape.id, toShape.id, beginArrow, endArrow, style);
83
129
  }
84
130
  async addImage(data, name, x, y, width, height) {
85
131
  // 1. Upload Media
package/dist/Shape.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { VisioShape } from './types/VisioTypes';
1
+ import { VisioShape, ConnectorStyle } 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,7 @@ 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): Promise<this>;
33
+ connectTo(targetShape: Shape, beginArrow?: string, endArrow?: string, style?: ConnectorStyle): Promise<this>;
34
34
  setStyle(style: ShapeStyle): Promise<this>;
35
35
  placeRightOf(targetShape: Shape, options?: {
36
36
  gap: number;
package/dist/Shape.js CHANGED
@@ -42,8 +42,8 @@ class Shape {
42
42
  async delete() {
43
43
  await this.modifier.deleteShape(this.pageId, this.id);
44
44
  }
45
- async connectTo(targetShape, beginArrow, endArrow) {
46
- await this.modifier.addConnector(this.pageId, this.id, targetShape.id, beginArrow, endArrow);
45
+ async connectTo(targetShape, beginArrow, endArrow, style) {
46
+ await this.modifier.addConnector(this.pageId, this.id, targetShape.id, beginArrow, endArrow, style);
47
47
  return this;
48
48
  }
49
49
  async setStyle(style) {
@@ -1,6 +1,6 @@
1
1
  import { VisioPackage } from './VisioPackage';
2
2
  import { HorzAlign, VertAlign } from './utils/StyleHelpers';
3
- import { NewShapeProps } from './types/VisioTypes';
3
+ import { NewShapeProps, ConnectorStyle } from './types/VisioTypes';
4
4
  import type { ShapeData, ShapeHyperlink } from './Shape';
5
5
  export declare class ShapeModifier {
6
6
  private pkg;
@@ -31,7 +31,7 @@ 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): Promise<string>;
34
+ addConnector(pageId: string, fromShapeId: string, toShapeId: string, beginArrow?: string, endArrow?: string, style?: ConnectorStyle): Promise<string>;
35
35
  addShape(pageId: string, props: NewShapeProps, parentId?: string): Promise<string>;
36
36
  deleteShape(pageId: string, shapeId: string): Promise<void>;
37
37
  private removeShapeFromTree;
@@ -94,6 +94,19 @@ export declare class ShapeModifier {
94
94
  * the declared type (Number, Boolean, Date, or String).
95
95
  */
96
96
  getShapeProperties(pageId: string, shapeId: string): Record<string, ShapeData>;
97
+ /**
98
+ * Set the page canvas size. Writes PageWidth / PageHeight into the PageSheet
99
+ * and sets DrawingSizeType=0 (Custom) so Visio does not override the values.
100
+ */
101
+ setPageSize(pageId: string, width: number, height: number): void;
102
+ /**
103
+ * Read the current page canvas dimensions.
104
+ * Returns 8.5 × 11 (US Letter) if no PageSheet cells are present.
105
+ */
106
+ getPageDimensions(pageId: string): {
107
+ width: number;
108
+ height: number;
109
+ };
97
110
  /**
98
111
  * Read back all hyperlinks attached to a shape.
99
112
  */
@@ -184,7 +184,7 @@ class ShapeModifier {
184
184
  }
185
185
  this.dirtyPages.clear();
186
186
  }
187
- async addConnector(pageId, fromShapeId, toShapeId, beginArrow, endArrow) {
187
+ async addConnector(pageId, fromShapeId, toShapeId, beginArrow, endArrow, style) {
188
188
  const parsed = this.getParsed(pageId);
189
189
  // Ensure Shapes collection exists
190
190
  if (!parsed.PageContents.Shapes) {
@@ -205,7 +205,7 @@ class ShapeModifier {
205
205
  return val;
206
206
  };
207
207
  const layout = ConnectorBuilder_1.ConnectorBuilder.calculateConnectorLayout(fromShapeId, toShapeId, shapeHierarchy);
208
- const connectorShape = ConnectorBuilder_1.ConnectorBuilder.createConnectorShapeObject(newId, layout, validateArrow(beginArrow), validateArrow(endArrow));
208
+ const connectorShape = ConnectorBuilder_1.ConnectorBuilder.createConnectorShapeObject(newId, layout, validateArrow(beginArrow), validateArrow(endArrow), style);
209
209
  const topLevelShapes = parsed.PageContents.Shapes.Shape;
210
210
  topLevelShapes.push(connectorShape);
211
211
  this.getShapeMap(parsed).set(newId, connectorShape);
@@ -1012,6 +1012,45 @@ class ShapeModifier {
1012
1012
  }
1013
1013
  return result;
1014
1014
  }
1015
+ /**
1016
+ * Set the page canvas size. Writes PageWidth / PageHeight into the PageSheet
1017
+ * and sets DrawingSizeType=0 (Custom) so Visio does not override the values.
1018
+ */
1019
+ setPageSize(pageId, width, height) {
1020
+ if (width <= 0 || height <= 0)
1021
+ throw new Error('Page dimensions must be positive');
1022
+ const parsed = this.getParsed(pageId);
1023
+ this.ensurePageSheet(parsed);
1024
+ const ps = parsed.PageContents.PageSheet;
1025
+ const upsert = (name, value) => {
1026
+ const existing = ps.Cell.find((c) => c['@_N'] === name);
1027
+ if (existing)
1028
+ existing['@_V'] = value;
1029
+ else
1030
+ ps.Cell.push({ '@_N': name, '@_V': value });
1031
+ };
1032
+ upsert('PageWidth', width.toString());
1033
+ upsert('PageHeight', height.toString());
1034
+ upsert('DrawingSizeType', '0');
1035
+ upsert('PageDrawSizeType', '0');
1036
+ this.saveParsed(pageId, parsed);
1037
+ }
1038
+ /**
1039
+ * Read the current page canvas dimensions.
1040
+ * Returns 8.5 × 11 (US Letter) if no PageSheet cells are present.
1041
+ */
1042
+ getPageDimensions(pageId) {
1043
+ const parsed = this.getParsed(pageId);
1044
+ const ps = parsed.PageContents?.PageSheet;
1045
+ if (!ps?.Cell)
1046
+ return { width: 8.5, height: 11 };
1047
+ const cells = Array.isArray(ps.Cell) ? ps.Cell : [ps.Cell];
1048
+ const getVal = (name, def) => {
1049
+ const c = cells.find((cell) => cell['@_N'] === name);
1050
+ return c ? parseFloat(c['@_V']) : def;
1051
+ };
1052
+ return { width: getVal('PageWidth', 8.5), height: getVal('PageHeight', 11) };
1053
+ }
1015
1054
  /**
1016
1055
  * Read back all hyperlinks attached to a shape.
1017
1056
  */
@@ -1,9 +1,11 @@
1
1
  import { Page } from './Page';
2
+ import { DocumentMetadata } from './types/VisioTypes';
2
3
  export declare class VisioDocument {
3
4
  private pkg;
4
5
  private pageManager;
5
6
  private _pageCache;
6
7
  private mediaManager;
8
+ private metadataManager;
7
9
  private constructor();
8
10
  static create(): Promise<VisioDocument>;
9
11
  static load(pathOrBuffer: string | Buffer | ArrayBuffer | Uint8Array): Promise<VisioDocument>;
@@ -27,5 +29,18 @@ export declare class VisioDocument {
27
29
  * and any BackPage references from other pages.
28
30
  */
29
31
  deletePage(page: Page): Promise<void>;
32
+ /**
33
+ * Read document metadata from `docProps/core.xml` and `docProps/app.xml`.
34
+ * Fields not present in the file are returned as `undefined`.
35
+ */
36
+ getMetadata(): DocumentMetadata;
37
+ /**
38
+ * Write document metadata. Only the supplied fields are changed;
39
+ * all other fields keep their existing values.
40
+ *
41
+ * @example
42
+ * doc.setMetadata({ title: 'My Diagram', author: 'Alice', company: 'ACME' });
43
+ */
44
+ setMetadata(props: Partial<DocumentMetadata>): void;
30
45
  save(filename?: string): Promise<Buffer>;
31
46
  }
@@ -38,12 +38,14 @@ const VisioPackage_1 = require("./VisioPackage");
38
38
  const PageManager_1 = require("./core/PageManager");
39
39
  const Page_1 = require("./Page");
40
40
  const MediaManager_1 = require("./core/MediaManager");
41
+ const MetadataManager_1 = require("./core/MetadataManager");
41
42
  class VisioDocument {
42
43
  constructor(pkg) {
43
44
  this.pkg = pkg;
44
45
  this._pageCache = null;
45
46
  this.pageManager = new PageManager_1.PageManager(pkg);
46
47
  this.mediaManager = new MediaManager_1.MediaManager(pkg);
48
+ this.metadataManager = new MetadataManager_1.MetadataManager(pkg);
47
49
  }
48
50
  static async create() {
49
51
  const pkg = await VisioPackage_1.VisioPackage.create();
@@ -131,6 +133,23 @@ class VisioDocument {
131
133
  await this.pageManager.deletePage(page.id);
132
134
  this._pageCache = null;
133
135
  }
136
+ /**
137
+ * Read document metadata from `docProps/core.xml` and `docProps/app.xml`.
138
+ * Fields not present in the file are returned as `undefined`.
139
+ */
140
+ getMetadata() {
141
+ return this.metadataManager.getMetadata();
142
+ }
143
+ /**
144
+ * Write document metadata. Only the supplied fields are changed;
145
+ * all other fields keep their existing values.
146
+ *
147
+ * @example
148
+ * doc.setMetadata({ title: 'My Diagram', author: 'Alice', company: 'ACME' });
149
+ */
150
+ setMetadata(props) {
151
+ this.metadataManager.setMetadata(props);
152
+ }
134
153
  async save(filename) {
135
154
  return this.pkg.save(filename);
136
155
  }
@@ -0,0 +1,22 @@
1
+ import { VisioPackage } from '../VisioPackage';
2
+ import { DocumentMetadata } from '../types/VisioTypes';
3
+ export declare class MetadataManager {
4
+ private pkg;
5
+ private parser;
6
+ private builder;
7
+ constructor(pkg: VisioPackage);
8
+ /** Read document metadata from `docProps/core.xml` and `docProps/app.xml`. */
9
+ getMetadata(): DocumentMetadata;
10
+ /** Merge the supplied fields into the existing metadata and persist to the package. */
11
+ setMetadata(props: Partial<DocumentMetadata>): void;
12
+ /** Extract a string from a parsed XML node (handles plain strings and #text objects). */
13
+ private str;
14
+ /** Parse an ISO datetime string into a Date (returns undefined on failure). */
15
+ private date;
16
+ private parsedCore;
17
+ private parsedApp;
18
+ private writeCore;
19
+ private writeApp;
20
+ private blankCoreParsed;
21
+ private blankAppParsed;
22
+ }
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MetadataManager = void 0;
4
+ const VisioConstants_1 = require("./VisioConstants");
5
+ const XmlHelper_1 = require("../utils/XmlHelper");
6
+ class MetadataManager {
7
+ constructor(pkg) {
8
+ this.pkg = pkg;
9
+ this.parser = (0, XmlHelper_1.createXmlParser)();
10
+ this.builder = (0, XmlHelper_1.createXmlBuilder)();
11
+ }
12
+ // ---- public API --------------------------------------------------------
13
+ /** Read document metadata from `docProps/core.xml` and `docProps/app.xml`. */
14
+ getMetadata() {
15
+ const core = this.parsedCore();
16
+ const app = this.parsedApp();
17
+ return {
18
+ title: this.str(core['dc:title']),
19
+ author: this.str(core['dc:creator']),
20
+ description: this.str(core['dc:description']),
21
+ keywords: this.str(core['cp:keywords']),
22
+ lastModifiedBy: this.str(core['cp:lastModifiedBy']),
23
+ created: this.date(core['dcterms:created']),
24
+ modified: this.date(core['dcterms:modified']),
25
+ company: this.str(app['Company']),
26
+ manager: this.str(app['Manager']),
27
+ };
28
+ }
29
+ /** Merge the supplied fields into the existing metadata and persist to the package. */
30
+ setMetadata(props) {
31
+ const coreKeys = [
32
+ 'title', 'author', 'description', 'keywords', 'lastModifiedBy', 'created', 'modified',
33
+ ];
34
+ const appKeys = ['company', 'manager'];
35
+ if (coreKeys.some(k => k in props))
36
+ this.writeCore(props);
37
+ if (appKeys.some(k => k in props))
38
+ this.writeApp(props);
39
+ }
40
+ // ---- private helpers ---------------------------------------------------
41
+ /** Extract a string from a parsed XML node (handles plain strings and #text objects). */
42
+ str(val) {
43
+ if (val === undefined || val === null)
44
+ return undefined;
45
+ if (typeof val === 'string')
46
+ return val || undefined;
47
+ if (typeof val === 'object' && '#text' in val) {
48
+ const t = val['#text'];
49
+ const s = typeof t === 'string' ? t : String(t);
50
+ return s || undefined;
51
+ }
52
+ return undefined;
53
+ }
54
+ /** Parse an ISO datetime string into a Date (returns undefined on failure). */
55
+ date(val) {
56
+ const text = this.str(val);
57
+ if (!text)
58
+ return undefined;
59
+ const d = new Date(text);
60
+ return isNaN(d.getTime()) ? undefined : d;
61
+ }
62
+ parsedCore() {
63
+ try {
64
+ const xml = this.pkg.getFileText('docProps/core.xml');
65
+ const parsed = this.parser.parse(xml);
66
+ return parsed['cp:coreProperties'] ?? {};
67
+ }
68
+ catch {
69
+ return {};
70
+ }
71
+ }
72
+ parsedApp() {
73
+ try {
74
+ const xml = this.pkg.getFileText('docProps/app.xml');
75
+ const parsed = this.parser.parse(xml);
76
+ return parsed['Properties'] ?? {};
77
+ }
78
+ catch {
79
+ return {};
80
+ }
81
+ }
82
+ writeCore(props) {
83
+ let parsed;
84
+ try {
85
+ const xml = this.pkg.getFileText('docProps/core.xml');
86
+ parsed = this.parser.parse(xml);
87
+ }
88
+ catch {
89
+ parsed = this.blankCoreParsed();
90
+ }
91
+ const root = parsed['cp:coreProperties'];
92
+ if ('title' in props)
93
+ root['dc:title'] = props.title ?? '';
94
+ if ('author' in props)
95
+ root['dc:creator'] = props.author ?? '';
96
+ if ('description' in props)
97
+ root['dc:description'] = props.description ?? '';
98
+ if ('keywords' in props)
99
+ root['cp:keywords'] = props.keywords ?? '';
100
+ if ('lastModifiedBy' in props)
101
+ root['cp:lastModifiedBy'] = props.lastModifiedBy ?? '';
102
+ if ('created' in props && props.created !== undefined) {
103
+ root['dcterms:created'] = {
104
+ '@_xsi:type': 'dcterms:W3CDTF',
105
+ '#text': props.created.toISOString(),
106
+ };
107
+ }
108
+ if ('modified' in props && props.modified !== undefined) {
109
+ root['dcterms:modified'] = {
110
+ '@_xsi:type': 'dcterms:W3CDTF',
111
+ '#text': props.modified.toISOString(),
112
+ };
113
+ }
114
+ this.pkg.updateFile('docProps/core.xml', (0, XmlHelper_1.buildXml)(this.builder, parsed));
115
+ }
116
+ writeApp(props) {
117
+ let parsed;
118
+ try {
119
+ const xml = this.pkg.getFileText('docProps/app.xml');
120
+ parsed = this.parser.parse(xml);
121
+ }
122
+ catch {
123
+ parsed = this.blankAppParsed();
124
+ }
125
+ const root = parsed['Properties'];
126
+ if ('company' in props)
127
+ root['Company'] = props.company ?? '';
128
+ if ('manager' in props)
129
+ root['Manager'] = props.manager ?? '';
130
+ this.pkg.updateFile('docProps/app.xml', (0, XmlHelper_1.buildXml)(this.builder, parsed));
131
+ }
132
+ blankCoreParsed() {
133
+ return {
134
+ 'cp:coreProperties': {
135
+ '@_xmlns:cp': VisioConstants_1.XML_NAMESPACES.CORE_PROPERTIES,
136
+ '@_xmlns:dc': VisioConstants_1.XML_NAMESPACES.DC_ELEMENTS,
137
+ '@_xmlns:dcterms': VisioConstants_1.XML_NAMESPACES.DC_TERMS,
138
+ '@_xmlns:dcmitype': VisioConstants_1.XML_NAMESPACES.DC_DCMITYPE,
139
+ '@_xmlns:xsi': VisioConstants_1.XML_NAMESPACES.XSI,
140
+ },
141
+ };
142
+ }
143
+ blankAppParsed() {
144
+ return {
145
+ 'Properties': {
146
+ '@_xmlns': VisioConstants_1.XML_NAMESPACES.EXTENDED_PROPERTIES,
147
+ '@_xmlns:vt': VisioConstants_1.XML_NAMESPACES.DOC_PROPS_VTYPES,
148
+ 'Application': 'ts-visio',
149
+ 'Template': 'Basic',
150
+ },
151
+ };
152
+ }
153
+ }
154
+ exports.MetadataManager = MetadataManager;
package/dist/index.d.ts CHANGED
@@ -7,4 +7,7 @@ export { VisioDocument } from './VisioDocument';
7
7
  export { Page } from './Page';
8
8
  export { Shape } from './Shape';
9
9
  export type { ShapeData, ShapeHyperlink } from './Shape';
10
+ export { Layer } from './Layer';
11
+ export { SchemaDiagram } from './SchemaDiagram';
12
+ export { VisioValidator } from './core/VisioValidator';
10
13
  export * from './types/VisioTypes';
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.Shape = exports.Page = exports.VisioDocument = exports.ShapeModifier = exports.ShapeReader = exports.PageManager = exports.VisioPackage = void 0;
17
+ exports.VisioValidator = exports.SchemaDiagram = exports.Layer = 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,4 +29,10 @@ 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 Layer_1 = require("./Layer");
33
+ Object.defineProperty(exports, "Layer", { enumerable: true, get: function () { return Layer_1.Layer; } });
34
+ var SchemaDiagram_1 = require("./SchemaDiagram");
35
+ Object.defineProperty(exports, "SchemaDiagram", { enumerable: true, get: function () { return SchemaDiagram_1.SchemaDiagram; } });
36
+ var VisioValidator_1 = require("./core/VisioValidator");
37
+ Object.defineProperty(exports, "VisioValidator", { enumerable: true, get: function () { return VisioValidator_1.VisioValidator; } });
32
38
  __exportStar(require("./types/VisioTypes"), exports);
@@ -1,3 +1,4 @@
1
+ import { ConnectorStyle } from '../types/VisioTypes';
1
2
  export declare class ConnectorBuilder {
2
3
  private static getCellVal;
3
4
  private static getAbsolutePos;
@@ -17,7 +18,7 @@ export declare class ConnectorBuilder {
17
18
  width: number;
18
19
  angle: number;
19
20
  };
20
- static createConnectorShapeObject(id: string, layout: any, beginArrow?: string, endArrow?: string): {
21
+ static createConnectorShapeObject(id: string, layout: any, beginArrow?: string, endArrow?: string, style?: ConnectorStyle): {
21
22
  '@_ID': string;
22
23
  '@_NameU': string;
23
24
  '@_Name': string;
@@ -2,6 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConnectorBuilder = void 0;
4
4
  const StyleHelpers_1 = require("../utils/StyleHelpers");
5
+ const ROUTING_VALUES = {
6
+ straight: '2',
7
+ orthogonal: '1',
8
+ curved: '16',
9
+ };
5
10
  class ConnectorBuilder {
6
11
  static getCellVal(shape, name) {
7
12
  if (!shape || !shape.Cell)
@@ -96,8 +101,12 @@ class ConnectorBuilder {
96
101
  const angle = Math.atan2(dy, dx);
97
102
  return { beginX, beginY, endX, endY, width, angle };
98
103
  }
99
- static createConnectorShapeObject(id, layout, beginArrow, endArrow) {
104
+ static createConnectorShapeObject(id, layout, beginArrow, endArrow, style) {
100
105
  const { beginX, beginY, endX, endY, width, angle } = layout;
106
+ const routeStyle = style?.routing ? (ROUTING_VALUES[style.routing] ?? '1') : '1';
107
+ const lineWeightIn = style?.lineWeight != null
108
+ ? (style.lineWeight / 72).toString() // convert pt → inches
109
+ : '0.01';
101
110
  return {
102
111
  '@_ID': id,
103
112
  '@_NameU': 'Dynamic connector',
@@ -118,7 +127,7 @@ class ConnectorBuilder {
118
127
  { '@_N': 'ObjType', '@_V': '2' },
119
128
  { '@_N': 'ShapePermeableX', '@_V': '0' },
120
129
  { '@_N': 'ShapePermeableY', '@_V': '0' },
121
- { '@_N': 'ShapeRouteStyle', '@_V': '1' },
130
+ { '@_N': 'ShapeRouteStyle', '@_V': routeStyle },
122
131
  { '@_N': 'ConFixedCode', '@_V': '0' },
123
132
  { '@_N': 'BeginArrow', '@_V': beginArrow || '0' },
124
133
  { '@_N': 'BeginArrowSize', '@_V': '2' },
@@ -127,8 +136,9 @@ class ConnectorBuilder {
127
136
  ],
128
137
  Section: [
129
138
  (0, StyleHelpers_1.createLineSection)({
130
- color: '#000000',
131
- weight: '0.01'
139
+ color: style?.lineColor ?? '#000000',
140
+ weight: lineWeightIn,
141
+ pattern: style?.linePattern != null ? String(style.linePattern) : undefined,
132
142
  }),
133
143
  {
134
144
  '@_N': 'Geometry',
@@ -0,0 +1,42 @@
1
+ import { ShapeGeometry } from '../types/VisioTypes';
2
+ export declare class GeometryBuilder {
3
+ /**
4
+ * Build a Geometry section object for the given props.
5
+ * Defaults to 'rectangle' when geometry is omitted.
6
+ */
7
+ static build(props: {
8
+ width: number;
9
+ height: number;
10
+ geometry?: ShapeGeometry;
11
+ cornerRadius?: number;
12
+ fillColor?: string;
13
+ }): any;
14
+ /** Standard rectangle: 4 LineTo rows starting at origin. */
15
+ static rectangle(W: number, H: number, noFill: string): any;
16
+ /**
17
+ * Ellipse: a single Ellipse row. No MoveTo required.
18
+ * X, Y = centre; A, B = rightmost point; C, D = topmost point.
19
+ */
20
+ static ellipse(W: number, H: number, noFill: string): any;
21
+ /**
22
+ * Diamond: 4 LineTo rows, starting at top vertex and going clockwise
23
+ * (top → right → bottom → left → top), matching Visio's built-in Decision shape.
24
+ */
25
+ static diamond(W: number, H: number, noFill: string): any;
26
+ /**
27
+ * Rounded rectangle with circular corner arcs of radius `r`.
28
+ * Uses EllipticalArcTo with C=0 (no rotation) and D=1 (circle).
29
+ * The A, B control cells are the arc midpoints: r*(1-√2/2) inset from each corner.
30
+ */
31
+ static roundedRectangle(W: number, H: number, r: number, noFill: string): any;
32
+ /**
33
+ * Right-pointing triangle (standard Visio flowchart orientation).
34
+ * Vertices: bottom-left (0,0), apex-right (W, H/2), top-left (0, H).
35
+ */
36
+ static triangle(W: number, H: number, noFill: string): any;
37
+ /**
38
+ * Parallelogram with a rightward skew of 20% of the width.
39
+ * Matches the proportions of Visio's built-in "Data" flowchart shape.
40
+ */
41
+ static parallelogram(W: number, H: number, noFill: string): any;
42
+ }
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GeometryBuilder = void 0;
4
+ /**
5
+ * For a 90° circular arc of radius r, the midpoint on the arc sits at 45° from
6
+ * the corner. Its distance from each endpoint along the arc edge is:
7
+ * r * (1 - cos(45°)) = r * (1 - √2/2) ≈ r * 0.29289
8
+ * This offset is used to compute the A, B control-point cells of EllipticalArcTo.
9
+ */
10
+ const ARC_OFFSET = 1 - Math.SQRT2 / 2; // ≈ 0.29289321881345254
11
+ // ---------------------------------------------------------------------------
12
+ // Internal helpers
13
+ // ---------------------------------------------------------------------------
14
+ function cell(n, value, formula) {
15
+ const obj = { '@_N': n, '@_V': typeof value === 'number' ? value.toString() : value };
16
+ if (formula)
17
+ obj['@_F'] = formula;
18
+ return obj;
19
+ }
20
+ function moveTo(ix, x, y, xF, yF) {
21
+ return {
22
+ '@_T': 'MoveTo',
23
+ '@_IX': String(ix),
24
+ Cell: [cell('X', x, xF), cell('Y', y, yF)],
25
+ };
26
+ }
27
+ function lineTo(ix, x, y, xF, yF) {
28
+ return {
29
+ '@_T': 'LineTo',
30
+ '@_IX': String(ix),
31
+ Cell: [cell('X', x, xF), cell('Y', y, yF)],
32
+ };
33
+ }
34
+ function arcTo(ix, endX, endY, midX, midY) {
35
+ return {
36
+ '@_T': 'EllipticalArcTo',
37
+ '@_IX': String(ix),
38
+ Cell: [
39
+ cell('X', endX), // arc endpoint X
40
+ cell('Y', endY), // arc endpoint Y
41
+ cell('A', midX), // midpoint-on-arc X
42
+ cell('B', midY), // midpoint-on-arc Y
43
+ cell('C', '0'), // major-axis angle (0 = no rotation)
44
+ cell('D', '1'), // eccentricity ratio (1 = circle)
45
+ ],
46
+ };
47
+ }
48
+ function section(rows, noFill) {
49
+ return {
50
+ '@_N': 'Geometry',
51
+ '@_IX': '0',
52
+ Cell: [{ '@_N': 'NoFill', '@_V': noFill }],
53
+ Row: rows,
54
+ };
55
+ }
56
+ // ---------------------------------------------------------------------------
57
+ // Public builder
58
+ // ---------------------------------------------------------------------------
59
+ class GeometryBuilder {
60
+ /**
61
+ * Build a Geometry section object for the given props.
62
+ * Defaults to 'rectangle' when geometry is omitted.
63
+ */
64
+ static build(props) {
65
+ const { width: W, height: H, geometry, cornerRadius, fillColor } = props;
66
+ const noFill = fillColor ? '0' : '1';
67
+ switch (geometry) {
68
+ case 'ellipse':
69
+ return GeometryBuilder.ellipse(W, H, noFill);
70
+ case 'diamond':
71
+ return GeometryBuilder.diamond(W, H, noFill);
72
+ case 'rounded-rectangle': {
73
+ const r = cornerRadius ?? Math.min(W, H) * 0.1;
74
+ return GeometryBuilder.roundedRectangle(W, H, r, noFill);
75
+ }
76
+ case 'triangle':
77
+ return GeometryBuilder.triangle(W, H, noFill);
78
+ case 'parallelogram':
79
+ return GeometryBuilder.parallelogram(W, H, noFill);
80
+ case 'rectangle':
81
+ default:
82
+ return GeometryBuilder.rectangle(W, H, noFill);
83
+ }
84
+ }
85
+ // -----------------------------------------------------------------------
86
+ // Individual geometry factories (also exported for direct use / testing)
87
+ // -----------------------------------------------------------------------
88
+ /** Standard rectangle: 4 LineTo rows starting at origin. */
89
+ static rectangle(W, H, noFill) {
90
+ return section([
91
+ moveTo(1, 0, 0),
92
+ lineTo(2, W, 0, 'Width'),
93
+ lineTo(3, W, H, 'Width', 'Height'),
94
+ lineTo(4, 0, H, undefined, 'Height'),
95
+ lineTo(5, 0, 0),
96
+ ], noFill);
97
+ }
98
+ /**
99
+ * Ellipse: a single Ellipse row. No MoveTo required.
100
+ * X, Y = centre; A, B = rightmost point; C, D = topmost point.
101
+ */
102
+ static ellipse(W, H, noFill) {
103
+ return section([
104
+ {
105
+ '@_T': 'Ellipse',
106
+ '@_IX': '1',
107
+ Cell: [
108
+ cell('X', W / 2, 'Width*0.5'), // centre X
109
+ cell('Y', H / 2, 'Height*0.5'), // centre Y
110
+ cell('A', W, 'Width'), // right X
111
+ cell('B', H / 2, 'Height*0.5'), // right Y
112
+ cell('C', W / 2, 'Width*0.5'), // top X
113
+ cell('D', H, 'Height'), // top Y
114
+ ],
115
+ },
116
+ ], noFill);
117
+ }
118
+ /**
119
+ * Diamond: 4 LineTo rows, starting at top vertex and going clockwise
120
+ * (top → right → bottom → left → top), matching Visio's built-in Decision shape.
121
+ */
122
+ static diamond(W, H, noFill) {
123
+ return section([
124
+ moveTo(1, W / 2, H, 'Width*0.5', 'Height'), // top
125
+ lineTo(2, W, H / 2, 'Width', 'Height*0.5'), // right
126
+ lineTo(3, W / 2, 0, 'Width*0.5'), // bottom
127
+ lineTo(4, 0, H / 2, undefined, 'Height*0.5'), // left
128
+ lineTo(5, W / 2, H, 'Width*0.5', 'Height'), // close
129
+ ], noFill);
130
+ }
131
+ /**
132
+ * Rounded rectangle with circular corner arcs of radius `r`.
133
+ * Uses EllipticalArcTo with C=0 (no rotation) and D=1 (circle).
134
+ * The A, B control cells are the arc midpoints: r*(1-√2/2) inset from each corner.
135
+ */
136
+ static roundedRectangle(W, H, r, noFill) {
137
+ const k = r * ARC_OFFSET; // midpoint inset ≈ r * 0.2929
138
+ return section([
139
+ moveTo(1, r, 0),
140
+ lineTo(2, W - r, 0),
141
+ arcTo(3, W, r, W - k, k), // bottom-right corner
142
+ lineTo(4, W, H - r),
143
+ arcTo(5, W - r, H, W - k, H - k), // top-right corner
144
+ lineTo(6, r, H),
145
+ arcTo(7, 0, H - r, k, H - k), // top-left corner
146
+ lineTo(8, 0, r),
147
+ arcTo(9, r, 0, k, k), // bottom-left corner (closes path)
148
+ ], noFill);
149
+ }
150
+ /**
151
+ * Right-pointing triangle (standard Visio flowchart orientation).
152
+ * Vertices: bottom-left (0,0), apex-right (W, H/2), top-left (0, H).
153
+ */
154
+ static triangle(W, H, noFill) {
155
+ return section([
156
+ moveTo(1, 0, 0),
157
+ lineTo(2, W, H / 2, 'Width', 'Height*0.5'),
158
+ lineTo(3, 0, H, undefined, 'Height'),
159
+ lineTo(4, 0, 0),
160
+ ], noFill);
161
+ }
162
+ /**
163
+ * Parallelogram with a rightward skew of 20% of the width.
164
+ * Matches the proportions of Visio's built-in "Data" flowchart shape.
165
+ */
166
+ static parallelogram(W, H, noFill) {
167
+ const s = W * 0.2; // horizontal skew offset
168
+ return section([
169
+ moveTo(1, s, 0),
170
+ lineTo(2, W, 0, 'Width'),
171
+ lineTo(3, W - s, H),
172
+ lineTo(4, 0, H, undefined, 'Height'),
173
+ lineTo(5, s, 0),
174
+ ], noFill);
175
+ }
176
+ }
177
+ exports.GeometryBuilder = GeometryBuilder;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ShapeBuilder = void 0;
4
4
  const StyleHelpers_1 = require("../utils/StyleHelpers");
5
+ const GeometryBuilder_1 = require("./GeometryBuilder");
5
6
  class ShapeBuilder {
6
7
  static createStandardShape(id, props) {
7
8
  // Validate dimensions
@@ -53,18 +54,7 @@ class ShapeBuilder {
53
54
  // Add Geometry
54
55
  // Only if NOT a Group AND NOT a Master Instance
55
56
  if (props.type !== 'Group' && !props.masterId) {
56
- shape.Section.push({
57
- '@_N': 'Geometry',
58
- '@_IX': '0',
59
- Cell: [{ '@_N': 'NoFill', '@_V': props.fillColor ? '0' : '1' }],
60
- Row: [
61
- { '@_T': 'MoveTo', '@_IX': '1', Cell: [{ '@_N': 'X', '@_V': '0' }, { '@_N': 'Y', '@_V': '0' }] },
62
- { '@_T': 'LineTo', '@_IX': '2', Cell: [{ '@_N': 'X', '@_V': props.width.toString(), '@_F': 'Width' }, { '@_N': 'Y', '@_V': '0' }] },
63
- { '@_T': 'LineTo', '@_IX': '3', Cell: [{ '@_N': 'X', '@_V': props.width.toString(), '@_F': 'Width' }, { '@_N': 'Y', '@_V': props.height.toString(), '@_F': 'Height' }] },
64
- { '@_T': 'LineTo', '@_IX': '4', Cell: [{ '@_N': 'X', '@_V': '0' }, { '@_N': 'Y', '@_V': props.height.toString(), '@_F': 'Height' }] },
65
- { '@_T': 'LineTo', '@_IX': '5', Cell: [{ '@_N': 'X', '@_V': '0' }, { '@_N': 'Y', '@_V': '0' }] }
66
- ]
67
- });
57
+ shape.Section.push(GeometryBuilder_1.GeometryBuilder.build(props));
68
58
  }
69
59
  // Handle Text if provided
70
60
  if (props.text !== undefined && props.text !== null) {
@@ -72,6 +72,80 @@ export declare enum VisioPropType {
72
72
  Duration = 6,
73
73
  Currency = 7
74
74
  }
75
+ /** Document-level metadata that maps to `docProps/core.xml` and `docProps/app.xml`. */
76
+ export interface DocumentMetadata {
77
+ /** Document title (`dc:title`). */
78
+ title?: string;
79
+ /** Author / creator (`dc:creator`). */
80
+ author?: string;
81
+ /** Short description (`dc:description`). */
82
+ description?: string;
83
+ /** Space-separated keywords (`cp:keywords`). */
84
+ keywords?: string;
85
+ /** Last-modified-by user (`cp:lastModifiedBy`). */
86
+ lastModifiedBy?: string;
87
+ /** Company name from `app.xml` `<Company>`. */
88
+ company?: string;
89
+ /** Manager name from `app.xml` `<Manager>`. */
90
+ manager?: string;
91
+ /** Document creation timestamp (`dcterms:created`). */
92
+ created?: Date;
93
+ /** Last-modified timestamp (`dcterms:modified`). */
94
+ modified?: Date;
95
+ }
96
+ export type PageOrientation = 'portrait' | 'landscape';
97
+ /** Common paper sizes in inches (width × height in portrait orientation). */
98
+ export declare const PageSizes: {
99
+ readonly Letter: {
100
+ readonly width: 8.5;
101
+ readonly height: 11;
102
+ };
103
+ readonly Legal: {
104
+ readonly width: 8.5;
105
+ readonly height: 14;
106
+ };
107
+ readonly Tabloid: {
108
+ readonly width: 11;
109
+ readonly height: 17;
110
+ };
111
+ readonly A3: {
112
+ readonly width: 11.693;
113
+ readonly height: 16.535;
114
+ };
115
+ readonly A4: {
116
+ readonly width: 8.268;
117
+ readonly height: 11.693;
118
+ };
119
+ readonly A5: {
120
+ readonly width: 5.827;
121
+ readonly height: 8.268;
122
+ };
123
+ };
124
+ export type PageSizeName = keyof typeof PageSizes;
125
+ /** Connector line-routing algorithm. */
126
+ export type ConnectorRouting = 'straight' | 'orthogonal' | 'curved';
127
+ /**
128
+ * Style options for a connector (dynamic connector shape).
129
+ * All fields are optional; omitted fields retain their defaults.
130
+ */
131
+ export interface ConnectorStyle {
132
+ /** Line stroke color as a CSS hex string (e.g. `'#ff0000'`). */
133
+ lineColor?: string;
134
+ /**
135
+ * Stroke weight in **points** (e.g. `1` for a 1 pt line).
136
+ * Converted to inches internally (pt / 72).
137
+ */
138
+ lineWeight?: number;
139
+ /**
140
+ * Line pattern. 0 = no line, 1 = solid (default), 2 = dashed,
141
+ * 3 = dotted, 4 = dash-dot, etc. Matches Visio's LinePattern cell.
142
+ */
143
+ linePattern?: number;
144
+ /** How Visio routes the connector between its endpoints. */
145
+ routing?: ConnectorRouting;
146
+ }
147
+ /** Non-rectangular geometry variants supported by ShapeBuilder. */
148
+ export type ShapeGeometry = 'rectangle' | 'ellipse' | 'diamond' | 'rounded-rectangle' | 'triangle' | 'parallelogram';
75
149
  export interface NewShapeProps {
76
150
  text: string;
77
151
  x: number;
@@ -90,6 +164,10 @@ export interface NewShapeProps {
90
164
  horzAlign?: 'left' | 'center' | 'right' | 'justify';
91
165
  /** Vertical text alignment within the shape. */
92
166
  verticalAlign?: 'top' | 'middle' | 'bottom';
167
+ /** Shape geometry. Defaults to 'rectangle'. */
168
+ geometry?: ShapeGeometry;
169
+ /** Corner radius in inches for 'rounded-rectangle'. Defaults to 10% of the smaller dimension. */
170
+ cornerRadius?: number;
93
171
  type?: string;
94
172
  masterId?: string;
95
173
  imgRelId?: string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.VisioPropType = void 0;
3
+ exports.PageSizes = exports.VisioPropType = void 0;
4
4
  var VisioPropType;
5
5
  (function (VisioPropType) {
6
6
  VisioPropType[VisioPropType["String"] = 0] = "String";
@@ -12,3 +12,12 @@ var VisioPropType;
12
12
  VisioPropType[VisioPropType["Duration"] = 6] = "Duration";
13
13
  VisioPropType[VisioPropType["Currency"] = 7] = "Currency";
14
14
  })(VisioPropType || (exports.VisioPropType = VisioPropType = {}));
15
+ /** Common paper sizes in inches (width × height in portrait orientation). */
16
+ exports.PageSizes = {
17
+ Letter: { width: 8.5, height: 11 },
18
+ Legal: { width: 8.5, height: 14 },
19
+ Tabloid: { width: 11, height: 17 },
20
+ A3: { width: 11.693, height: 16.535 },
21
+ A4: { width: 8.268, height: 11.693 },
22
+ A5: { width: 5.827, height: 8.268 },
23
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-visio",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {