@unmade/text-renderer 0.2.0 → 0.3.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
@@ -34,6 +34,18 @@ import { getRenderedText } from '@unmade/text-renderer/node'; // Node.js
34
34
  import { getRenderedText } from '@unmade/text-renderer/worker'; // Web Worker
35
35
  ```
36
36
 
37
+ ### ESM-only
38
+
39
+ This package is ESM-only — `require('@unmade/text-renderer')` will not work. This is a hard constraint: harfbuzzjs (the font shaping engine) uses top-level `await` for WASM initialisation, which Rollup cannot emit as CJS.
40
+
41
+ **Node.js / Lambda**: Node 18+ supports ESM natively, so this is not a blocker. When integrating from a CJS codebase, use dynamic import:
42
+
43
+ ```typescript
44
+ const { getRenderedText } = await import('@unmade/text-renderer/node');
45
+ ```
46
+
47
+ **CE Lambda integration**: When wiring text-renderer into the Configuration Engine Lambda, use the `/node` ESM entry (`@unmade/text-renderer/node`) from an ESM Lambda handler, or use the `await import()` pattern above from an existing CJS handler. The `ce-adapter` sub-package (`@unmade/text-renderer/ce-adapter`) retains a CJS build and can be `require()`'d normally.
48
+
37
49
  ## Configuration Engine adapter
38
50
 
39
51
  The `ce-adapter` sub-package bridges CE (Configuration Engine) editor state to renderer options:
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function C(t){return t.type==="text"}function P(t){const i=t.getState().garment;return t.settings.garments.find(s=>s.slug===i)}function w(t,o){if(t.embellishment_positions)return t.embellishment_positions.find(i=>i.placement===o)}function A(t,o){const i=t.fonts.find(s=>s.id===o);if(i)return{id:i.id,fontUrl:i.font_url,fontFamily:i.font_family,letterSpacing:i.letter_spacing??0,letterSpacingOutline:i.letter_spacing_outline??0,outlineWidth:i.outline_width??0,fixedColours:i.fixed_colours}}function v(t,o,i,s,c){const{library:u}=c,a=[];for(const[l,h]of Object.entries(s)){if(h.type!=="text")continue;const f=h,{meta:n}=f;if(!n?.text)continue;const{libraryItemID:d}=f,g=u[d];if(!g||!C(g))continue;const y=n.font;if(y===void 0)continue;const m=A(g,y);if(!m)continue;const r=w(o,l);if(!r)continue;let x=null,b=null;if(n.colours&&g.palette!=null)try{const e=t.palettes.getUsedColours(g.palette,n.colours);x=e[0]??null,b=e[1]??null}catch(e){console.warn(`[extractTextPlacements] Colour resolution failed for ${l}:`,e)}const S=c.byId?.[l],p={vertical:"top",horizontal:"left"};if(S){const e=S;e.vertical_alignment&&(p.vertical=e.vertical_alignment),e.horizontal_alignment&&(p.horizontal=e.horizontal_alignment)}a.push({placementId:l,displayName:l,source:i,text:n.text,font:m,isCurved:n.isCurved??!1,isDistributed:n.isDistributed??!1,verticalAlignment:n.verticalAlignment??p.vertical,horizontalAlignment:n.horizontalAlignment??p.horizontal,rotation:n.rotation??0,spacing:n.spacing??null,fillColour:x,outlineColour:b,position:{width:r.width,height:r.height,physicalWidth:r.physical_width_decimal,physicalHeight:r.physical_height_decimal,physicalUnits:r.physical_size_units,baselinePath:r.baseline_path,rotation:r.rotation},userSetPhysicalSize:n.retainedPhysicalSize??null,fontMetrics:(()=>{const e=n.fontMetrics??{size:0,height:0,baseline:0,width:0},z=e.physicalFontSize||(e.physicalHeight!=null&&e.physicalHeight>0&&e.physicalUnits!=null?[e.physicalHeight,e.physicalUnits]:void 0);return{...e,physicalFontSize:z}})(),manufacturingMethod:n.manufacturing_method??""})}return a}function U(t){const o=t.getState(),i=P(t);if(!i)return console.warn("[extractTextPlacements] No matching garment found"),[];const s=[],c=o.vals?.placements,u=t.settings.placements;c&&u&&s.push(...v(t,i,"placements",c,u));const a=o.vals?.embellishments,l=t.settings.embellishments;return a&&l&&s.push(...v(t,i,"embellishments",a,l)),s.length===0&&console.warn("[extractTextPlacements] No text entries found in either placements or embellishments"),s}function _(t){return t==="center"?"centre":t}function O(t){let o=_(t.horizontalAlignment);t.isDistributed&&(o="distributed");let i;t.position.baselinePath?i={type:"custom",path:t.position.baselinePath}:t.isCurved?i="curved":i="flat";const s=t.userSetPhysicalSize?[t.userSetPhysicalSize[0],t.userSetPhysicalSize[1]]:t.fontMetrics.physicalFontSize!=null?t.fontMetrics.physicalFontSize:[t.position.physicalHeight,t.position.physicalUnits],u={outlineWidth:t.outlineColour!=null?t.font.outlineWidth:0,letterSpacing:t.spacing?.letter_spacing??0,letterSpacingOutline:t.spacing?.letter_spacing_outline??0},a=t.fillColour?{hex:t.fillColour.hex,reference:t.fillColour.reference??void 0}:void 0,l=t.outlineColour?{hex:t.outlineColour.hex,reference:t.outlineColour.reference??void 0}:void 0;let h;if(t.font.fixedColours){const f={};for(const[n,d]of Object.entries(t.font.fixedColours))f[n]={hex:d.hex,reference:d.reference};h=f}return{text:t.text,fontUrl:t.font.fontUrl,physicalSize:s,fontSizeFallback:t.fontMetrics.size,boxDimensions:{width:t.position.width,height:t.position.height,physicalWidth:t.position.physicalWidth,physicalHeight:t.position.physicalHeight,physicalUnits:t.position.physicalUnits},spacing:u,verticalAlignment:_(t.verticalAlignment),horizontalAlignment:o,baseline:i,fillColour:a,strokeColour:l,colourMap:h}}exports.extractTextPlacements=U;exports.mapStateToRendererOptions=O;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function P(t){return t.type==="text"}function w(t){const i=t.getState().garment;return t.settings.garments.find(s=>s.slug===i)}function A(t,l,i){if(!t.embellishment_positions)return;const s=t.embellishment_positions.filter(o=>o.placement!==l?!1:!i||!o.variant?!0:o.variant===i||o.variant.toLowerCase()==="common");if(i&&s.length>1){const o=s.find(a=>a.variant===i);if(o)return o}return s[0]}function U(t,l){const i=t.fonts.find(s=>s.id===l);if(i)return{id:i.id,fontUrl:i.font_url,fontFamily:i.font_family,letterSpacing:i.letter_spacing??0,letterSpacingOutline:i.letter_spacing_outline??0,outlineWidth:i.outline_width??0,fixedColours:i.fixed_colours}}function v(t,l,i,s,o,a){const{library:h}=o,u=[];for(const[r,f]of Object.entries(s)){if(f.type!=="text")continue;const d=f,{meta:e}=d;if(!e?.text)continue;const{libraryItemID:z}=d,g=h[z];if(!g||!P(g))continue;const y=e.font;if(y===void 0)continue;const m=U(g,y);if(!m)continue;const c=A(l,r,a);if(!c)continue;let x=null,b=null;if(e.colours&&g.palette!=null)try{const n=t.palettes.getUsedColours(g.palette,e.colours);x=n[0]??null,b=n[1]??null}catch(n){console.warn(`[extractTextPlacements] Colour resolution failed for ${r}:`,n)}const S=o.byId?.[r],p={vertical:"top",horizontal:"left"};if(S){const n=S;n.vertical_alignment&&(p.vertical=n.vertical_alignment),n.horizontal_alignment&&(p.horizontal=n.horizontal_alignment)}u.push({placementId:r,displayName:r,source:i,text:e.text,font:m,isCurved:e.isCurved??!1,isDistributed:e.isDistributed??!1,verticalAlignment:e.verticalAlignment??p.vertical,horizontalAlignment:e.horizontalAlignment??p.horizontal,rotation:e.rotation??0,spacing:e.spacing??null,fillColour:x,outlineColour:b,position:{width:c.width,height:c.height,physicalWidth:c.physical_width_decimal,physicalHeight:c.physical_height_decimal,physicalUnits:c.physical_size_units,baselinePath:c.baseline_path,rotation:c.rotation},userSetPhysicalSize:e.retainedPhysicalSize??null,fontMetrics:(()=>{const n=e.fontMetrics??{size:0,height:0,baseline:0,width:0},C=n.physicalFontSize||(n.physicalHeight!=null&&n.physicalHeight>0&&n.physicalUnits!=null?[n.physicalHeight,n.physicalUnits]:void 0);return{...n,physicalFontSize:C}})(),manufacturingMethod:e.manufacturing_method??""})}return u}function O(t){const l=t.getState(),i=w(t);if(!i)return console.warn("[extractTextPlacements] No matching garment found"),[];const s=typeof t.getCurrentVariant=="function"?t.getCurrentVariant():void 0,o=[],a=l.vals?.placements,h=t.settings.placements;a&&h&&o.push(...v(t,i,"placements",a,h,s));const u=l.vals?.embellishments,r=t.settings.embellishments;return u&&r&&o.push(...v(t,i,"embellishments",u,r,s)),o.length===0&&console.warn("[extractTextPlacements] No text entries found in either placements or embellishments"),o}function _(t){return t==="center"?"centre":t}function T(t){let l=_(t.horizontalAlignment);t.isDistributed&&(l="distributed");let i;t.position.baselinePath?i={type:"custom",path:t.position.baselinePath}:t.isCurved?i="curved":i="flat";const s=t.userSetPhysicalSize?[t.userSetPhysicalSize[0],t.userSetPhysicalSize[1]]:t.fontMetrics.physicalFontSize!=null?t.fontMetrics.physicalFontSize:[t.position.physicalHeight,t.position.physicalUnits],a={outlineWidth:t.outlineColour!=null?t.font.outlineWidth:0,letterSpacing:t.spacing?.letter_spacing??0,letterSpacingOutline:t.spacing?.letter_spacing_outline??0},h=t.fillColour?{hex:t.fillColour.hex,reference:t.fillColour.reference??void 0}:void 0,u=t.outlineColour?{hex:t.outlineColour.hex,reference:t.outlineColour.reference??void 0}:void 0;let r;if(t.font.fixedColours){const f={};for(const[d,e]of Object.entries(t.font.fixedColours))f[d]={hex:e.hex,reference:e.reference};r=f}return{text:t.text,fontUrl:t.font.fontUrl,physicalSize:s,fontSizeFallback:t.fontMetrics.size,boxDimensions:{width:t.position.width,height:t.position.height,physicalWidth:t.position.physicalWidth,physicalHeight:t.position.physicalHeight,physicalUnits:t.position.physicalUnits},spacing:a,verticalAlignment:_(t.verticalAlignment),horizontalAlignment:l,baseline:i,fillColour:h,strokeColour:u,colourMap:r}}exports.extractTextPlacements=O;exports.mapStateToRendererOptions=T;
@@ -1,18 +1,21 @@
1
- function C(t) {
1
+ function w(t) {
2
2
  return t.type === "text";
3
3
  }
4
4
  function P(t) {
5
5
  const i = t.getState().garment;
6
6
  return t.settings.garments.find((s) => s.slug === i);
7
7
  }
8
- function w(t, o) {
9
- if (t.embellishment_positions)
10
- return t.embellishment_positions.find(
11
- (i) => i.placement === o
12
- );
8
+ function A(t, l, i) {
9
+ if (!t.embellishment_positions) return;
10
+ const s = t.embellishment_positions.filter((o) => o.placement !== l ? !1 : !i || !o.variant ? !0 : o.variant === i || o.variant.toLowerCase() === "common");
11
+ if (i && s.length > 1) {
12
+ const o = s.find((a) => a.variant === i);
13
+ if (o) return o;
14
+ }
15
+ return s[0];
13
16
  }
14
- function A(t, o) {
15
- const i = t.fonts.find((s) => s.id === o);
17
+ function U(t, l) {
18
+ const i = t.fonts.find((s) => s.id === l);
16
19
  if (i)
17
20
  return {
18
21
  id: i.id,
@@ -24,109 +27,109 @@ function A(t, o) {
24
27
  fixedColours: i.fixed_colours
25
28
  };
26
29
  }
27
- function v(t, o, i, s, c) {
28
- const { library: u } = c, a = [];
29
- for (const [l, h] of Object.entries(s)) {
30
- if (h.type !== "text") continue;
31
- const f = h, { meta: n } = f;
32
- if (!n?.text) continue;
33
- const { libraryItemID: d } = f, g = u[d];
34
- if (!g || !C(g)) continue;
35
- const y = n.font;
30
+ function S(t, l, i, s, o, a) {
31
+ const { library: h } = o, u = [];
32
+ for (const [r, f] of Object.entries(s)) {
33
+ if (f.type !== "text") continue;
34
+ const d = f, { meta: e } = d;
35
+ if (!e?.text) continue;
36
+ const { libraryItemID: z } = d, g = h[z];
37
+ if (!g || !w(g)) continue;
38
+ const y = e.font;
36
39
  if (y === void 0) continue;
37
- const m = A(g, y);
40
+ const m = U(g, y);
38
41
  if (!m) continue;
39
- const r = w(o, l);
40
- if (!r) continue;
42
+ const c = A(l, r, a);
43
+ if (!c) continue;
41
44
  let x = null, b = null;
42
- if (n.colours && g.palette != null)
45
+ if (e.colours && g.palette != null)
43
46
  try {
44
- const e = t.palettes.getUsedColours(
47
+ const n = t.palettes.getUsedColours(
45
48
  g.palette,
46
- n.colours
49
+ e.colours
47
50
  );
48
- x = e[0] ?? null, b = e[1] ?? null;
49
- } catch (e) {
50
- console.warn(`[extractTextPlacements] Colour resolution failed for ${l}:`, e);
51
+ x = n[0] ?? null, b = n[1] ?? null;
52
+ } catch (n) {
53
+ console.warn(`[extractTextPlacements] Colour resolution failed for ${r}:`, n);
51
54
  }
52
- const S = c.byId?.[l], p = {
55
+ const v = o.byId?.[r], p = {
53
56
  vertical: "top",
54
57
  horizontal: "left"
55
58
  };
56
- if (S) {
57
- const e = S;
58
- e.vertical_alignment && (p.vertical = e.vertical_alignment), e.horizontal_alignment && (p.horizontal = e.horizontal_alignment);
59
+ if (v) {
60
+ const n = v;
61
+ n.vertical_alignment && (p.vertical = n.vertical_alignment), n.horizontal_alignment && (p.horizontal = n.horizontal_alignment);
59
62
  }
60
- a.push({
61
- placementId: l,
62
- displayName: l,
63
+ u.push({
64
+ placementId: r,
65
+ displayName: r,
63
66
  source: i,
64
- text: n.text,
67
+ text: e.text,
65
68
  font: m,
66
- isCurved: n.isCurved ?? !1,
67
- isDistributed: n.isDistributed ?? !1,
68
- verticalAlignment: n.verticalAlignment ?? p.vertical,
69
- horizontalAlignment: n.horizontalAlignment ?? p.horizontal,
70
- rotation: n.rotation ?? 0,
71
- spacing: n.spacing ?? null,
69
+ isCurved: e.isCurved ?? !1,
70
+ isDistributed: e.isDistributed ?? !1,
71
+ verticalAlignment: e.verticalAlignment ?? p.vertical,
72
+ horizontalAlignment: e.horizontalAlignment ?? p.horizontal,
73
+ rotation: e.rotation ?? 0,
74
+ spacing: e.spacing ?? null,
72
75
  fillColour: x,
73
76
  outlineColour: b,
74
77
  position: {
75
- width: r.width,
76
- height: r.height,
77
- physicalWidth: r.physical_width_decimal,
78
- physicalHeight: r.physical_height_decimal,
79
- physicalUnits: r.physical_size_units,
80
- baselinePath: r.baseline_path,
81
- rotation: r.rotation
78
+ width: c.width,
79
+ height: c.height,
80
+ physicalWidth: c.physical_width_decimal,
81
+ physicalHeight: c.physical_height_decimal,
82
+ physicalUnits: c.physical_size_units,
83
+ baselinePath: c.baseline_path,
84
+ rotation: c.rotation
82
85
  },
83
- userSetPhysicalSize: n.retainedPhysicalSize ?? null,
86
+ userSetPhysicalSize: e.retainedPhysicalSize ?? null,
84
87
  fontMetrics: (() => {
85
- const e = n.fontMetrics ?? { size: 0, height: 0, baseline: 0, width: 0 }, z = e.physicalFontSize || (e.physicalHeight != null && e.physicalHeight > 0 && e.physicalUnits != null ? [e.physicalHeight, e.physicalUnits] : void 0);
86
- return { ...e, physicalFontSize: z };
88
+ const n = e.fontMetrics ?? { size: 0, height: 0, baseline: 0, width: 0 }, C = n.physicalFontSize || (n.physicalHeight != null && n.physicalHeight > 0 && n.physicalUnits != null ? [n.physicalHeight, n.physicalUnits] : void 0);
89
+ return { ...n, physicalFontSize: C };
87
90
  })(),
88
- manufacturingMethod: n.manufacturing_method ?? ""
91
+ manufacturingMethod: e.manufacturing_method ?? ""
89
92
  });
90
93
  }
91
- return a;
94
+ return u;
92
95
  }
93
- function U(t) {
94
- const o = t.getState(), i = P(t);
96
+ function F(t) {
97
+ const l = t.getState(), i = P(t);
95
98
  if (!i)
96
99
  return console.warn("[extractTextPlacements] No matching garment found"), [];
97
- const s = [], c = o.vals?.placements, u = t.settings.placements;
98
- c && u && s.push(...v(t, i, "placements", c, u));
99
- const a = o.vals?.embellishments, l = t.settings.embellishments;
100
- return a && l && s.push(...v(t, i, "embellishments", a, l)), s.length === 0 && console.warn("[extractTextPlacements] No text entries found in either placements or embellishments"), s;
100
+ const s = typeof t.getCurrentVariant == "function" ? t.getCurrentVariant() : void 0, o = [], a = l.vals?.placements, h = t.settings.placements;
101
+ a && h && o.push(...S(t, i, "placements", a, h, s));
102
+ const u = l.vals?.embellishments, r = t.settings.embellishments;
103
+ return u && r && o.push(...S(t, i, "embellishments", u, r, s)), o.length === 0 && console.warn("[extractTextPlacements] No text entries found in either placements or embellishments"), o;
101
104
  }
102
105
  function _(t) {
103
106
  return t === "center" ? "centre" : t;
104
107
  }
105
- function F(t) {
106
- let o = _(t.horizontalAlignment);
107
- t.isDistributed && (o = "distributed");
108
+ function H(t) {
109
+ let l = _(t.horizontalAlignment);
110
+ t.isDistributed && (l = "distributed");
108
111
  let i;
109
112
  t.position.baselinePath ? i = { type: "custom", path: t.position.baselinePath } : t.isCurved ? i = "curved" : i = "flat";
110
113
  const s = t.userSetPhysicalSize ? [
111
114
  t.userSetPhysicalSize[0],
112
115
  t.userSetPhysicalSize[1]
113
- ] : t.fontMetrics.physicalFontSize != null ? t.fontMetrics.physicalFontSize : [t.position.physicalHeight, t.position.physicalUnits], u = {
116
+ ] : t.fontMetrics.physicalFontSize != null ? t.fontMetrics.physicalFontSize : [t.position.physicalHeight, t.position.physicalUnits], a = {
114
117
  outlineWidth: t.outlineColour != null ? t.font.outlineWidth : 0,
115
118
  letterSpacing: t.spacing?.letter_spacing ?? 0,
116
119
  letterSpacingOutline: t.spacing?.letter_spacing_outline ?? 0
117
- }, a = t.fillColour ? {
120
+ }, h = t.fillColour ? {
118
121
  hex: t.fillColour.hex,
119
122
  reference: t.fillColour.reference ?? void 0
120
- } : void 0, l = t.outlineColour ? {
123
+ } : void 0, u = t.outlineColour ? {
121
124
  hex: t.outlineColour.hex,
122
125
  reference: t.outlineColour.reference ?? void 0
123
126
  } : void 0;
124
- let h;
127
+ let r;
125
128
  if (t.font.fixedColours) {
126
129
  const f = {};
127
- for (const [n, d] of Object.entries(t.font.fixedColours))
128
- f[n] = { hex: d.hex, reference: d.reference };
129
- h = f;
130
+ for (const [d, e] of Object.entries(t.font.fixedColours))
131
+ f[d] = { hex: e.hex, reference: e.reference };
132
+ r = f;
130
133
  }
131
134
  return {
132
135
  text: t.text,
@@ -140,18 +143,18 @@ function F(t) {
140
143
  physicalHeight: t.position.physicalHeight,
141
144
  physicalUnits: t.position.physicalUnits
142
145
  },
143
- spacing: u,
146
+ spacing: a,
144
147
  verticalAlignment: _(
145
148
  t.verticalAlignment
146
149
  ),
147
- horizontalAlignment: o,
150
+ horizontalAlignment: l,
148
151
  baseline: i,
149
- fillColour: a,
150
- strokeColour: l,
151
- colourMap: h
152
+ fillColour: h,
153
+ strokeColour: u,
154
+ colourMap: r
152
155
  };
153
156
  }
154
157
  export {
155
- U as extractTextPlacements,
156
- F as mapStateToRendererOptions
158
+ F as extractTextPlacements,
159
+ H as mapStateToRendererOptions
157
160
  };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,3 @@
1
- import { default as default_2 } from 'opentype.js';
2
- import { SVGGlyphData } from '@unmade/svg-glyphs';
3
-
4
1
  export declare type BaselineConfig = {
5
2
  type: 'flat';
6
3
  } | {
@@ -67,7 +64,7 @@ export declare interface GenerateFlatBaselineOptions {
67
64
  lineNumber?: number;
68
65
  }
69
66
 
70
- export declare const getFontCache: () => Map<any, any>;
67
+ export declare const getFontCache: () => Map<string, LoadedFont>;
71
68
 
72
69
  /**
73
70
  * Get the unique colours used by a font's SVG glyphs.
@@ -147,10 +144,45 @@ export declare type HorizontalTextAlignmentValues = 'left' | 'centre' | 'right'
147
144
 
148
145
  export declare const LINE_HEIGHT = 1.1;
149
146
 
150
- export declare type LoadedFont = default_2.Font & LoadedFontExtras;
151
-
152
- declare interface LoadedFontExtras {
147
+ export declare interface LoadedFont {
148
+ unitsPerEm: number;
149
+ names: {
150
+ fontFamily: string;
151
+ };
153
152
  svgGlyphs?: Map<number, SVGGlyphData>;
153
+ charToGlyph(char: string): {
154
+ index: number;
155
+ advanceWidth: number;
156
+ name: string;
157
+ getBoundingBox(): {
158
+ x1: number;
159
+ y1: number;
160
+ x2: number;
161
+ y2: number;
162
+ };
163
+ };
164
+ getPath(text: string, x: number, y: number, fontSize: number, options?: {
165
+ drawSVG?: boolean;
166
+ }): {
167
+ getBoundingBox(): {
168
+ x1: number;
169
+ y1: number;
170
+ x2: number;
171
+ y2: number;
172
+ };
173
+ toPathData(decimals?: number): string;
174
+ };
175
+ getPaths(text: string, x: number, y: number, fontSize: number, options?: {
176
+ drawSVG?: boolean;
177
+ }): Array<{
178
+ getBoundingBox(): {
179
+ x1: number;
180
+ y1: number;
181
+ x2: number;
182
+ y2: number;
183
+ };
184
+ toPathData(decimals?: number): string;
185
+ }>;
154
186
  }
155
187
 
156
188
  export declare const loadOpentypeFont: (fontUrl: string, fetchSVGGlyphs?: boolean) => Promise<LoadedFont>;
@@ -186,6 +218,11 @@ export declare interface RenderTextOptions {
186
218
  colourMap?: ColourMap;
187
219
  }
188
220
 
221
+ declare interface SVGGlyphData {
222
+ svg: string;
223
+ viewbox: string;
224
+ }
225
+
189
226
  export declare interface SvgGlyphEntry {
190
227
  content: string;
191
228
  viewBox: string;