domotion-svg 0.1.1

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 (119) hide show
  1. package/FEATURES.md +102 -0
  2. package/LICENSE +21 -0
  3. package/README.md +66 -0
  4. package/dist/animator.d.ts +158 -0
  5. package/dist/animator.js +424 -0
  6. package/dist/animator.test.d.ts +5 -0
  7. package/dist/animator.test.js +169 -0
  8. package/dist/border-radius.test.d.ts +1 -0
  9. package/dist/border-radius.test.js +148 -0
  10. package/dist/capture.d.ts +193 -0
  11. package/dist/capture.js +786 -0
  12. package/dist/chrome.d.ts +45 -0
  13. package/dist/chrome.js +107 -0
  14. package/dist/cli.d.ts +16 -0
  15. package/dist/cli.js +512 -0
  16. package/dist/client/dom.d.ts +10 -0
  17. package/dist/client/dom.js +17 -0
  18. package/dist/conic-raster.d.ts +58 -0
  19. package/dist/conic-raster.js +292 -0
  20. package/dist/conic-raster.test.d.ts +1 -0
  21. package/dist/conic-raster.test.js +187 -0
  22. package/dist/coretext-extractor.test.d.ts +1 -0
  23. package/dist/coretext-extractor.test.js +94 -0
  24. package/dist/coretext-helper.d.ts +60 -0
  25. package/dist/coretext-helper.js +205 -0
  26. package/dist/cross-origin-font-face.test.d.ts +1 -0
  27. package/dist/cross-origin-font-face.test.js +107 -0
  28. package/dist/cursor-overlay.d.ts +123 -0
  29. package/dist/cursor-overlay.js +207 -0
  30. package/dist/cursor-overlay.test.d.ts +1 -0
  31. package/dist/cursor-overlay.test.js +88 -0
  32. package/dist/dark-mode-capture.test.d.ts +1 -0
  33. package/dist/dark-mode-capture.test.js +158 -0
  34. package/dist/dark-mode-form-controls.test.d.ts +1 -0
  35. package/dist/dark-mode-form-controls.test.js +218 -0
  36. package/dist/dom-to-svg.d.ts +1016 -0
  37. package/dist/dom-to-svg.js +7717 -0
  38. package/dist/embed-remote-images.test.d.ts +1 -0
  39. package/dist/embed-remote-images.test.js +424 -0
  40. package/dist/form-controls.d.ts +70 -0
  41. package/dist/form-controls.js +1151 -0
  42. package/dist/frame-merge.d.ts +95 -0
  43. package/dist/frame-merge.js +374 -0
  44. package/dist/frame-merge.test.d.ts +6 -0
  45. package/dist/frame-merge.test.js +144 -0
  46. package/dist/gradients.d.ts +184 -0
  47. package/dist/gradients.js +937 -0
  48. package/dist/gradients.test.d.ts +1 -0
  49. package/dist/gradients.test.js +150 -0
  50. package/dist/index.d.ts +12 -0
  51. package/dist/index.js +7 -0
  52. package/dist/jsx-runtime.d.ts +27 -0
  53. package/dist/jsx-runtime.js +96 -0
  54. package/dist/jsx-runtime.test.d.ts +1 -0
  55. package/dist/jsx-runtime.test.js +41 -0
  56. package/dist/kerfjs-imports.test.d.ts +1 -0
  57. package/dist/kerfjs-imports.test.js +36 -0
  58. package/dist/mask.test.d.ts +1 -0
  59. package/dist/mask.test.js +206 -0
  60. package/dist/optimize.d.ts +12 -0
  61. package/dist/optimize.js +32 -0
  62. package/dist/preserve-aspect-ratio.test.d.ts +1 -0
  63. package/dist/preserve-aspect-ratio.test.js +38 -0
  64. package/dist/resize-embedded-images.d.ts +33 -0
  65. package/dist/resize-embedded-images.js +164 -0
  66. package/dist/resize-embedded-images.test.d.ts +9 -0
  67. package/dist/resize-embedded-images.test.js +255 -0
  68. package/dist/stacking-context.test.d.ts +1 -0
  69. package/dist/stacking-context.test.js +927 -0
  70. package/dist/text-renderer.d.ts +42 -0
  71. package/dist/text-renderer.js +608 -0
  72. package/dist/text-renderer.test.d.ts +1 -0
  73. package/dist/text-renderer.test.js +150 -0
  74. package/dist/text-to-path.d.ts +265 -0
  75. package/dist/text-to-path.js +1800 -0
  76. package/dist/text-to-path.test.d.ts +1 -0
  77. package/dist/text-to-path.test.js +570 -0
  78. package/dist/utils/escapeHtml.d.ts +2 -0
  79. package/dist/utils/escapeHtml.js +15 -0
  80. package/dist/webfont-unicode-range.test.d.ts +1 -0
  81. package/dist/webfont-unicode-range.test.js +174 -0
  82. package/package.json +55 -0
  83. package/src/animator.test.ts +179 -0
  84. package/src/animator.ts +660 -0
  85. package/src/border-radius.test.ts +160 -0
  86. package/src/capture.ts +810 -0
  87. package/src/cli.ts +582 -0
  88. package/src/conic-raster.test.ts +213 -0
  89. package/src/conic-raster.ts +309 -0
  90. package/src/coretext-extractor.test.ts +130 -0
  91. package/src/coretext-helper.ts +256 -0
  92. package/src/cross-origin-font-face.test.ts +119 -0
  93. package/src/cursor-overlay.test.ts +95 -0
  94. package/src/cursor-overlay.ts +297 -0
  95. package/src/dark-mode-capture.test.ts +177 -0
  96. package/src/dark-mode-form-controls.test.ts +228 -0
  97. package/src/dom-to-svg.ts +8376 -0
  98. package/src/embed-remote-images.test.ts +461 -0
  99. package/src/form-controls.ts +1174 -0
  100. package/src/frame-merge.test.ts +157 -0
  101. package/src/frame-merge.ts +447 -0
  102. package/src/globals.d.ts +2 -0
  103. package/src/gradients.test.ts +175 -0
  104. package/src/gradients.ts +955 -0
  105. package/src/index.ts +12 -0
  106. package/src/kerf-jsx-augmentation.d.ts +36 -0
  107. package/src/kerfjs-imports.test.tsx +45 -0
  108. package/src/mask.test.ts +274 -0
  109. package/src/optimize.ts +34 -0
  110. package/src/preserve-aspect-ratio.test.ts +49 -0
  111. package/src/resize-embedded-images.test.ts +292 -0
  112. package/src/resize-embedded-images.ts +180 -0
  113. package/src/stacking-context.test.ts +967 -0
  114. package/src/text-renderer.test.ts +162 -0
  115. package/src/text-renderer.ts +623 -0
  116. package/src/text-to-path.test.ts +639 -0
  117. package/src/text-to-path.ts +1810 -0
  118. package/src/utils/escapeHtml.ts +16 -0
  119. package/src/webfont-unicode-range.test.ts +207 -0
@@ -0,0 +1,175 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildLinearGradientDef, parseConicGradient, parseGradient, parseLinearGradient } from "./gradients.js";
3
+
4
+ describe("parseLinearGradient: repeating support (DM-275)", () => {
5
+ it("parses repeating-linear-gradient with the repeating flag set", () => {
6
+ const g = parseLinearGradient("repeating-linear-gradient(90deg, red 0%, blue 10%)");
7
+ expect(g).not.toBeNull();
8
+ expect(g!.kind).toBe("linear");
9
+ expect(g!.repeating).toBe(true);
10
+ expect(g!.angleDeg).toBe(90);
11
+ expect(g!.stops).toHaveLength(2);
12
+ });
13
+
14
+ it("plain linear-gradient leaves repeating undefined", () => {
15
+ const g = parseLinearGradient("linear-gradient(90deg, red 0%, blue 100%)");
16
+ expect(g).not.toBeNull();
17
+ expect(g!.repeating).toBeUndefined();
18
+ });
19
+ });
20
+
21
+ describe("parseGradient: calc(N% ± Mpx) stop positions", () => {
22
+ it("captures calc offsets on stops via calcOffset (DM-275)", () => {
23
+ const g = parseGradient(
24
+ "repeating-linear-gradient(90deg, transparent 0px, transparent calc(10% - 1px), rgb(148, 163, 184) calc(10% - 1px), rgb(148, 163, 184) 10%)",
25
+ );
26
+ expect(g).not.toBeNull();
27
+ expect(g!.kind).toBe("linear");
28
+ expect(g!.stops).toHaveLength(4);
29
+ const stops = g!.stops as Array<{ calcOffset?: { pct: number; px: number } }>;
30
+ expect(stops[1].calcOffset).toEqual({ pct: 10, px: -1 });
31
+ expect(stops[2].calcOffset).toEqual({ pct: 10, px: -1 });
32
+ });
33
+
34
+ it("supports calc with reversed term order and pure %", () => {
35
+ const g = parseLinearGradient("linear-gradient(0deg, red calc(2px + 10%), blue 50%)");
36
+ expect(g!.stops[0].calcOffset).toEqual({ pct: 10, px: 2 });
37
+ });
38
+ });
39
+
40
+ describe("buildLinearGradientDef: repeating tiles across the gradient line", () => {
41
+ it("emits multiple tiles spanning [0, 1] with calc-resolved offsets", () => {
42
+ // A 100px-wide rect with a repeating-linear-gradient running 90deg means
43
+ // the gradient line length L=100. Period 10% = 10px → 10 tiles.
44
+ const g = parseGradient(
45
+ "repeating-linear-gradient(90deg, transparent 0px, transparent calc(10% - 1px), rgb(148, 163, 184) calc(10% - 1px), rgb(148, 163, 184) 10%)",
46
+ )!;
47
+ const svg = buildLinearGradientDef(g as any, "g0", { x: 0, y: 0, w: 100, h: 6 });
48
+ // 10 full tiles × 4 stops + the one boundary stop at offset 1 from the
49
+ // 11th tile's first stop — out-of-range stops within that tile are
50
+ // clipped out. Allow some slack for boundary/rounding.
51
+ const stopCount = (svg.match(/<stop /g) || []).length;
52
+ expect(stopCount).toBeGreaterThanOrEqual(40);
53
+ expect(stopCount).toBeLessThanOrEqual(44);
54
+ expect(svg).toContain('id="g0"');
55
+ expect(svg).toContain('stop-color="rgb(148, 163, 184)"');
56
+ // First and last offsets at the ends of the gradient line.
57
+ expect(svg).toContain('offset="0"');
58
+ expect(svg).toContain('offset="1"');
59
+ });
60
+
61
+ it("non-repeating gradients still emit their two stops unchanged", () => {
62
+ const g = parseLinearGradient("linear-gradient(90deg, red, blue)")!;
63
+ const svg = buildLinearGradientDef(g, "g1", { x: 0, y: 0, w: 100, h: 10 });
64
+ expect((svg.match(/<stop /g) || []).length).toBe(2);
65
+ });
66
+ });
67
+
68
+ describe("parseConicGradient (DM-548)", () => {
69
+ it("parses bare conic-gradient with default origin and center", () => {
70
+ const g = parseConicGradient("conic-gradient(red, yellow, green, blue)");
71
+ expect(g).not.toBeNull();
72
+ expect(g!.kind).toBe("conic");
73
+ expect(g!.fromAngleDeg).toBe(0);
74
+ expect(g!.position).toEqual({ x: { kind: "frac", value: 0.5 }, y: { kind: "frac", value: 0.5 } });
75
+ expect(g!.stops).toHaveLength(4);
76
+ expect(g!.repeating).toBeUndefined();
77
+ });
78
+
79
+ it("parses repeating-conic-gradient with the repeating flag set", () => {
80
+ const g = parseConicGradient("repeating-conic-gradient(#ddd 0 25%, white 0 50%)");
81
+ expect(g).not.toBeNull();
82
+ expect(g!.repeating).toBe(true);
83
+ // Hard-stop double-position form: 4 stops total (each color emitted twice).
84
+ expect(g!.stops).toHaveLength(4);
85
+ expect(g!.stops[0].offset).toBe(0);
86
+ expect(g!.stops[1].offset).toBe(0.25);
87
+ expect(g!.stops[2].offset).toBe(0);
88
+ expect(g!.stops[3].offset).toBe(0.5);
89
+ });
90
+
91
+ it("parses `from <angle>` clause", () => {
92
+ const g = parseConicGradient("conic-gradient(from 90deg, red, blue)");
93
+ expect(g!.fromAngleDeg).toBe(90);
94
+ expect(g!.stops).toHaveLength(2);
95
+ });
96
+
97
+ it("parses `from <turn>` and converts to degrees", () => {
98
+ const g = parseConicGradient("conic-gradient(from 0.25turn, red, blue)");
99
+ expect(g!.fromAngleDeg).toBe(90);
100
+ });
101
+
102
+ it("parses `at <position>` clause", () => {
103
+ const g = parseConicGradient("conic-gradient(at 25% 75%, red, blue)");
104
+ expect(g!.position).toEqual({ x: { kind: "frac", value: 0.25 }, y: { kind: "frac", value: 0.75 } });
105
+ expect(g!.fromAngleDeg).toBe(0);
106
+ });
107
+
108
+ it("parses `at top right` keyword position", () => {
109
+ const g = parseConicGradient("conic-gradient(at top right, red, blue)");
110
+ // top right → x=right(1), y=top(0). The pair-parser swaps when 'top' precedes a side keyword.
111
+ expect(g!.position.x).toEqual({ kind: "frac", value: 1 });
112
+ expect(g!.position.y).toEqual({ kind: "frac", value: 0 });
113
+ });
114
+
115
+ it("parses combined `from <angle> at <position>`", () => {
116
+ const g = parseConicGradient("conic-gradient(from 0.25turn at top right, red, blue)");
117
+ expect(g!.fromAngleDeg).toBe(90);
118
+ expect(g!.position.x).toEqual({ kind: "frac", value: 1 });
119
+ expect(g!.position.y).toEqual({ kind: "frac", value: 0 });
120
+ });
121
+
122
+ it("parses angle-positioned stops (deg)", () => {
123
+ const g = parseConicGradient("conic-gradient(red 0deg, yellow 90deg, blue 180deg)");
124
+ expect(g!.stops[0].offset).toBe(0);
125
+ expect(g!.stops[1].offset).toBe(0.25);
126
+ expect(g!.stops[2].offset).toBe(0.5);
127
+ });
128
+
129
+ it("parses turn-positioned stops", () => {
130
+ const g = parseConicGradient("conic-gradient(red 0turn, blue 0.5turn)");
131
+ expect(g!.stops[0].offset).toBe(0);
132
+ expect(g!.stops[1].offset).toBe(0.5);
133
+ });
134
+
135
+ it("parses percentage-positioned stops", () => {
136
+ const g = parseConicGradient("conic-gradient(red 0%, yellow 50%, blue 100%)");
137
+ expect(g!.stops[0].offset).toBe(0);
138
+ expect(g!.stops[1].offset).toBe(0.5);
139
+ expect(g!.stops[2].offset).toBe(1);
140
+ });
141
+
142
+ it("parses double-position hard stops", () => {
143
+ const g = parseConicGradient("conic-gradient(red 0% 25%, blue 25% 50%)");
144
+ expect(g!.stops).toHaveLength(4);
145
+ expect(g!.stops[0]).toMatchObject({ color: "red", offset: 0 });
146
+ expect(g!.stops[1]).toMatchObject({ color: "red", offset: 0.25 });
147
+ expect(g!.stops[2]).toMatchObject({ color: "blue", offset: 0.25 });
148
+ expect(g!.stops[3]).toMatchObject({ color: "blue", offset: 0.5 });
149
+ });
150
+
151
+ it("preserves rgba/rgb color text including modern slash-alpha syntax", () => {
152
+ const g = parseConicGradient("conic-gradient(rgb(255, 0, 0) 0%, rgba(0, 0, 255, 0.5) 100%)");
153
+ expect(g!.stops[0].color).toBe("rgb(255, 0, 0)");
154
+ expect(g!.stops[1].color).toBe("rgba(0, 0, 255, 0.5)");
155
+ });
156
+
157
+ it("rejects malformed text", () => {
158
+ expect(parseConicGradient("conic-gradient()")).toBeNull();
159
+ expect(parseConicGradient("conic-gradient(red)")).toBeNull();
160
+ expect(parseConicGradient("linear-gradient(red, blue)")).toBeNull();
161
+ expect(parseConicGradient(null)).toBeNull();
162
+ expect(parseConicGradient(undefined)).toBeNull();
163
+ });
164
+
165
+ it("is reachable via parseGradient dispatch", () => {
166
+ const g = parseGradient("conic-gradient(red, blue)");
167
+ expect(g).not.toBeNull();
168
+ expect(g!.kind).toBe("conic");
169
+ });
170
+
171
+ it("parseGradient still picks linear/radial first when matching", () => {
172
+ expect(parseGradient("linear-gradient(red, blue)")?.kind).toBe("linear");
173
+ expect(parseGradient("radial-gradient(red, blue)")?.kind).toBe("radial");
174
+ });
175
+ });