ts-visio 1.0.2 → 1.2.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.
@@ -18,8 +18,8 @@ class ShapeBuilder {
18
18
  { '@_N': 'PinY', '@_V': props.y.toString() },
19
19
  { '@_N': 'Width', '@_V': props.width.toString() },
20
20
  { '@_N': 'Height', '@_V': props.height.toString() },
21
- { '@_N': 'LocPinX', '@_V': (props.width / 2).toString() },
22
- { '@_N': 'LocPinY', '@_V': (props.height / 2).toString() }
21
+ { '@_N': 'LocPinX', '@_V': (props.width / 2).toString(), '@_F': 'Width*0.5' },
22
+ { '@_N': 'LocPinY', '@_V': (props.height / 2).toString(), '@_F': 'Height*0.5' }
23
23
  ],
24
24
  Section: []
25
25
  // Text added at end by caller or we can do it here if props.text is final
@@ -36,12 +36,20 @@ class ShapeBuilder {
36
36
  weight: '0.01'
37
37
  }));
38
38
  }
39
- if (props.fontColor || props.bold) {
39
+ if (props.fontColor || props.bold || props.fontSize !== undefined || props.fontFamily !== undefined) {
40
40
  shape.Section.push((0, StyleHelpers_1.createCharacterSection)({
41
41
  bold: props.bold,
42
- color: props.fontColor
42
+ color: props.fontColor,
43
+ fontSize: props.fontSize,
44
+ fontFamily: props.fontFamily,
43
45
  }));
44
46
  }
47
+ if (props.horzAlign !== undefined) {
48
+ shape.Section.push((0, StyleHelpers_1.createParagraphSection)(props.horzAlign));
49
+ }
50
+ if (props.verticalAlign !== undefined) {
51
+ shape.Cell.push({ '@_N': 'VerticalAlign', '@_V': (0, StyleHelpers_1.vertAlignValue)(props.verticalAlign) });
52
+ }
45
53
  // Add Geometry
46
54
  // Only if NOT a Group AND NOT a Master Instance
47
55
  if (props.type !== 'Group' && !props.masterId) {
@@ -51,9 +59,9 @@ class ShapeBuilder {
51
59
  Cell: [{ '@_N': 'NoFill', '@_V': props.fillColor ? '0' : '1' }],
52
60
  Row: [
53
61
  { '@_T': 'MoveTo', '@_IX': '1', Cell: [{ '@_N': 'X', '@_V': '0' }, { '@_N': 'Y', '@_V': '0' }] },
54
- { '@_T': 'LineTo', '@_IX': '2', Cell: [{ '@_N': 'X', '@_V': props.width.toString() }, { '@_N': 'Y', '@_V': '0' }] },
55
- { '@_T': 'LineTo', '@_IX': '3', Cell: [{ '@_N': 'X', '@_V': props.width.toString() }, { '@_N': 'Y', '@_V': props.height.toString() }] },
56
- { '@_T': 'LineTo', '@_IX': '4', Cell: [{ '@_N': 'X', '@_V': '0' }, { '@_N': 'Y', '@_V': props.height.toString() }] },
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' }] },
57
65
  { '@_T': 'LineTo', '@_IX': '5', Cell: [{ '@_N': 'X', '@_V': '0' }, { '@_N': 'Y', '@_V': '0' }] }
58
66
  ]
59
67
  });
@@ -45,6 +45,10 @@ export interface VisioPage {
45
45
  ID: string;
46
46
  Name: string;
47
47
  NameU?: string;
48
+ /** Resolved OPC part path (e.g. "visio/pages/page2.xml"). When present,
49
+ * this takes precedence over the ID-derived fallback path so that loaded
50
+ * files with non-sequential page filenames are handled correctly. */
51
+ xmlPath?: string;
48
52
  Shapes: VisioShape[];
49
53
  Connects: VisioConnect[];
50
54
  isBackground?: boolean;
@@ -78,6 +82,14 @@ export interface NewShapeProps {
78
82
  fillColor?: string;
79
83
  fontColor?: string;
80
84
  bold?: boolean;
85
+ /** Font size in points (e.g. 14 for 14pt). */
86
+ fontSize?: number;
87
+ /** Font family name (e.g. "Arial", "Times New Roman"). */
88
+ fontFamily?: string;
89
+ /** Horizontal text alignment within the shape. */
90
+ horzAlign?: 'left' | 'center' | 'right' | 'justify';
91
+ /** Vertical text alignment within the shape. */
92
+ verticalAlign?: 'top' | 'middle' | 'bottom';
81
93
  type?: string;
82
94
  masterId?: string;
83
95
  imgRelId?: string;
@@ -15,16 +15,33 @@ export declare const ArrowHeads: {
15
15
  CrowsFoot: string;
16
16
  One: string;
17
17
  };
18
+ declare const HORZ_ALIGN_VALUES: {
19
+ readonly left: "0";
20
+ readonly center: "1";
21
+ readonly right: "2";
22
+ readonly justify: "3";
23
+ };
24
+ declare const VERT_ALIGN_VALUES: {
25
+ readonly top: "0";
26
+ readonly middle: "1";
27
+ readonly bottom: "2";
28
+ };
29
+ export type HorzAlign = keyof typeof HORZ_ALIGN_VALUES;
30
+ export type VertAlign = keyof typeof VERT_ALIGN_VALUES;
31
+ export declare function horzAlignValue(align: HorzAlign): string;
32
+ export declare function vertAlignValue(align: VertAlign): string;
18
33
  export declare function createCharacterSection(props: {
19
34
  bold?: boolean;
20
35
  color?: string;
36
+ /** Font size in points (e.g. 12 for 12pt). Stored internally as inches (pt / 72). */
37
+ fontSize?: number;
38
+ /** Font family name (e.g. "Arial"). Uses FONT() formula for portability. */
39
+ fontFamily?: string;
21
40
  }): VisioSection;
41
+ export declare function createParagraphSection(horzAlign: HorzAlign): VisioSection;
22
42
  export declare function createLineSection(props: {
23
43
  color?: string;
24
44
  pattern?: string;
25
45
  weight?: string;
26
- beginArrow?: string;
27
- beginArrowSize?: string;
28
- endArrow?: string;
29
- endArrowSize?: string;
30
46
  }): VisioSection;
47
+ export {};
@@ -2,7 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ArrowHeads = void 0;
4
4
  exports.createFillSection = createFillSection;
5
+ exports.horzAlignValue = horzAlignValue;
6
+ exports.vertAlignValue = vertAlignValue;
5
7
  exports.createCharacterSection = createCharacterSection;
8
+ exports.createParagraphSection = createParagraphSection;
6
9
  exports.createLineSection = createLineSection;
7
10
  const hexToRgb = (hex) => {
8
11
  // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
@@ -35,32 +38,66 @@ exports.ArrowHeads = {
35
38
  One: '24', // Visio "One" (Dash) - Approximate, or '26'
36
39
  // There are many variants, but 29 is the standard "Fork"
37
40
  };
41
+ const HORZ_ALIGN_VALUES = {
42
+ left: '0',
43
+ center: '1',
44
+ right: '2',
45
+ justify: '3',
46
+ };
47
+ const VERT_ALIGN_VALUES = {
48
+ top: '0',
49
+ middle: '1',
50
+ bottom: '2',
51
+ };
52
+ function horzAlignValue(align) {
53
+ return HORZ_ALIGN_VALUES[align];
54
+ }
55
+ function vertAlignValue(align) {
56
+ return VERT_ALIGN_VALUES[align];
57
+ }
38
58
  function createCharacterSection(props) {
39
- // Visio Character Section
40
- // N="Character"
41
- // Row T="Character"
42
- // Cell N="Color" V="#FF0000"
43
- // Cell N="Style" V="1" (1=Bold, 2=Italic, 4=Underline) - Bitwise
44
- // Visio booleans are often 0 or 1.
45
- // Style=1 (Bold)
46
- // Default Style is 0 (Normal)
47
59
  let styleVal = 0;
48
60
  if (props.bold) {
49
- styleVal += 1; // Add Bold bit
61
+ styleVal += 1; // Bold bit
50
62
  }
51
- // Default Color is usually 0 (Black) or specific hex
52
63
  const colorVal = props.color || '#000000';
64
+ const cells = [
65
+ { '@_N': 'Color', '@_V': colorVal, '@_F': hexToRgb(colorVal) },
66
+ { '@_N': 'Style', '@_V': styleVal.toString() },
67
+ ];
68
+ if (props.fontSize !== undefined) {
69
+ // Visio stores size in inches internally; @_U="PT" is a display hint for the ShapeSheet UI
70
+ const sizeInInches = props.fontSize / 72;
71
+ cells.push({ '@_N': 'Size', '@_V': sizeInInches.toString(), '@_U': 'PT' });
72
+ }
73
+ if (props.fontFamily !== undefined) {
74
+ // FONT("name") formula lets Visio resolve the font by name at load time.
75
+ // @_V="0" is a safe placeholder (document default font) used before Visio evaluates the formula.
76
+ cells.push({ '@_N': 'Font', '@_V': '0', '@_F': `FONT("${props.fontFamily}")` });
77
+ }
78
+ else {
79
+ cells.push({ '@_N': 'Font', '@_V': '1' }); // Default (Calibri)
80
+ }
53
81
  return {
54
82
  '@_N': 'Character',
55
83
  Row: [
56
84
  {
57
85
  '@_T': 'Character',
58
86
  '@_IX': '0',
87
+ Cell: cells,
88
+ }
89
+ ]
90
+ };
91
+ }
92
+ function createParagraphSection(horzAlign) {
93
+ return {
94
+ '@_N': 'Paragraph',
95
+ Row: [
96
+ {
97
+ '@_T': 'Paragraph',
98
+ '@_IX': '0',
59
99
  Cell: [
60
- { '@_N': 'Color', '@_V': colorVal, '@_F': hexToRgb(colorVal) },
61
- { '@_N': 'Style', '@_V': styleVal.toString() },
62
- // Size, Font, etc could go here
63
- { '@_N': 'Font', '@_V': '1' } // Default font (Calibri usually)
100
+ { '@_N': 'HorzAlign', '@_V': HORZ_ALIGN_VALUES[horzAlign] },
64
101
  ]
65
102
  }
66
103
  ]
@@ -70,24 +107,12 @@ function createLineSection(props) {
70
107
  const cells = [
71
108
  { '@_N': 'LineColor', '@_V': props.color || '#000000' },
72
109
  { '@_N': 'LinePattern', '@_V': props.pattern || '1' }, // 1 = Solid
73
- { '@_N': 'LineWeight', '@_V': props.weight || '0.01' } // ~0.72pt
110
+ { '@_N': 'LineWeight', '@_V': props.weight || '0.01', '@_U': 'IN' } // ~0.72pt
74
111
  ];
75
112
  // Add RGB Formula for custom colors
76
113
  if (props.color) {
77
114
  cells[0]['@_F'] = hexToRgb(props.color);
78
115
  }
79
- if (props.beginArrow) {
80
- cells.push({ '@_N': 'BeginArrow', '@_V': props.beginArrow });
81
- }
82
- if (props.beginArrowSize) {
83
- cells.push({ '@_N': 'BeginArrowSize', '@_V': props.beginArrowSize });
84
- }
85
- if (props.endArrow) {
86
- cells.push({ '@_N': 'EndArrow', '@_V': props.endArrow });
87
- }
88
- if (props.endArrowSize) {
89
- cells.push({ '@_N': 'EndArrowSize', '@_V': props.endArrowSize });
90
- }
91
116
  return {
92
117
  '@_N': 'Line',
93
118
  Cell: cells
@@ -0,0 +1,39 @@
1
+ import { XMLParser, XMLBuilder } from 'fast-xml-parser';
2
+ /**
3
+ * Standard XMLParser options used across all Visio XML parts.
4
+ *
5
+ * - ignoreDeclaration: false → preserves <?xml?> in the parsed object
6
+ * - ignoreAttributes: false → preserves xmlns, xmlns:r, xml:space, etc.
7
+ * - parseAttributeValue: false → keeps attribute values as strings (avoids
8
+ * numeric coercion on IDs, versions, etc.)
9
+ */
10
+ export declare const PARSER_OPTIONS: {
11
+ readonly ignoreAttributes: false;
12
+ readonly attributeNamePrefix: "@_";
13
+ readonly ignoreDeclaration: false;
14
+ readonly parseAttributeValue: false;
15
+ };
16
+ /**
17
+ * Standard XMLBuilder options used across all Visio XML parts.
18
+ *
19
+ * - suppressBooleanAttributes: false → always emit attribute="value", never
20
+ * bare attribute (important for Visio attributes like xml:space="preserve")
21
+ */
22
+ export declare const BUILDER_OPTIONS: {
23
+ readonly ignoreAttributes: false;
24
+ readonly attributeNamePrefix: "@_";
25
+ readonly format: true;
26
+ readonly suppressBooleanAttributes: false;
27
+ };
28
+ export declare function createXmlParser(): XMLParser;
29
+ export declare function createXmlBuilder(): XMLBuilder;
30
+ /**
31
+ * Serialize a parsed XML object to a string, guaranteeing the XML declaration
32
+ * is always present at the top of the output.
33
+ *
34
+ * fast-xml-parser's XMLBuilder re-emits the `?xml` key when it is present in
35
+ * the parsed object (requires ignoreDeclaration: false on the parser side).
36
+ * This function acts as a safety net for cases where the declaration was
37
+ * absent in the source XML or was stripped for any reason.
38
+ */
39
+ export declare function buildXml(builder: XMLBuilder, parsed: unknown): string;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BUILDER_OPTIONS = exports.PARSER_OPTIONS = void 0;
4
+ exports.createXmlParser = createXmlParser;
5
+ exports.createXmlBuilder = createXmlBuilder;
6
+ exports.buildXml = buildXml;
7
+ const fast_xml_parser_1 = require("fast-xml-parser");
8
+ const XML_DECLARATION = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
9
+ /**
10
+ * Standard XMLParser options used across all Visio XML parts.
11
+ *
12
+ * - ignoreDeclaration: false → preserves <?xml?> in the parsed object
13
+ * - ignoreAttributes: false → preserves xmlns, xmlns:r, xml:space, etc.
14
+ * - parseAttributeValue: false → keeps attribute values as strings (avoids
15
+ * numeric coercion on IDs, versions, etc.)
16
+ */
17
+ exports.PARSER_OPTIONS = {
18
+ ignoreAttributes: false,
19
+ attributeNamePrefix: '@_',
20
+ ignoreDeclaration: false,
21
+ parseAttributeValue: false,
22
+ };
23
+ /**
24
+ * Standard XMLBuilder options used across all Visio XML parts.
25
+ *
26
+ * - suppressBooleanAttributes: false → always emit attribute="value", never
27
+ * bare attribute (important for Visio attributes like xml:space="preserve")
28
+ */
29
+ exports.BUILDER_OPTIONS = {
30
+ ignoreAttributes: false,
31
+ attributeNamePrefix: '@_',
32
+ format: true,
33
+ suppressBooleanAttributes: false,
34
+ };
35
+ function createXmlParser() {
36
+ return new fast_xml_parser_1.XMLParser(exports.PARSER_OPTIONS);
37
+ }
38
+ function createXmlBuilder() {
39
+ return new fast_xml_parser_1.XMLBuilder(exports.BUILDER_OPTIONS);
40
+ }
41
+ /**
42
+ * Serialize a parsed XML object to a string, guaranteeing the XML declaration
43
+ * is always present at the top of the output.
44
+ *
45
+ * fast-xml-parser's XMLBuilder re-emits the `?xml` key when it is present in
46
+ * the parsed object (requires ignoreDeclaration: false on the parser side).
47
+ * This function acts as a safety net for cases where the declaration was
48
+ * absent in the source XML or was stripped for any reason.
49
+ */
50
+ function buildXml(builder, parsed) {
51
+ const xml = builder.build(parsed);
52
+ if (xml.trimStart().startsWith('<?xml')) {
53
+ return xml;
54
+ }
55
+ return XML_DECLARATION + '\n' + xml;
56
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-visio",
3
- "version": "1.0.2",
3
+ "version": "1.2.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {
@@ -28,4 +28,4 @@
28
28
  "typescript": "^5.9.3",
29
29
  "vitest": "^4.0.17"
30
30
  }
31
- }
31
+ }