@snowcone-app/canvas 0.28.0 → 0.30.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.
Files changed (84) hide show
  1. package/ERROR_CONTRACT.md +13 -0
  2. package/dist/{CanvasRenderer-B_3GISlW.js → CanvasRenderer-BEf0hXeF.js} +1083 -1071
  3. package/dist/CanvasRenderer-BEf0hXeF.js.map +1 -0
  4. package/dist/{CanvasStateV1-kL09wsAn.js → CanvasStateV1-BDSyYo4A.js} +872 -844
  5. package/dist/{CanvasStateV1-kL09wsAn.js.map → CanvasStateV1-BDSyYo4A.js.map} +1 -1
  6. package/dist/ContextualToolbars-C3D_6sep.js +5960 -0
  7. package/dist/ContextualToolbars-C3D_6sep.js.map +1 -0
  8. package/dist/{EditorCanvasImpl-CrI9C_xM.js → EditorCanvasImpl-DXeBz2CX.js} +8 -8
  9. package/dist/{EditorCanvasImpl-CrI9C_xM.js.map → EditorCanvasImpl-DXeBz2CX.js.map} +1 -1
  10. package/dist/{ExportManager-J2xK86uq.js → ExportManager-DUdis6se.js} +1307 -970
  11. package/dist/ExportManager-DUdis6se.js.map +1 -0
  12. package/dist/{FontAnalyzer-PE6nysqE.js → FontAnalyzer-DKG5JK0x.js} +1419 -1342
  13. package/dist/FontAnalyzer-DKG5JK0x.js.map +1 -0
  14. package/dist/{ImagePanel-CmQCxBGm.js → ImagePanel-CBgGqmVV.js} +53 -53
  15. package/dist/{ImagePanel-CmQCxBGm.js.map → ImagePanel-CBgGqmVV.js.map} +1 -1
  16. package/dist/SnowconeCanvas-BWPBngvI.js +1192 -0
  17. package/dist/SnowconeCanvas-BWPBngvI.js.map +1 -0
  18. package/dist/{UnifiedFontService-BJzoqfBF.js → UnifiedFontService-Co9tQ59z.js} +869 -826
  19. package/dist/UnifiedFontService-Co9tQ59z.js.map +1 -0
  20. package/dist/advanced.mjs +14 -14
  21. package/dist/api/embed.d.ts +2 -0
  22. package/dist/api/stable.d.ts +2 -0
  23. package/dist/components/CanvasContextMenu.d.ts +15 -0
  24. package/dist/components/CanvasEditor/hooks/useCanvasInteraction.d.ts +27 -0
  25. package/dist/components/CanvasEditor.d.ts +8 -0
  26. package/dist/components/embed/SnowconeCanvas.d.ts +13 -0
  27. package/dist/components/embed/kernel.d.ts +8 -0
  28. package/dist/components/toolbars/SecondaryPanels.d.ts +11 -0
  29. package/dist/components/ui/icons.d.ts +2 -0
  30. package/dist/contexts/CanvasServicesContext.d.ts +17 -0
  31. package/dist/contexts/EditorContext.d.ts +7 -0
  32. package/dist/core/TextElement.d.ts +13 -0
  33. package/dist/core/TextMetrics.d.ts +17 -3
  34. package/dist/core/rich-text-layout.d.ts +2 -0
  35. package/dist/core/text-spacing.d.ts +74 -0
  36. package/dist/{custom-icons-BWiUGqD6.js → custom-icons-BIaRovwG.js} +2 -2
  37. package/dist/{custom-icons-BWiUGqD6.js.map → custom-icons-BIaRovwG.js.map} +1 -1
  38. package/dist/{design-builder-bquxIHct.js → design-builder-JYLO_Hyi.js} +2 -2
  39. package/dist/{design-builder-bquxIHct.js.map → design-builder-JYLO_Hyi.js.map} +1 -1
  40. package/dist/{editorErrors-8uDRMhow.js → editorErrors-WzaNxU1Y.js} +181 -194
  41. package/dist/editorErrors-WzaNxU1Y.js.map +1 -0
  42. package/dist/{embed-L3oRIgsj.js → embed-Csez1hui.js} +113 -112
  43. package/dist/{embed-L3oRIgsj.js.map → embed-Csez1hui.js.map} +1 -1
  44. package/dist/embed.mjs +51 -45
  45. package/dist/embed.mjs.map +1 -1
  46. package/dist/errors/editorError.d.ts +8 -0
  47. package/dist/{guards-BlbhM5MX.js → guards-BSoX73yg.js} +2 -2
  48. package/dist/{guards-BlbhM5MX.js.map → guards-BSoX73yg.js.map} +1 -1
  49. package/dist/hooks/useKeyboardShortcuts.d.ts +15 -2
  50. package/dist/hooks/useLayers.d.ts +2 -0
  51. package/dist/hooks/useTextToolbar.d.ts +8 -0
  52. package/dist/index.mjs +42 -37
  53. package/dist/index.mjs.map +1 -1
  54. package/dist/internals.mjs +47 -47
  55. package/dist/rendering/renderer-types.d.ts +4 -0
  56. package/dist/rendering/rich-text-renderer.d.ts +4 -0
  57. package/dist/rendering/text-renderer.d.ts +4 -1
  58. package/dist/state/CanvasStateV1.d.ts +34 -3
  59. package/dist/state/index.d.ts +2 -2
  60. package/dist/{template-fields-ai7vnW8g.js → template-fields-DYPW7bVU.js} +40 -40
  61. package/dist/{template-fields-ai7vnW8g.js.map → template-fields-DYPW7bVU.js.map} +1 -1
  62. package/dist/testing.mjs +13 -13
  63. package/dist/types/index.d.ts +26 -0
  64. package/dist/{usePieceGuideBundle-CMe5br2a.js → usePieceGuideBundle-CRLY2iQa.js} +2 -2
  65. package/dist/{usePieceGuideBundle-CMe5br2a.js.map → usePieceGuideBundle-CRLY2iQa.js.map} +1 -1
  66. package/dist/utils/custom-fonts.d.ts +99 -0
  67. package/dist/utils/featureApplied.d.ts +6 -0
  68. package/dist/validation-BGEl9rrE.js +8686 -0
  69. package/dist/validation-BGEl9rrE.js.map +1 -0
  70. package/dist/workers/export-protocol.d.ts +13 -0
  71. package/dist/workers/export-worker.bundle.string.d.ts +1 -1
  72. package/example/package.json +1 -1
  73. package/package.json +1 -1
  74. package/dist/CanvasRenderer-B_3GISlW.js.map +0 -1
  75. package/dist/ContextualToolbars-C69oYLdw.js +0 -5771
  76. package/dist/ContextualToolbars-C69oYLdw.js.map +0 -1
  77. package/dist/ExportManager-J2xK86uq.js.map +0 -1
  78. package/dist/FontAnalyzer-PE6nysqE.js.map +0 -1
  79. package/dist/SnowconeCanvas-CQiFVR0X.js +0 -1173
  80. package/dist/SnowconeCanvas-CQiFVR0X.js.map +0 -1
  81. package/dist/UnifiedFontService-BJzoqfBF.js.map +0 -1
  82. package/dist/editorErrors-8uDRMhow.js.map +0 -1
  83. package/dist/validation-B8kIYutI.js +0 -8397
  84. package/dist/validation-B8kIYutI.js.map +0 -1
package/dist/embed.mjs CHANGED
@@ -1,54 +1,60 @@
1
- import { C as a, a as r, b as t, u as i, d as o, f as n, g as d } from "./UnifiedFontService-BJzoqfBF.js";
2
- import { E as l, R as u, a as E, b as c, S as p, c as g, d as S, f, p as x, e as T, g as v, h as C, v as P } from "./embed-L3oRIgsj.js";
3
- import { g as B, i as h, u as y } from "./editorErrors-8uDRMhow.js";
4
- import { D as k, d as D, a as I, i as F } from "./design-builder-bquxIHct.js";
5
- import { D as w, T as M, h as A, i as G, j as K, k as _, l as j, m as z, r as N, u as Y, a as q, b as H, c as J, d as O, e as Q, f as U, w as V } from "./template-fields-ai7vnW8g.js";
6
- import { s as X } from "./CanvasStateV1-kL09wsAn.js";
1
+ import { C as a, a as t, b as r, u as o, d as i, f as n, g as m } from "./UnifiedFontService-Co9tQ59z.js";
2
+ import { E as u, R as l, a as E, b as c, S as p, c as g, d as f, f as x, p as S, e as C, g as T, h as v, v as y } from "./embed-Csez1hui.js";
3
+ import { E as P, y as b } from "./FontAnalyzer-DKG5JK0x.js";
4
+ import { D as R, d as h, a as k, i as D } from "./design-builder-JYLO_Hyi.js";
5
+ import { u as L } from "./editorErrors-WzaNxU1Y.js";
6
+ import { D as M, T as A, h as G, i as K, j as _, k as j, l as z, m as N, r as Y, u as q, a as H, b as J, c as O, d as Q, e as U, f as V, w as W } from "./template-fields-DYPW7bVU.js";
7
+ import { c as Z, e as $, l as ee, r as se } from "./ExportManager-DUdis6se.js";
8
+ import { s as te } from "./CanvasStateV1-BDSyYo4A.js";
7
9
  export {
8
10
  a as CanvasServicesProvider,
9
- k as DesignBuilder,
10
- w as DesignStateStore,
11
- l as Editor,
12
- B as EditorError,
13
- u as RealtimeCanvas,
11
+ R as DesignBuilder,
12
+ M as DesignStateStore,
13
+ u as Editor,
14
+ P as EditorError,
15
+ l as RealtimeCanvas,
14
16
  E as RealtimeEmbed,
15
17
  c as RealtimeMockup,
16
18
  p as SnowconeEditor,
17
- M as TEMPLATE_FIELDS_KEY,
19
+ A as TEMPLATE_FIELDS_KEY,
18
20
  g as auditEditor,
19
- S as collectEmbedIssues,
20
- r as createProxyGenerate,
21
- t as createProxyImageSearch,
22
- D as design,
23
- A as editorElementKind,
24
- f as formatBuyPrice,
25
- I as isColorToken,
26
- h as isEditorError,
27
- G as isGroupElement,
28
- K as isImageElement,
29
- F as isLayerToken,
30
- _ as isPathElement,
31
- j as isShapeElement,
32
- z as isTextElement,
33
- x as productToRenderConfig,
34
- N as readTemplateFields,
35
- X as serializeState,
36
- i as useArtboards,
37
- o as useCanvasServices,
38
- Y as useColorBinding,
39
- q as useCommands,
40
- y as useEditor,
41
- H as useElementByName,
21
+ f as collectEmbedIssues,
22
+ t as createProxyGenerate,
23
+ r as createProxyImageSearch,
24
+ Z as customFontRegistry,
25
+ h as design,
26
+ G as editorElementKind,
27
+ $ as ensureCustomFontsLoaded,
28
+ x as formatBuyPrice,
29
+ k as isColorToken,
30
+ b as isEditorError,
31
+ K as isGroupElement,
32
+ _ as isImageElement,
33
+ D as isLayerToken,
34
+ j as isPathElement,
35
+ z as isShapeElement,
36
+ N as isTextElement,
37
+ ee as loadCustomFont,
38
+ S as productToRenderConfig,
39
+ Y as readTemplateFields,
40
+ se as registerCustomFonts,
41
+ te as serializeState,
42
+ o as useArtboards,
43
+ i as useCanvasServices,
44
+ q as useColorBinding,
45
+ H as useCommands,
46
+ L as useEditor,
47
+ J as useElementByName,
42
48
  n as useExport,
43
- J as useFontBinding,
44
- O as useImageBinding,
45
- d as useLayers,
46
- T as usePlacements,
47
- v as useProductSelection,
48
- C as useRealtimeMockups,
49
- Q as useSelectedElement,
50
- U as useTextBinding,
51
- P as validateEmbed,
52
- V as withTemplateFields
49
+ O as useFontBinding,
50
+ Q as useImageBinding,
51
+ m as useLayers,
52
+ C as usePlacements,
53
+ T as useProductSelection,
54
+ v as useRealtimeMockups,
55
+ U as useSelectedElement,
56
+ V as useTextBinding,
57
+ y as validateEmbed,
58
+ W as withTemplateFields
53
59
  };
54
60
  //# sourceMappingURL=embed.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"embed.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;"}
1
+ {"version":3,"file":"embed.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
@@ -88,6 +88,14 @@ export type EditorErrorCode =
88
88
  /** `auditEditor()` called with no `<Editor.Root>` mounted — the imperative
89
89
  * audit reads the most-recent mounted Root's registry (#10). */
90
90
  | 'E_AUDIT_NO_ROOT'
91
+ /** A custom (brand) font failed to load from its hosted URL — the binary
92
+ * fetch failed (CORS, 404, network) or FontFace rejected the bytes.
93
+ * Custom fonts NEVER fall back silently to a system face: the design
94
+ * would render with a different typeface/metrics everywhere it's
95
+ * consumed. `data` carries `family`, `url`, `weight`, `style`. Fix the
96
+ * URL (must serve the font file over a plain cross-origin GET) and
97
+ * re-register. Dev AND prod. */
98
+ | 'E_FONT_LOAD_FAILED'
91
99
  /** `computeDistribute()` called with fewer than 3 movable elements —
92
100
  * distributing needs something between the two ends. Gate the action on
93
101
  * `useAlignDistribute().canDistribute` instead of calling below the
@@ -1,4 +1,4 @@
1
- import { G as n, I as t, P as a, S as s, T as i } from "./FontAnalyzer-PE6nysqE.js";
1
+ import { G as n, I as t, P as a, S as s, T as i } from "./FontAnalyzer-DKG5JK0x.js";
2
2
  function r(e) {
3
3
  return e instanceof i;
4
4
  }
@@ -21,4 +21,4 @@ export {
21
21
  r as d,
22
22
  u as i
23
23
  };
24
- //# sourceMappingURL=guards-BlbhM5MX.js.map
24
+ //# sourceMappingURL=guards-BSoX73yg.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"guards-BlbhM5MX.js","sources":["../src/types/guards.ts"],"sourcesContent":["/**\n * Type guards for EditorElement discrimination\n *\n * These guards use instanceof checks against the concrete element classes,\n * matching the pattern established in EditorContext.tsx. They accept\n * BaseElement | EditorElement | undefined | null for maximum flexibility\n * at call sites.\n */\n\nimport { BaseElement } from '../core/BaseElement.js';\nimport { TextElement } from '../core/TextElement.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport { GroupElement } from '../core/GroupElement.js';\nimport { ShapeElement } from '../core/ShapeElement.js';\nimport { PathElement } from '../core/PathElement.js';\n\ntype Narrowable = BaseElement | TextElement | ImageElement | GroupElement | ShapeElement | PathElement | undefined | null;\n\n/**\n * Type guard: checks if an element is a TextElement (or any text-based transform subclass).\n *\n * TextElement is the base class for all text transforms (custom, circle, arch, wave, etc.),\n * so this returns true for any text-based element regardless of its specific transformType.\n */\nexport function isTextElement(element: Narrowable): element is TextElement {\n return element instanceof TextElement;\n}\n\n/**\n * Type guard: checks if an element is an ImageElement.\n */\nexport function isImageElement(element: Narrowable): element is ImageElement {\n return element instanceof ImageElement;\n}\n\n/**\n * Type guard: checks if an element is a GroupElement.\n */\nexport function isGroupElement(element: Narrowable): element is GroupElement {\n return element instanceof GroupElement;\n}\n\n/**\n * Type guard: checks if an element is a ShapeElement.\n */\nexport function isShapeElement(element: Narrowable): element is ShapeElement {\n return element instanceof ShapeElement;\n}\n\n/**\n * Type guard: checks if an element is a PathElement.\n */\nexport function isPathElement(element: Narrowable): element is PathElement {\n return element instanceof PathElement;\n}\n"],"names":["isTextElement","element","TextElement","isImageElement","ImageElement","isGroupElement","GroupElement","isShapeElement","ShapeElement","isPathElement","PathElement"],"mappings":";AAwBO,SAASA,EAAcC,GAA6C;AACzE,SAAOA,aAAmBC;AAC5B;AAKO,SAASC,EAAeF,GAA8C;AAC3E,SAAOA,aAAmBG;AAC5B;AAKO,SAASC,EAAeJ,GAA8C;AAC3E,SAAOA,aAAmBK;AAC5B;AAKO,SAASC,EAAeN,GAA8C;AAC3E,SAAOA,aAAmBO;AAC5B;AAKO,SAASC,EAAcR,GAA6C;AACzE,SAAOA,aAAmBS;AAC5B;"}
1
+ {"version":3,"file":"guards-BSoX73yg.js","sources":["../src/types/guards.ts"],"sourcesContent":["/**\n * Type guards for EditorElement discrimination\n *\n * These guards use instanceof checks against the concrete element classes,\n * matching the pattern established in EditorContext.tsx. They accept\n * BaseElement | EditorElement | undefined | null for maximum flexibility\n * at call sites.\n */\n\nimport { BaseElement } from '../core/BaseElement.js';\nimport { TextElement } from '../core/TextElement.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport { GroupElement } from '../core/GroupElement.js';\nimport { ShapeElement } from '../core/ShapeElement.js';\nimport { PathElement } from '../core/PathElement.js';\n\ntype Narrowable = BaseElement | TextElement | ImageElement | GroupElement | ShapeElement | PathElement | undefined | null;\n\n/**\n * Type guard: checks if an element is a TextElement (or any text-based transform subclass).\n *\n * TextElement is the base class for all text transforms (custom, circle, arch, wave, etc.),\n * so this returns true for any text-based element regardless of its specific transformType.\n */\nexport function isTextElement(element: Narrowable): element is TextElement {\n return element instanceof TextElement;\n}\n\n/**\n * Type guard: checks if an element is an ImageElement.\n */\nexport function isImageElement(element: Narrowable): element is ImageElement {\n return element instanceof ImageElement;\n}\n\n/**\n * Type guard: checks if an element is a GroupElement.\n */\nexport function isGroupElement(element: Narrowable): element is GroupElement {\n return element instanceof GroupElement;\n}\n\n/**\n * Type guard: checks if an element is a ShapeElement.\n */\nexport function isShapeElement(element: Narrowable): element is ShapeElement {\n return element instanceof ShapeElement;\n}\n\n/**\n * Type guard: checks if an element is a PathElement.\n */\nexport function isPathElement(element: Narrowable): element is PathElement {\n return element instanceof PathElement;\n}\n"],"names":["isTextElement","element","TextElement","isImageElement","ImageElement","isGroupElement","GroupElement","isShapeElement","ShapeElement","isPathElement","PathElement"],"mappings":";AAwBO,SAASA,EAAcC,GAA6C;AACzE,SAAOA,aAAmBC;AAC5B;AAKO,SAASC,EAAeF,GAA8C;AAC3E,SAAOA,aAAmBG;AAC5B;AAKO,SAASC,EAAeJ,GAA8C;AAC3E,SAAOA,aAAmBK;AAC5B;AAKO,SAASC,EAAeN,GAA8C;AAC3E,SAAOA,aAAmBO;AAC5B;AAKO,SAASC,EAAcR,GAA6C;AACzE,SAAOA,aAAmBS;AAC5B;"}
@@ -1,7 +1,20 @@
1
+ import { useEditor } from '../contexts/EditorContext.js';
1
2
  /**
2
- * useKeyboardShortcuts - Hook for handling keyboard shortcuts in the editor
3
- * Provides copy/paste and other keyboard shortcuts
3
+ * Select every element on the ACTIVE artboard (multi-selection).
4
+ *
5
+ * Shared implementation behind Cmd/Ctrl+A AND the canvas context menu's
6
+ * "Select All" entry — one definition so the two surfaces can't drift.
7
+ * Locked elements are included — locking gates manipulation, not selection
8
+ * (gotcha #20); hidden elements are skipped to mirror marquee selection.
9
+ * Same state path as marquee selection: the multi-selection array is the
10
+ * source of truth; single selection is cleared alongside it.
4
11
  */
12
+ export declare function selectAllOnActiveArtboard(opts: {
13
+ elements: ReturnType<typeof useEditor>['elements'];
14
+ artboardManager: ReturnType<typeof useEditor>['artboardManager'];
15
+ handleSelectionChange: (id: string | null) => void;
16
+ setMultiSelection: (ids: string[]) => void;
17
+ }): void;
5
18
  /**
6
19
  * Hook that sets up keyboard shortcuts for the editor
7
20
  * Handles copy/paste, delete, undo/redo, and other shortcuts
@@ -40,6 +40,8 @@ export interface UseLayersReturn {
40
40
  reorderWithinGroup: (draggedId: string, targetId: string, position: 'before' | 'after', groupId: string) => void;
41
41
  moveLayerForward: (id: string) => void;
42
42
  moveLayerBackward: (id: string) => void;
43
+ bringToFront: (id: string) => void;
44
+ sendToBack: (id: string) => void;
43
45
  groupLayers: (ids: string[]) => void;
44
46
  ungroupLayer: (id: string) => void;
45
47
  addToGroup: (elementId: string, groupId: string) => void;
@@ -12,8 +12,16 @@ export interface UseTextToolbarReturn {
12
12
  italic: boolean;
13
13
  underline: boolean;
14
14
  letterCase: string;
15
+ /** Letter-spacing in px per character (element-level default; 0 = none). */
16
+ letterSpacing: number;
17
+ /** Line-height multiplier (1.2 = the layout default). */
18
+ lineHeight: number;
15
19
  handleFontSizeChange: (size: number) => void;
16
20
  handleFontSizeIncrement: (delta: number) => void;
21
+ /** Per-character aware (applies to the selection while editing text). */
22
+ handleLetterSpacingChange: (spacing: number) => void;
23
+ /** Element-level only (line layout is an element concern, not a span one). */
24
+ handleLineHeightChange: (lineHeight: number) => void;
17
25
  handleFontFamilyChange: (family: string) => void;
18
26
  handleFontColorChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
19
27
  handleBoldToggle: () => void;
package/dist/index.mjs CHANGED
@@ -1,64 +1,69 @@
1
- import { e as u } from "./UnifiedFontService-BJzoqfBF.js";
2
- import { C as l, c as x, a as I, b as v, u as A, d as T, f as g, g as B } from "./UnifiedFontService-BJzoqfBF.js";
3
- import { E as _, S as y, S as L, u as O, a as M } from "./SnowconeCanvas-CQiFVR0X.js";
4
- import { u as d, a as m } from "./editorErrors-8uDRMhow.js";
5
- import { T as z, r as w, u as F, a as D, b as K, c as N, d as V, e as b, f as h, w as U } from "./template-fields-ai7vnW8g.js";
6
- import { A as Z, C as G, E as H, M as j, P as k, r as q, v as J } from "./validation-B8kIYutI.js";
7
- import { C as W, D as X, d as $, s as ee } from "./design-builder-bquxIHct.js";
8
- import { c as se, e as te } from "./compose-DtQL_oit.js";
9
- import { d as oe, m as ie, s as ne, v as ue } from "./CanvasStateV1-kL09wsAn.js";
10
- function C() {
11
- const { isCanvasReady: e } = d();
1
+ import { e as u } from "./UnifiedFontService-Co9tQ59z.js";
2
+ import { C as p, c as x, a as I, b as v, u as A, d as g, f as T, g as B } from "./UnifiedFontService-Co9tQ59z.js";
3
+ import { E as y, S as F, S as L, u as _, a as O } from "./SnowconeCanvas-BWPBngvI.js";
4
+ import { u as m, a as d } from "./editorErrors-WzaNxU1Y.js";
5
+ import { T as M, r as z, u as w, a as D, b as K, c as N, d as V, e as b, f as h, w as U } from "./template-fields-DYPW7bVU.js";
6
+ import { A as Z, C as G, E as H, M as j, P as k, r as q, v as J } from "./validation-BGEl9rrE.js";
7
+ import { c as W, e as X, l as $, r as ee } from "./ExportManager-DUdis6se.js";
8
+ import { C as ae, D as te, d as oe, s as re } from "./design-builder-JYLO_Hyi.js";
9
+ import { c as ie, e as ue } from "./compose-DtQL_oit.js";
10
+ import { d as de, m as Ce, s as Se, v as Ee } from "./CanvasStateV1-BDSyYo4A.js";
11
+ function E() {
12
+ const { isCanvasReady: e } = m();
12
13
  return e;
13
14
  }
14
- function f() {
15
- const { zoom: e, panOffset: a, zoomIn: s, zoomOut: t, zoomToFit: r, resetView: o, setZoom: i, setPanOffset: n } = m();
16
- return { zoom: e, panOffset: a, zoomIn: s, zoomOut: t, zoomToFit: r, resetView: o, setZoom: i, setPanOffset: n };
15
+ function c() {
16
+ const { zoom: e, panOffset: s, zoomIn: a, zoomOut: t, zoomToFit: o, resetView: r, setZoom: n, setPanOffset: i } = d();
17
+ return { zoom: e, panOffset: s, zoomIn: a, zoomOut: t, zoomToFit: o, resetView: r, setZoom: n, setPanOffset: i };
17
18
  }
18
19
  u();
19
20
  export {
20
21
  Z as ALL_CAPABILITIES,
21
- W as CANVAS_STATE_SCHEMA_VERSION,
22
+ ae as CANVAS_STATE_SCHEMA_VERSION,
22
23
  G as COMPACT_CUSTOMIZER,
23
- l as CanvasServicesProvider,
24
- X as DesignBuilder,
24
+ p as CanvasServicesProvider,
25
+ te as DesignBuilder,
25
26
  H as EMBED_ONLY,
26
- _ as ErrorBoundary,
27
+ y as ErrorBoundary,
27
28
  j as MINIMAL_CAPABILITIES,
28
29
  k as PRO_STUDIO,
29
- y as SnowconeCanvas,
30
- z as TEMPLATE_FIELDS_KEY,
31
- se as createKit,
30
+ F as SnowconeCanvas,
31
+ M as TEMPLATE_FIELDS_KEY,
32
+ ie as createKit,
32
33
  x as createProxyBgRemove,
33
34
  I as createProxyGenerate,
34
35
  v as createProxyImageSearch,
36
+ W as customFontRegistry,
35
37
  L as default,
36
- oe as deserializeState,
37
- $ as design,
38
- te as extendKit,
39
- ie as migrateState,
40
- w as readTemplateFields,
38
+ de as deserializeState,
39
+ oe as design,
40
+ X as ensureCustomFontsLoaded,
41
+ ue as extendKit,
42
+ $ as loadCustomFont,
43
+ Ce as migrateState,
44
+ z as readTemplateFields,
45
+ ee as registerCustomFonts,
41
46
  q as resolveKit,
42
- ne as serializeState,
43
- ee as serializeStateForServer,
47
+ Se as serializeState,
48
+ re as serializeStateForServer,
44
49
  A as useArtboards,
45
- O as useAutoExport,
46
- C as useCanvasReady,
47
- T as useCanvasServices,
48
- F as useColorBinding,
50
+ _ as useAutoExport,
51
+ E as useCanvasReady,
52
+ g as useCanvasServices,
53
+ w as useColorBinding,
49
54
  D as useCommands,
50
- M as useContentReady,
51
- d as useEditor,
55
+ O as useContentReady,
56
+ m as useEditor,
52
57
  K as useElementByName,
53
- g as useExport,
58
+ T as useExport,
54
59
  N as useFontBinding,
55
60
  V as useImageBinding,
56
61
  B as useLayers,
57
62
  b as useSelectedElement,
58
63
  h as useTextBinding,
59
- f as useViewport,
64
+ c as useViewport,
60
65
  J as validateKit,
61
- ue as validateState,
66
+ Ee as validateState,
62
67
  U as withTemplateFields
63
68
  };
64
69
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../src/hooks/useCanvasReady.ts","../src/hooks/useViewport.ts"],"sourcesContent":["import { useEditor } from '../contexts/EditorContext.js';\n\n/**\n * Returns whether the canvas is fully initialized and ready for operations.\n *\n * The canvas is not ready until the underlying `<canvas>` element is mounted\n * and the editor has completed its initial setup. Use this to gate operations\n * like export or programmatic element manipulation that require a live canvas.\n *\n * @returns `true` when the canvas is mounted and ready, `false` otherwise.\n *\n * @example\n * ```tsx\n * function ExportButton() {\n * const ready = useCanvasReady();\n * return (\n * <button disabled={!ready} onClick={handleExport}>\n * Export PNG\n * </button>\n * );\n * }\n * ```\n */\nexport function useCanvasReady(): boolean {\n const { isCanvasReady } = useEditor();\n return isCanvasReady;\n}\n","import React from 'react';\nimport { useViewportContext } from '../contexts/ViewportContext.js';\nimport type { PanOffset } from '../contexts/ViewportContext.js';\n\n/**\n * Return type for {@link useViewport}.\n */\nexport interface UseViewportReturn {\n /** Current zoom level (1.0 = 100%) */\n zoom: number;\n /** Current pan offset in world coordinates */\n panOffset: PanOffset;\n /** Increment zoom by one step */\n zoomIn: () => void;\n /** Decrement zoom by one step */\n zoomOut: () => void;\n /** Zoom to fit the active artboard in the viewport */\n zoomToFit: () => void;\n /** Reset zoom to 100% and pan offset to origin */\n resetView: () => void;\n /** Set zoom to an exact value */\n setZoom: React.Dispatch<React.SetStateAction<number>>;\n /** Set pan offset to an exact value */\n setPanOffset: React.Dispatch<React.SetStateAction<PanOffset>>;\n}\n\n/**\n * Returns viewport state (zoom, pan) and control functions.\n *\n * This hook now reads directly from ViewportContext (instead of the full\n * EditorContext), so components using it will only re-render when viewport\n * state changes -- not when elements, selection, or other state changes.\n *\n * @example\n * ```tsx\n * function ZoomBar() {\n * const { zoom, zoomIn, zoomOut, zoomToFit } = useViewport();\n * return (\n * <div>\n * <button onClick={zoomOut}>-</button>\n * <span>{Math.round(zoom * 100)}%</span>\n * <button onClick={zoomIn}>+</button>\n * <button onClick={zoomToFit}>Fit</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useViewport(): UseViewportReturn {\n const { zoom, panOffset, zoomIn, zoomOut, zoomToFit, resetView, setZoom, setPanOffset } = useViewportContext();\n return { zoom, panOffset, zoomIn, zoomOut, zoomToFit, resetView, setZoom, setPanOffset };\n}\n"],"names":["useCanvasReady","isCanvasReady","useEditor","useViewport","zoom","panOffset","zoomIn","zoomOut","zoomToFit","resetView","setZoom","setPanOffset","useViewportContext"],"mappings":";;;;;;;;;AAuBO,SAASA,IAA0B;AACxC,QAAM,EAAE,eAAAC,EAAA,IAAkBC,EAAA;AAC1B,SAAOD;AACT;ACsBO,SAASE,IAAiC;AAC/C,QAAM,EAAE,MAAAC,GAAM,WAAAC,GAAW,QAAAC,GAAQ,SAAAC,GAAS,WAAAC,GAAW,WAAAC,GAAW,SAAAC,GAAS,cAAAC,EAAA,IAAiBC,EAAA;AAC1F,SAAO,EAAE,MAAAR,GAAM,WAAAC,GAAW,QAAAC,GAAQ,SAAAC,GAAS,WAAAC,GAAW,WAAAC,GAAW,SAAAC,GAAS,cAAAC,EAAA;AAC5E;;"}
1
+ {"version":3,"file":"index.mjs","sources":["../src/hooks/useCanvasReady.ts","../src/hooks/useViewport.ts"],"sourcesContent":["import { useEditor } from '../contexts/EditorContext.js';\n\n/**\n * Returns whether the canvas is fully initialized and ready for operations.\n *\n * The canvas is not ready until the underlying `<canvas>` element is mounted\n * and the editor has completed its initial setup. Use this to gate operations\n * like export or programmatic element manipulation that require a live canvas.\n *\n * @returns `true` when the canvas is mounted and ready, `false` otherwise.\n *\n * @example\n * ```tsx\n * function ExportButton() {\n * const ready = useCanvasReady();\n * return (\n * <button disabled={!ready} onClick={handleExport}>\n * Export PNG\n * </button>\n * );\n * }\n * ```\n */\nexport function useCanvasReady(): boolean {\n const { isCanvasReady } = useEditor();\n return isCanvasReady;\n}\n","import React from 'react';\nimport { useViewportContext } from '../contexts/ViewportContext.js';\nimport type { PanOffset } from '../contexts/ViewportContext.js';\n\n/**\n * Return type for {@link useViewport}.\n */\nexport interface UseViewportReturn {\n /** Current zoom level (1.0 = 100%) */\n zoom: number;\n /** Current pan offset in world coordinates */\n panOffset: PanOffset;\n /** Increment zoom by one step */\n zoomIn: () => void;\n /** Decrement zoom by one step */\n zoomOut: () => void;\n /** Zoom to fit the active artboard in the viewport */\n zoomToFit: () => void;\n /** Reset zoom to 100% and pan offset to origin */\n resetView: () => void;\n /** Set zoom to an exact value */\n setZoom: React.Dispatch<React.SetStateAction<number>>;\n /** Set pan offset to an exact value */\n setPanOffset: React.Dispatch<React.SetStateAction<PanOffset>>;\n}\n\n/**\n * Returns viewport state (zoom, pan) and control functions.\n *\n * This hook now reads directly from ViewportContext (instead of the full\n * EditorContext), so components using it will only re-render when viewport\n * state changes -- not when elements, selection, or other state changes.\n *\n * @example\n * ```tsx\n * function ZoomBar() {\n * const { zoom, zoomIn, zoomOut, zoomToFit } = useViewport();\n * return (\n * <div>\n * <button onClick={zoomOut}>-</button>\n * <span>{Math.round(zoom * 100)}%</span>\n * <button onClick={zoomIn}>+</button>\n * <button onClick={zoomToFit}>Fit</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useViewport(): UseViewportReturn {\n const { zoom, panOffset, zoomIn, zoomOut, zoomToFit, resetView, setZoom, setPanOffset } = useViewportContext();\n return { zoom, panOffset, zoomIn, zoomOut, zoomToFit, resetView, setZoom, setPanOffset };\n}\n"],"names":["useCanvasReady","isCanvasReady","useEditor","useViewport","zoom","panOffset","zoomIn","zoomOut","zoomToFit","resetView","setZoom","setPanOffset","useViewportContext"],"mappings":";;;;;;;;;;AAuBO,SAASA,IAA0B;AACxC,QAAM,EAAE,eAAAC,EAAA,IAAkBC,EAAA;AAC1B,SAAOD;AACT;ACsBO,SAASE,IAAiC;AAC/C,QAAM,EAAE,MAAAC,GAAM,WAAAC,GAAW,QAAAC,GAAQ,SAAAC,GAAS,WAAAC,GAAW,WAAAC,GAAW,SAAAC,GAAS,cAAAC,EAAA,IAAiBC,EAAA;AAC1F,SAAO,EAAE,MAAAR,GAAM,WAAAC,GAAW,QAAAC,GAAQ,SAAAC,GAAS,WAAAC,GAAW,WAAAC,GAAW,SAAAC,GAAS,cAAAC,EAAA;AAC5E;;"}
@@ -1,8 +1,8 @@
1
- import { A as w, a as b, C as I, I as R, R as W, S as A, T as E } from "./CanvasRenderer-B_3GISlW.js";
2
- import { r as p } from "./ExportManager-J2xK86uq.js";
3
- import { D as O, E as F, I as L, W as X, a as z, b as K, c as Y, d as B, e as _, f as J, h as N, i as V, j, k as q, l as Q, m as U, n as Z, o as $, p as m, q as G, s as x, t as D, u as rr, v as or, w as sr, x as ir, y as er, z as ar, A as nr, B as tr, C as hr } from "./ExportManager-J2xK86uq.js";
4
- import { D as cr, E as fr, H as pr, J as gr, K as lr, L as ur, M as Tr, N as kr, O as Mr, Q as yr, W as Cr, V as vr, X as Sr, Y as Pr, c as wr, Z as br, _ as Ir, $ as Rr, a0 as Wr, a1 as Ar } from "./FontAnalyzer-PE6nysqE.js";
5
- import { A as Hr, e as Or, C as Fr, S as Lr } from "./CanvasStateV1-kL09wsAn.js";
1
+ import { A as w, a as b, C as I, I as R, R as W, S as A, T as E } from "./CanvasRenderer-BEf0hXeF.js";
2
+ import { a as p } from "./ExportManager-DUdis6se.js";
3
+ import { D as O, E as F, I as L, W as X, b as z, d as K, f as Y, h as B, i as J, j as _, k as N, m as V, n as j, o as q, p as G, q as Q, s as U, t as Z, u as $, v as m, w as x, x as D, y as rr, z as or, A as sr, B as ir, C as ar, F as er, G as nr, H as tr, J as hr } from "./ExportManager-DUdis6se.js";
4
+ import { J as cr, K as fr, L as pr, M as gr, N as lr, O as ur, Q as Tr, V as kr, W as Mr, X as yr, Y as Cr, Z as vr, _ as Sr, $ as Pr, c as wr, a0 as br, a1 as Ir, a2 as Rr, a3 as Wr, a4 as Ar } from "./FontAnalyzer-DKG5JK0x.js";
5
+ import { A as Hr, e as Or, C as Fr, S as Lr } from "./CanvasStateV1-BDSyYo4A.js";
6
6
  function y(r, o) {
7
7
  return !r.needsCropPixelConversion || r.cropWidth === void 0 ? r : {
8
8
  ...r,
@@ -14,10 +14,10 @@ function y(r, o) {
14
14
  };
15
15
  }
16
16
  function C(r, o, c) {
17
- var a, n;
17
+ var e, n;
18
18
  r.save(), r.translate(o.x, o.y), o.rotation && r.rotate(-o.rotation * Math.PI / 180);
19
- let e = 1, h = 1;
20
- if (o.flipHorizontal && (e = -1), o.flipVertical && (h = -1), (e !== 1 || h !== 1) && r.scale(e, h), o.opacity !== void 0 && o.opacity !== 1 && (r.globalAlpha = o.opacity), o.borderRadius && o.borderRadius > 0) {
19
+ let a = 1, h = 1;
20
+ if (o.flipHorizontal && (a = -1), o.flipVertical && (h = -1), (a !== 1 || h !== 1) && r.scale(a, h), o.opacity !== void 0 && o.opacity !== 1 && (r.globalAlpha = o.opacity), o.borderRadius && o.borderRadius > 0) {
21
21
  const i = Math.min(
22
22
  o.borderRadius / 100 * Math.min(o.width, o.height),
23
23
  o.width / 2,
@@ -37,7 +37,7 @@ function C(r, o, c) {
37
37
  -o.height / 2,
38
38
  o.width,
39
39
  o.height
40
- ) : r.drawImage(c, -o.width / 2, -o.height / 2, o.width, o.height), r.restore(), (a = o.stroke) != null && a.enabled) {
40
+ ) : r.drawImage(c, -o.width / 2, -o.height / 2, o.width, o.height), r.restore(), (e = o.stroke) != null && e.enabled) {
41
41
  const i = ((n = o.knockoutParts) == null ? void 0 : n.stroke) === !0, s = {
42
42
  x: o.x,
43
43
  y: o.y,
@@ -79,13 +79,13 @@ function T(r, o) {
79
79
  return c.width = r, c.height = o, c;
80
80
  }
81
81
  function v(r, o) {
82
- var a;
83
- const c = o.opacity ?? 1, e = !!((a = o.stroke) != null && a.enabled);
84
- c < 1 && e ? k(r, o, c) : g(r, o, c);
82
+ var e;
83
+ const c = o.opacity ?? 1, a = !!((e = o.stroke) != null && e.enabled);
84
+ c < 1 && a ? k(r, o, c) : g(r, o, c);
85
85
  }
86
86
  function k(r, o, c) {
87
87
  var f;
88
- const e = o.transformData, a = (((f = o.stroke) == null ? void 0 : f.width) || 2) + 2, n = Math.ceil(e.width + a * 2), i = Math.ceil(e.height + a * 2), s = T(n, i), t = s.getContext("2d");
88
+ const a = o.transformData, e = (((f = o.stroke) == null ? void 0 : f.width) || 2) + 2, n = Math.ceil(a.width + e * 2), i = Math.ceil(a.height + e * 2), s = T(n, i), t = s.getContext("2d");
89
89
  if (!t) {
90
90
  g(r, o, c);
91
91
  return;
@@ -98,65 +98,65 @@ function k(r, o, c) {
98
98
  g(t, d, 1), r.save(), r.translate(o.x, o.y), o.rotation && r.rotate(-o.rotation * Math.PI / 180), r.globalAlpha = c, r.drawImage(s, -n / 2, -i / 2), r.restore();
99
99
  }
100
100
  function g(r, o, c) {
101
- var a;
102
- const e = o.transformData, h = e.fillOpacity ?? 1;
103
- if (r.save(), r.translate(o.x, o.y), o.rotation && r.rotate(-o.rotation * Math.PI / 180), r.globalAlpha = c * h, r.fillStyle = e.fillColor || "#3b82f6", r.beginPath(), u(r, e), r.fill(), r.restore(), (a = o.stroke) != null && a.enabled) {
101
+ var e;
102
+ const a = o.transformData, h = a.fillOpacity ?? 1;
103
+ if (r.save(), r.translate(o.x, o.y), o.rotation && r.rotate(-o.rotation * Math.PI / 180), r.globalAlpha = c * h, r.fillStyle = a.fillColor || "#3b82f6", r.beginPath(), u(r, a), r.fill(), r.restore(), (e = o.stroke) != null && e.enabled) {
104
104
  r.save(), r.translate(o.x, o.y), o.rotation && r.rotate(-o.rotation * Math.PI / 180);
105
105
  const n = o.stroke;
106
- r.globalAlpha = c * (n.opacity ?? 1), r.strokeStyle = n.color || "#000000", r.lineWidth = n.width || 2, r.lineCap = n.lineCap || "round", r.lineJoin = n.lineJoin || "round", r.beginPath(), u(r, e), r.stroke(), r.restore();
106
+ r.globalAlpha = c * (n.opacity ?? 1), r.strokeStyle = n.color || "#000000", r.lineWidth = n.width || 2, r.lineCap = n.lineCap || "round", r.lineJoin = n.lineJoin || "round", r.beginPath(), u(r, a), r.stroke(), r.restore();
107
107
  }
108
108
  }
109
109
  function u(r, o) {
110
- const { shapeType: c, width: e, height: h } = o;
110
+ const { shapeType: c, width: a, height: h } = o;
111
111
  switch (c) {
112
112
  case "rectangle": {
113
- const a = o.borderRadius || 0, n = -e / 2, i = -h / 2;
114
- if (a > 0) {
115
- const s = Math.min(a / 100 * Math.min(e, h), e / 2, h / 2);
116
- r.roundRect(n, i, e, h, s);
113
+ const e = o.borderRadius || 0, n = -a / 2, i = -h / 2;
114
+ if (e > 0) {
115
+ const s = Math.min(e / 100 * Math.min(a, h), a / 2, h / 2);
116
+ r.roundRect(n, i, a, h, s);
117
117
  } else
118
- r.rect(n, i, e, h);
118
+ r.rect(n, i, a, h);
119
119
  break;
120
120
  }
121
121
  case "circle": {
122
- const a = Math.min(e, h) / 2;
123
- r.arc(0, 0, a, 0, Math.PI * 2);
122
+ const e = Math.min(a, h) / 2;
123
+ r.arc(0, 0, e, 0, Math.PI * 2);
124
124
  break;
125
125
  }
126
126
  case "ellipse": {
127
- r.ellipse(0, 0, e / 2, h / 2, 0, 0, Math.PI * 2);
127
+ r.ellipse(0, 0, a / 2, h / 2, 0, 0, Math.PI * 2);
128
128
  break;
129
129
  }
130
130
  case "triangle": {
131
- const a = e / 2, n = h / 2;
132
- r.moveTo(0, -n), r.lineTo(a, n), r.lineTo(-a, n), r.closePath();
131
+ const e = a / 2, n = h / 2;
132
+ r.moveTo(0, -n), r.lineTo(e, n), r.lineTo(-e, n), r.closePath();
133
133
  break;
134
134
  }
135
135
  case "polygon": {
136
- const a = o.sides || 5, n = Math.min(e, h) / 2;
137
- for (let i = 0; i < a; i++) {
138
- const s = i * 2 * Math.PI / a - Math.PI / 2, t = n * Math.cos(s), d = n * Math.sin(s);
136
+ const e = o.sides || 5, n = Math.min(a, h) / 2;
137
+ for (let i = 0; i < e; i++) {
138
+ const s = i * 2 * Math.PI / e - Math.PI / 2, t = n * Math.cos(s), d = n * Math.sin(s);
139
139
  i === 0 ? r.moveTo(t, d) : r.lineTo(t, d);
140
140
  }
141
141
  r.closePath();
142
142
  break;
143
143
  }
144
144
  case "star": {
145
- const a = o.points || 5, n = Math.min(e, h) / 2, i = n * (o.innerRadius || 0.4);
146
- for (let s = 0; s < a * 2; s++) {
147
- const t = s * Math.PI / a - Math.PI / 2, d = s % 2 === 0 ? n : i, f = d * Math.cos(t), l = d * Math.sin(t);
145
+ const e = o.points || 5, n = Math.min(a, h) / 2, i = n * (o.innerRadius || 0.4);
146
+ for (let s = 0; s < e * 2; s++) {
147
+ const t = s * Math.PI / e - Math.PI / 2, d = s % 2 === 0 ? n : i, f = d * Math.cos(t), l = d * Math.sin(t);
148
148
  s === 0 ? r.moveTo(f, l) : r.lineTo(f, l);
149
149
  }
150
150
  r.closePath();
151
151
  break;
152
152
  }
153
153
  case "line": {
154
- const a = e / 2, n = h / 2;
155
- r.rect(-a, -n, e, h);
154
+ const e = a / 2, n = h / 2;
155
+ r.rect(-e, -n, a, h);
156
156
  break;
157
157
  }
158
158
  default:
159
- r.rect(-e / 2, -h / 2, e, h);
159
+ r.rect(-a / 2, -h / 2, a, h);
160
160
  }
161
161
  }
162
162
  export {
@@ -193,22 +193,22 @@ export {
193
193
  Sr as calculateResizeHandles,
194
194
  Pr as calculateRotationHandlePosition,
195
195
  B as clearImageBitmapCache,
196
- _ as clearMaskCache,
197
- J as clearRegisteredBitmapKeys,
196
+ J as clearMaskCache,
197
+ _ as clearRegisteredBitmapKeys,
198
198
  N as createCirclePath,
199
199
  V as createImagePath,
200
200
  wr as createLogger,
201
201
  j as createRectPath,
202
202
  q as createTextPath,
203
- Q as getFontMetrics,
204
- U as getSharedWorkerExportManager,
203
+ G as getFontMetrics,
204
+ Q as getSharedWorkerExportManager,
205
205
  br as hitTestCircle,
206
206
  Ir as hitTestRect,
207
- Z as invalidateMaskCache,
207
+ U as invalidateMaskCache,
208
208
  Rr as logger,
209
- $ as measureTextWidth,
210
- m as removeFromImageBitmapCache,
211
- G as renderCustomTransform,
209
+ Z as measureTextWidth,
210
+ $ as removeFromImageBitmapCache,
211
+ m as renderCustomTransform,
212
212
  C as renderImageElement,
213
213
  p as renderImageStroke,
214
214
  x as renderMultilineText,
@@ -218,8 +218,8 @@ export {
218
218
  or as renderTextElement,
219
219
  sr as renderTextFillOnly,
220
220
  ir as renderTextStroke,
221
- er as renderWithKnockout,
222
- ar as renderWithMasks,
221
+ ar as renderWithKnockout,
222
+ er as renderWithMasks,
223
223
  y as resolveServerCrop,
224
224
  Wr as splitRichTextIntoLines,
225
225
  nr as terminateSharedWorkerExportManager,
@@ -19,6 +19,10 @@ export interface SerializedTextElement {
19
19
  italic: boolean;
20
20
  underline: boolean;
21
21
  strikethrough: boolean;
22
+ /** Extra advance per character in px (custom transform only; spans may override). */
23
+ letterSpacing?: number;
24
+ /** Line-height multiplier; undefined = LINE_HEIGHT_MULTIPLIER (1.2). Custom transform only. */
25
+ lineHeight?: number;
22
26
  transformData: AnyTransformData;
23
27
  stroke?: StrokeConfig;
24
28
  openTypeFeatures?: OpenTypeFeatures;
@@ -15,4 +15,8 @@ export declare function renderRichTextFillOnly(ctx: RenderContext, richText: Ric
15
15
  underline?: boolean;
16
16
  strikethrough?: boolean;
17
17
  textAlign?: TextAlign;
18
+ /** Element-level letter-spacing default (px/char); spans may override. */
19
+ letterSpacing?: number;
20
+ /** Line-height multiplier; undefined = LINE_HEIGHT_MULTIPLIER (1.2). */
21
+ lineHeight?: number;
18
22
  }, maxWidth?: number, lockedLineCount?: number): void;
@@ -6,8 +6,9 @@ import { RenderContext, SerializedTextElement } from './renderer-types.js';
6
6
  export declare function buildFontString(fontSize: number, fontFamily: string, bold?: boolean, italic?: boolean): string;
7
7
  /**
8
8
  * Measure text width
9
+ * @param letterSpacing - Extra advance per character in px (default 0 — legacy width)
9
10
  */
10
- export declare function measureTextWidth(text: string, fontSize: number, fontFamily: string, bold?: boolean, italic?: boolean): number;
11
+ export declare function measureTextWidth(text: string, fontSize: number, fontFamily: string, bold?: boolean, italic?: boolean, letterSpacing?: number): number;
11
12
  /**
12
13
  * Get font metrics for accurate text positioning
13
14
  */
@@ -49,6 +50,8 @@ export declare function renderTextFillOnly(ctx: RenderContext, element: {
49
50
  italic?: boolean;
50
51
  textAlign?: TextAlign;
51
52
  color: string;
53
+ /** Extra advance per character in px (custom transform only). */
54
+ letterSpacing?: number;
52
55
  }): void;
53
56
  /**
54
57
  * Word wrap text to fit within a given width