@motion-script/web 0.1.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 (262) hide show
  1. package/README.md +47 -0
  2. package/dist/audio/player.d.ts +43 -0
  3. package/dist/audio/player.d.ts.map +1 -0
  4. package/dist/audio/player.js +165 -0
  5. package/dist/audio/player.js.map +1 -0
  6. package/dist/effects/bloom.d.ts +19 -0
  7. package/dist/effects/bloom.d.ts.map +1 -0
  8. package/dist/effects/bloom.js +64 -0
  9. package/dist/effects/bloom.js.map +1 -0
  10. package/dist/effects/blur.d.ts +8 -0
  11. package/dist/effects/blur.d.ts.map +1 -0
  12. package/dist/effects/blur.js +12 -0
  13. package/dist/effects/blur.js.map +1 -0
  14. package/dist/effects/bulge-pinch.d.ts +20 -0
  15. package/dist/effects/bulge-pinch.d.ts.map +1 -0
  16. package/dist/effects/bulge-pinch.js +86 -0
  17. package/dist/effects/bulge-pinch.js.map +1 -0
  18. package/dist/effects/chromatic-aberration.d.ts +19 -0
  19. package/dist/effects/chromatic-aberration.d.ts.map +1 -0
  20. package/dist/effects/chromatic-aberration.js +59 -0
  21. package/dist/effects/chromatic-aberration.js.map +1 -0
  22. package/dist/effects/effect.d.ts +32 -0
  23. package/dist/effects/effect.d.ts.map +1 -0
  24. package/dist/effects/effect.js +22 -0
  25. package/dist/effects/effect.js.map +1 -0
  26. package/dist/effects/grayscale.d.ts +12 -0
  27. package/dist/effects/grayscale.d.ts.map +1 -0
  28. package/dist/effects/grayscale.js +31 -0
  29. package/dist/effects/grayscale.js.map +1 -0
  30. package/dist/effects/index.d.ts +13 -0
  31. package/dist/effects/index.d.ts.map +1 -0
  32. package/dist/effects/index.js +13 -0
  33. package/dist/effects/index.js.map +1 -0
  34. package/dist/effects/pixelate.d.ts +23 -0
  35. package/dist/effects/pixelate.d.ts.map +1 -0
  36. package/dist/effects/pixelate.js +37 -0
  37. package/dist/effects/pixelate.js.map +1 -0
  38. package/dist/effects/registry.d.ts +17 -0
  39. package/dist/effects/registry.d.ts.map +1 -0
  40. package/dist/effects/registry.js +56 -0
  41. package/dist/effects/registry.js.map +1 -0
  42. package/dist/effects/sksl-cache.d.ts +6 -0
  43. package/dist/effects/sksl-cache.d.ts.map +1 -0
  44. package/dist/effects/sksl-cache.js +21 -0
  45. package/dist/effects/sksl-cache.js.map +1 -0
  46. package/dist/effects/sksl-layer.d.ts +30 -0
  47. package/dist/effects/sksl-layer.d.ts.map +1 -0
  48. package/dist/effects/sksl-layer.js +82 -0
  49. package/dist/effects/sksl-layer.js.map +1 -0
  50. package/dist/effects/texture.d.ts +31 -0
  51. package/dist/effects/texture.d.ts.map +1 -0
  52. package/dist/effects/texture.js +66 -0
  53. package/dist/effects/texture.js.map +1 -0
  54. package/dist/effects/vintage.d.ts +20 -0
  55. package/dist/effects/vintage.d.ts.map +1 -0
  56. package/dist/effects/vintage.js +47 -0
  57. package/dist/effects/vintage.js.map +1 -0
  58. package/dist/effects/zoom.d.ts +20 -0
  59. package/dist/effects/zoom.d.ts.map +1 -0
  60. package/dist/effects/zoom.js +65 -0
  61. package/dist/effects/zoom.js.map +1 -0
  62. package/dist/exporter.d.ts +24 -0
  63. package/dist/exporter.d.ts.map +1 -0
  64. package/dist/exporter.js +177 -0
  65. package/dist/exporter.js.map +1 -0
  66. package/dist/fills/conic-gradient.d.ts +12 -0
  67. package/dist/fills/conic-gradient.d.ts.map +1 -0
  68. package/dist/fills/conic-gradient.js +44 -0
  69. package/dist/fills/conic-gradient.js.map +1 -0
  70. package/dist/fills/filters/alpha.d.ts +9 -0
  71. package/dist/fills/filters/alpha.d.ts.map +1 -0
  72. package/dist/fills/filters/alpha.js +21 -0
  73. package/dist/fills/filters/alpha.js.map +1 -0
  74. package/dist/fills/filters/blur.d.ts +9 -0
  75. package/dist/fills/filters/blur.d.ts.map +1 -0
  76. package/dist/fills/filters/blur.js +12 -0
  77. package/dist/fills/filters/blur.js.map +1 -0
  78. package/dist/fills/filters/color-adjustment.d.ts +14 -0
  79. package/dist/fills/filters/color-adjustment.d.ts.map +1 -0
  80. package/dist/fills/filters/color-adjustment.js +147 -0
  81. package/dist/fills/filters/color-adjustment.js.map +1 -0
  82. package/dist/fills/filters/color-matrix.d.ts +9 -0
  83. package/dist/fills/filters/color-matrix.d.ts.map +1 -0
  84. package/dist/fills/filters/color-matrix.js +14 -0
  85. package/dist/fills/filters/color-matrix.js.map +1 -0
  86. package/dist/fills/filters/curves.d.ts +9 -0
  87. package/dist/fills/filters/curves.d.ts.map +1 -0
  88. package/dist/fills/filters/curves.js +89 -0
  89. package/dist/fills/filters/curves.js.map +1 -0
  90. package/dist/fills/filters/exposure.d.ts +9 -0
  91. package/dist/fills/filters/exposure.d.ts.map +1 -0
  92. package/dist/fills/filters/exposure.js +22 -0
  93. package/dist/fills/filters/exposure.js.map +1 -0
  94. package/dist/fills/filters/filter.d.ts +17 -0
  95. package/dist/fills/filters/filter.d.ts.map +1 -0
  96. package/dist/fills/filters/filter.js +16 -0
  97. package/dist/fills/filters/filter.js.map +1 -0
  98. package/dist/fills/filters/grayscale.d.ts +9 -0
  99. package/dist/fills/filters/grayscale.d.ts.map +1 -0
  100. package/dist/fills/filters/grayscale.js +25 -0
  101. package/dist/fills/filters/grayscale.js.map +1 -0
  102. package/dist/fills/filters/registry.d.ts +16 -0
  103. package/dist/fills/filters/registry.d.ts.map +1 -0
  104. package/dist/fills/filters/registry.js +50 -0
  105. package/dist/fills/filters/registry.js.map +1 -0
  106. package/dist/fills/gradient-cache.d.ts +29 -0
  107. package/dist/fills/gradient-cache.d.ts.map +1 -0
  108. package/dist/fills/gradient-cache.js +57 -0
  109. package/dist/fills/gradient-cache.js.map +1 -0
  110. package/dist/fills/handler.d.ts +49 -0
  111. package/dist/fills/handler.d.ts.map +1 -0
  112. package/dist/fills/handler.js +172 -0
  113. package/dist/fills/handler.js.map +1 -0
  114. package/dist/fills/image.d.ts +34 -0
  115. package/dist/fills/image.d.ts.map +1 -0
  116. package/dist/fills/image.js +91 -0
  117. package/dist/fills/image.js.map +1 -0
  118. package/dist/fills/linear-gradient.d.ts +12 -0
  119. package/dist/fills/linear-gradient.d.ts.map +1 -0
  120. package/dist/fills/linear-gradient.js +48 -0
  121. package/dist/fills/linear-gradient.js.map +1 -0
  122. package/dist/fills/noise.d.ts +11 -0
  123. package/dist/fills/noise.d.ts.map +1 -0
  124. package/dist/fills/noise.js +82 -0
  125. package/dist/fills/noise.js.map +1 -0
  126. package/dist/fills/radial-gradient.d.ts +9 -0
  127. package/dist/fills/radial-gradient.d.ts.map +1 -0
  128. package/dist/fills/radial-gradient.js +43 -0
  129. package/dist/fills/radial-gradient.js.map +1 -0
  130. package/dist/fills/registry.d.ts +10 -0
  131. package/dist/fills/registry.d.ts.map +1 -0
  132. package/dist/fills/registry.js +34 -0
  133. package/dist/fills/registry.js.map +1 -0
  134. package/dist/fills/renderer.d.ts +24 -0
  135. package/dist/fills/renderer.d.ts.map +1 -0
  136. package/dist/fills/renderer.js +5 -0
  137. package/dist/fills/renderer.js.map +1 -0
  138. package/dist/fills/solid.d.ts +7 -0
  139. package/dist/fills/solid.d.ts.map +1 -0
  140. package/dist/fills/solid.js +13 -0
  141. package/dist/fills/solid.js.map +1 -0
  142. package/dist/fills/stripe.d.ts +7 -0
  143. package/dist/fills/stripe.d.ts.map +1 -0
  144. package/dist/fills/stripe.js +85 -0
  145. package/dist/fills/stripe.js.map +1 -0
  146. package/dist/fills/video.d.ts +6 -0
  147. package/dist/fills/video.d.ts.map +1 -0
  148. package/dist/fills/video.js +14 -0
  149. package/dist/fills/video.js.map +1 -0
  150. package/dist/font-style.d.ts +23 -0
  151. package/dist/font-style.d.ts.map +1 -0
  152. package/dist/font-style.js +34 -0
  153. package/dist/font-style.js.map +1 -0
  154. package/dist/getter.d.ts +9 -0
  155. package/dist/getter.d.ts.map +1 -0
  156. package/dist/getter.js +26 -0
  157. package/dist/getter.js.map +1 -0
  158. package/dist/index.d.ts +9 -0
  159. package/dist/index.d.ts.map +1 -0
  160. package/dist/index.js +11 -0
  161. package/dist/index.js.map +1 -0
  162. package/dist/master-clock.d.ts +42 -0
  163. package/dist/master-clock.d.ts.map +1 -0
  164. package/dist/master-clock.js +134 -0
  165. package/dist/master-clock.js.map +1 -0
  166. package/dist/measure-scope.d.ts +14 -0
  167. package/dist/measure-scope.d.ts.map +1 -0
  168. package/dist/measure-scope.js +29 -0
  169. package/dist/measure-scope.js.map +1 -0
  170. package/dist/render-context.d.ts +107 -0
  171. package/dist/render-context.d.ts.map +1 -0
  172. package/dist/render-context.js +940 -0
  173. package/dist/render-context.js.map +1 -0
  174. package/dist/shapes/alpha-contour.d.ts +27 -0
  175. package/dist/shapes/alpha-contour.d.ts.map +1 -0
  176. package/dist/shapes/alpha-contour.js +330 -0
  177. package/dist/shapes/alpha-contour.js.map +1 -0
  178. package/dist/shapes/base.d.ts +46 -0
  179. package/dist/shapes/base.d.ts.map +1 -0
  180. package/dist/shapes/base.js +95 -0
  181. package/dist/shapes/base.js.map +1 -0
  182. package/dist/shapes/boolean.d.ts +28 -0
  183. package/dist/shapes/boolean.d.ts.map +1 -0
  184. package/dist/shapes/boolean.js +90 -0
  185. package/dist/shapes/boolean.js.map +1 -0
  186. package/dist/shapes/ellipse.d.ts +32 -0
  187. package/dist/shapes/ellipse.d.ts.map +1 -0
  188. package/dist/shapes/ellipse.js +50 -0
  189. package/dist/shapes/ellipse.js.map +1 -0
  190. package/dist/shapes/image.d.ts +66 -0
  191. package/dist/shapes/image.d.ts.map +1 -0
  192. package/dist/shapes/image.js +214 -0
  193. package/dist/shapes/image.js.map +1 -0
  194. package/dist/shapes/index.d.ts +67 -0
  195. package/dist/shapes/index.d.ts.map +1 -0
  196. package/dist/shapes/index.js +297 -0
  197. package/dist/shapes/index.js.map +1 -0
  198. package/dist/shapes/line.d.ts +25 -0
  199. package/dist/shapes/line.d.ts.map +1 -0
  200. package/dist/shapes/line.js +87 -0
  201. package/dist/shapes/line.js.map +1 -0
  202. package/dist/shapes/mask.d.ts +28 -0
  203. package/dist/shapes/mask.d.ts.map +1 -0
  204. package/dist/shapes/mask.js +106 -0
  205. package/dist/shapes/mask.js.map +1 -0
  206. package/dist/shapes/paragraph-layout.d.ts +64 -0
  207. package/dist/shapes/paragraph-layout.d.ts.map +1 -0
  208. package/dist/shapes/paragraph-layout.js +156 -0
  209. package/dist/shapes/paragraph-layout.js.map +1 -0
  210. package/dist/shapes/path.d.ts +29 -0
  211. package/dist/shapes/path.d.ts.map +1 -0
  212. package/dist/shapes/path.js +71 -0
  213. package/dist/shapes/path.js.map +1 -0
  214. package/dist/shapes/polygon.d.ts +33 -0
  215. package/dist/shapes/polygon.d.ts.map +1 -0
  216. package/dist/shapes/polygon.js +86 -0
  217. package/dist/shapes/polygon.js.map +1 -0
  218. package/dist/shapes/polygram.d.ts +34 -0
  219. package/dist/shapes/polygram.d.ts.map +1 -0
  220. package/dist/shapes/polygram.js +90 -0
  221. package/dist/shapes/polygram.js.map +1 -0
  222. package/dist/shapes/rect.d.ts +41 -0
  223. package/dist/shapes/rect.d.ts.map +1 -0
  224. package/dist/shapes/rect.js +111 -0
  225. package/dist/shapes/rect.js.map +1 -0
  226. package/dist/shapes/richtext.d.ts +28 -0
  227. package/dist/shapes/richtext.d.ts.map +1 -0
  228. package/dist/shapes/richtext.js +32 -0
  229. package/dist/shapes/richtext.js.map +1 -0
  230. package/dist/shapes/shape-handler.d.ts +79 -0
  231. package/dist/shapes/shape-handler.d.ts.map +1 -0
  232. package/dist/shapes/shape-handler.js +304 -0
  233. package/dist/shapes/shape-handler.js.map +1 -0
  234. package/dist/shapes/text.d.ts +13 -0
  235. package/dist/shapes/text.d.ts.map +1 -0
  236. package/dist/shapes/text.js +67 -0
  237. package/dist/shapes/text.js.map +1 -0
  238. package/dist/shapes/trim.d.ts +10 -0
  239. package/dist/shapes/trim.d.ts.map +1 -0
  240. package/dist/shapes/trim.js +49 -0
  241. package/dist/shapes/trim.js.map +1 -0
  242. package/dist/storage-adapter.d.ts +56 -0
  243. package/dist/storage-adapter.d.ts.map +1 -0
  244. package/dist/storage-adapter.js +188 -0
  245. package/dist/storage-adapter.js.map +1 -0
  246. package/dist/stroke/index.d.ts +34 -0
  247. package/dist/stroke/index.d.ts.map +1 -0
  248. package/dist/stroke/index.js +360 -0
  249. package/dist/stroke/index.js.map +1 -0
  250. package/dist/stroke/stroke-handler.d.ts +45 -0
  251. package/dist/stroke/stroke-handler.d.ts.map +1 -0
  252. package/dist/stroke/stroke-handler.js +371 -0
  253. package/dist/stroke/stroke-handler.js.map +1 -0
  254. package/dist/video/extract.d.ts +54 -0
  255. package/dist/video/extract.d.ts.map +1 -0
  256. package/dist/video/extract.js +192 -0
  257. package/dist/video/extract.js.map +1 -0
  258. package/dist/video/extract.worker.d.ts +50 -0
  259. package/dist/video/extract.worker.d.ts.map +1 -0
  260. package/dist/video/extract.worker.js +224 -0
  261. package/dist/video/extract.worker.js.map +1 -0
  262. package/package.json +57 -0
@@ -0,0 +1,371 @@
1
+ import { FillRenderRegistry } from "../fills/registry";
2
+ /**
3
+ * Paints strokes and shadows over shape silhouettes. Handles the gritty parts
4
+ * of vector stroking on Skia: sub-pixel-width strokes are rasterized as filled
5
+ * paths for uniform AA (see {@link drawStroke}), thin strokes get pixel-grid
6
+ * snapped (see {@link snapPath}), dashes are length-fit per contour, and text
7
+ * gets a synthesized union outline since glyph paths aren't directly available
8
+ * (see {@link drawTextUnionStroke}). Delegates shader/paint setup for fills to
9
+ * {@link FillHandler}.
10
+ */
11
+ export class StrokeHandler {
12
+ canvasKit;
13
+ getCanvas;
14
+ getPaint;
15
+ fills;
16
+ constructor(canvasKit, getCanvas, getPaint, fills) {
17
+ this.canvasKit = canvasKit;
18
+ this.getCanvas = getCanvas;
19
+ this.getPaint = getPaint;
20
+ this.fills = fills;
21
+ }
22
+ makeUniformDashEffect(path, dash, dashOffset = 0, fit) {
23
+ const info = fit ?? this.measureDashFit(path, dash);
24
+ if (!info)
25
+ return null;
26
+ return this.canvasKit.PathEffect.MakeDash(info.scaledDash, dashOffset * info.scale);
27
+ }
28
+ // Compute a per-contour-uniform dash pattern by scaling the dash array so an
29
+ // integer number of cycles fits the path length exactly.
30
+ measureDashFit(path, dash) {
31
+ const cycleLength = dash.reduce((a, b) => a + b, 0);
32
+ if (cycleLength <= 0)
33
+ return null;
34
+ let totalLength = 0;
35
+ const iter = new this.canvasKit.ContourMeasureIter(path, false, 1);
36
+ let contour = iter.next();
37
+ while (contour) {
38
+ totalLength += contour.length();
39
+ contour.delete();
40
+ contour = iter.next();
41
+ }
42
+ iter.delete();
43
+ if (totalLength <= 0)
44
+ return null;
45
+ const cycles = Math.max(1, Math.round(totalLength / cycleLength));
46
+ const scale = totalLength / (cycles * cycleLength);
47
+ return { scaledDash: dash.map(d => d * scale), scale };
48
+ }
49
+ // Build a path consisting of just the "on" dash segments along the original
50
+ // path. Used so we can run makeStroked() on a dashed path and draw the
51
+ // result as a fill (gives uniform AA on all sides). The caller owns the
52
+ // returned path and must delete() it.
53
+ buildDashedPath(path, dash, dashOffset, fit) {
54
+ const resolvedFit = fit ?? this.measureDashFit(path, dash);
55
+ if (!resolvedFit)
56
+ return null;
57
+ const { scaledDash, scale } = resolvedFit;
58
+ const cycle = scaledDash.reduce((a, b) => a + b, 0);
59
+ if (cycle <= 0)
60
+ return null;
61
+ const startOffset = ((dashOffset * scale) % cycle + cycle) % cycle;
62
+ const builder = new this.canvasKit.PathBuilder();
63
+ const iter = new this.canvasKit.ContourMeasureIter(path, false, 1);
64
+ let contour = iter.next();
65
+ let appended = false;
66
+ while (contour) {
67
+ const length = contour.length();
68
+ // Locate the dash segment that contains startOffset.
69
+ let dashIndex = 0;
70
+ let segStart = 0;
71
+ while (segStart + scaledDash[dashIndex] <= startOffset && dashIndex < scaledDash.length) {
72
+ segStart += scaledDash[dashIndex];
73
+ dashIndex = (dashIndex + 1) % scaledDash.length;
74
+ if (dashIndex === 0)
75
+ break;
76
+ }
77
+ let dashRemaining = scaledDash[dashIndex] - (startOffset - segStart);
78
+ let drawing = dashIndex % 2 === 0;
79
+ let t = 0;
80
+ while (t < length - 1e-6) {
81
+ const segLen = Math.min(dashRemaining, length - t);
82
+ if (drawing && segLen > 1e-3) {
83
+ const seg = contour.getSegment(t, t + segLen, true);
84
+ if (seg) {
85
+ builder.addPath(seg);
86
+ seg.delete();
87
+ appended = true;
88
+ }
89
+ }
90
+ t += segLen;
91
+ dashRemaining -= segLen;
92
+ if (dashRemaining <= 1e-6) {
93
+ dashIndex = (dashIndex + 1) % scaledDash.length;
94
+ dashRemaining = scaledDash[dashIndex];
95
+ drawing = !drawing;
96
+ }
97
+ }
98
+ contour.delete();
99
+ contour = iter.next();
100
+ }
101
+ iter.delete();
102
+ if (!appended) {
103
+ builder.delete();
104
+ return null;
105
+ }
106
+ return builder.detachAndDelete();
107
+ }
108
+ deviceMetrics(canvas) {
109
+ const m = canvas.getTotalMatrix();
110
+ return {
111
+ sx: Math.hypot(m[0], m[3]),
112
+ sy: Math.hypot(m[1], m[4]),
113
+ tx: m[2],
114
+ ty: m[5],
115
+ };
116
+ }
117
+ // For thin strokes (≤ ~2.5 device px), round to an integer device width
118
+ // ≥ 1 px and convert back to logical units. Returns the adjusted logical
119
+ // weight and the integer device width (or -1 when no snapping should occur).
120
+ resolveStrokeWidth(weight, deviceScale) {
121
+ if (weight <= 0 || deviceScale <= 0) {
122
+ return { logical: 0, intDeviceWidth: -1 };
123
+ }
124
+ const deviceWidth = weight * deviceScale;
125
+ if (deviceWidth >= 2.5) {
126
+ return { logical: weight, intDeviceWidth: -1 };
127
+ }
128
+ const intDeviceWidth = Math.max(1, Math.round(deviceWidth));
129
+ return { logical: intDeviceWidth / deviceScale, intDeviceWidth };
130
+ }
131
+ // Snap the canvas so the path's bounds land exactly on the device pixel
132
+ // grid — half-pixel offsets for odd stroke widths, integer for even — on
133
+ // ALL four sides. When the path's device-space size isn't already an
134
+ // integer we apply a sub-pixel scale around the top-left corner so right
135
+ // and bottom also align. Bails on rotated/skewed matrices since "pixel
136
+ // grid" isn't well defined there. Returns true if canvas.save() was called.
137
+ snapPath(canvas, intDeviceWidth, ckPath) {
138
+ if (intDeviceWidth <= 0 || !ckPath)
139
+ return false;
140
+ const m = canvas.getTotalMatrix();
141
+ if (Math.abs(m[1]) > 1e-6 || Math.abs(m[3]) > 1e-6)
142
+ return false;
143
+ const sx = m[0], sy = m[4], tx = m[2], ty = m[5];
144
+ if (sx === 0 || sy === 0)
145
+ return false;
146
+ const b = ckPath.getBounds();
147
+ const wLog = b[2] - b[0];
148
+ const hLog = b[3] - b[1];
149
+ if (wLog <= 0 || hLog <= 0)
150
+ return false;
151
+ const leftDev = tx + sx * b[0];
152
+ const topDev = ty + sy * b[1];
153
+ const rightDev = tx + sx * b[2];
154
+ const bottomDev = ty + sy * b[3];
155
+ const offset = intDeviceWidth % 2 === 0 ? 0 : 0.5;
156
+ const targetLeft = Math.round(leftDev - offset) + offset;
157
+ const targetTop = Math.round(topDev - offset) + offset;
158
+ const targetRight = Math.round(rightDev - offset) + offset;
159
+ const targetBottom = Math.round(bottomDev - offset) + offset;
160
+ // Refuse to snap if rounding would collapse the path.
161
+ if (targetRight - targetLeft < 1)
162
+ return false;
163
+ if (targetBottom - targetTop < 1)
164
+ return false;
165
+ const scaleX = (targetRight - targetLeft) / (rightDev - leftDev);
166
+ const scaleY = (targetBottom - targetTop) / (bottomDev - topDev);
167
+ // Skip when the path is already aligned.
168
+ if (Math.abs(scaleX - 1) < 1e-4
169
+ && Math.abs(scaleY - 1) < 1e-4
170
+ && Math.abs(targetLeft - leftDev) < 1e-4
171
+ && Math.abs(targetTop - topDev) < 1e-4)
172
+ return false;
173
+ // Concat L such that current * L maps the path bounds to the targets.
174
+ // L is axis-aligned scale+translate in path-local space:
175
+ // L = [scaleX 0 c; 0 scaleY d; 0 0 1]
176
+ // where (scaleX, scaleY) makes device width/height integer and (c, d)
177
+ // shifts the top-left corner to (targetLeft, targetTop).
178
+ const c = (targetLeft - tx) / sx - scaleX * b[0];
179
+ const d = (targetTop - ty) / sy - scaleY * b[1];
180
+ canvas.save();
181
+ canvas.concat([scaleX, 0, c, 0, scaleY, d, 0, 0, 1]);
182
+ return true;
183
+ }
184
+ // `resolveBounds`, when supplied, returns the bounds a stroke's fill shader
185
+ // should resolve against for a given shape (driven by the fill's `space`).
186
+ // The shader is rebuilt per shape so each can resolve against its own rect;
187
+ // when omitted, bounds come from whatever the fill handler last set.
188
+ /** Paints each resolved stroke over every shape: resolves device-px stroke width (with thin-stroke snapping), builds the fill shader per shape via the registry, then draws through {@link drawStroke}. */
189
+ applyStrokes(strokes, shapes, resolveBounds) {
190
+ if (strokes.length === 0)
191
+ return false;
192
+ const canvas = this.getCanvas();
193
+ const paint = this.getPaint();
194
+ paint.setStyle(this.canvasKit.PaintStyle.Stroke);
195
+ const rendererCtx = this.fills.buildRendererCtx(paint);
196
+ for (const stroke of strokes) {
197
+ const weight = stroke.weight ?? 1;
198
+ const { sx, sy } = this.deviceMetrics(canvas);
199
+ const { logical, intDeviceWidth } = this.resolveStrokeWidth(weight, Math.max(sx, sy));
200
+ paint.setStrokeWidth(logical);
201
+ const opacity = stroke.fill.opacity !== undefined ? stroke.fill.opacity : 1.0;
202
+ paint.setAlphaf(opacity);
203
+ if (stroke.fill.blend) {
204
+ paint.setBlendMode(this.fills.getCanvasKitBlendMode(stroke.fill.blend));
205
+ }
206
+ else {
207
+ paint.setBlendMode(this.canvasKit.BlendMode.SrcOver);
208
+ }
209
+ for (const shape of shapes) {
210
+ if (resolveBounds)
211
+ resolveBounds(stroke.fill, shape);
212
+ if (!FillRenderRegistry.applyPaint(stroke.fill, rendererCtx))
213
+ continue;
214
+ const snapped = this.snapPath(canvas, intDeviceWidth, shape.ckPath);
215
+ this.drawStroke(canvas, paint, shape, stroke, logical, intDeviceWidth);
216
+ if (snapped)
217
+ canvas.restore();
218
+ }
219
+ }
220
+ paint.setBlendMode(this.canvasKit.BlendMode.SrcOver);
221
+ paint.setAlphaf(1.0);
222
+ paint.setShader(null);
223
+ paint.setStyle(this.canvasKit.PaintStyle.Stroke);
224
+ return true;
225
+ }
226
+ // Draw one stroke pass on one shape. For thin strokes (intDeviceWidth > 0)
227
+ // we convert the stroke to a filled path via Path.makeStroked() and draw it
228
+ // as a fill — fill rasterization gives uniform AA on every side, which the
229
+ // stroke rasterizer doesn't for sub-pixel widths. Dashed thin strokes get
230
+ // their dash pattern baked into a path first (buildDashedPath) so the same
231
+ // makeStroked trick works on them.
232
+ drawStroke(canvas, paint, shape, stroke, logicalWidth, intDeviceWidth) {
233
+ if (shape.isText) {
234
+ this.drawTextUnionStroke(canvas, paint, shape, stroke, logicalWidth);
235
+ return;
236
+ }
237
+ const hasDash = !!(stroke.dash && stroke.dash.length > 0);
238
+ // Measure dash fit once; pass to both buildDashedPath and makeUniformDashEffect
239
+ // so the contour iteration runs only once per dashed shape.
240
+ const dashFit = hasDash && shape.ckPath
241
+ ? this.measureDashFit(shape.ckPath, stroke.dash)
242
+ : null;
243
+ if (intDeviceWidth > 0 && shape.ckPath) {
244
+ const source = hasDash
245
+ ? this.buildDashedPath(shape.ckPath, stroke.dash, stroke.dashOffset ?? 0, dashFit ?? undefined)
246
+ : shape.ckPath;
247
+ if (source) {
248
+ const filled = source.makeStroked({ width: logicalWidth });
249
+ if (source !== shape.ckPath)
250
+ source.delete();
251
+ if (filled) {
252
+ paint.setStyle(this.canvasKit.PaintStyle.Fill);
253
+ canvas.drawPath(filled, paint);
254
+ paint.setStyle(this.canvasKit.PaintStyle.Stroke);
255
+ filled.delete();
256
+ return;
257
+ }
258
+ }
259
+ }
260
+ // Fallback for thick strokes or when makeStroked/dashing failed.
261
+ if (hasDash && shape.ckPath) {
262
+ const effect = this.makeUniformDashEffect(shape.ckPath, stroke.dash, stroke.dashOffset ?? 0, dashFit ?? undefined);
263
+ if (effect) {
264
+ paint.setPathEffect(effect);
265
+ canvas.drawPath(shape.ckPath, paint);
266
+ paint.setPathEffect(null);
267
+ effect.delete();
268
+ return;
269
+ }
270
+ }
271
+ shape.draw(paint);
272
+ }
273
+ // Strokes that follow the union of all glyph silhouettes, like Figma. We
274
+ // can't get glyph paths out of canvaskit-wasm to compute a real union, so
275
+ // we stroke each glyph at 2× width inside a saveLayer and then erase the
276
+ // interior (DstOut against the fill silhouette). What's left is an
277
+ // outside-aligned stroke of the requested width on the outer boundary of
278
+ // the union — internal crossings at overlapping glyphs disappear because
279
+ // they sit inside the silhouette and get erased.
280
+ //
281
+ // Dashed text strokes piggy-back on Skia's drawText → glyph-path fallback:
282
+ // setting PathEffect.MakeDash on the paint causes the stroke rasterizer to
283
+ // dash along each glyph's outline. We can't size the dash to the contour
284
+ // (no path to measure), so the dash array is used as-is.
285
+ drawTextUnionStroke(canvas, paint, shape, stroke, logicalWidth) {
286
+ const hasDash = !!(stroke.dash && stroke.dash.length > 0);
287
+ let dashEffect = null;
288
+ if (hasDash) {
289
+ dashEffect = this.canvasKit.PathEffect.MakeDash(stroke.dash, stroke.dashOffset ?? 0);
290
+ paint.setPathEffect(dashEffect);
291
+ }
292
+ canvas.saveLayer();
293
+ paint.setStrokeWidth(logicalWidth * 2);
294
+ shape.draw(paint);
295
+ const eraser = new this.canvasKit.Paint();
296
+ eraser.setStyle(this.canvasKit.PaintStyle.Fill);
297
+ eraser.setBlendMode(this.canvasKit.BlendMode.DstOut);
298
+ eraser.setColor(this.canvasKit.WHITE);
299
+ shape.draw(eraser);
300
+ eraser.delete();
301
+ canvas.restore();
302
+ paint.setStrokeWidth(logicalWidth);
303
+ if (dashEffect) {
304
+ paint.setPathEffect(null);
305
+ dashEffect.delete();
306
+ }
307
+ }
308
+ /** Paints drop shadows: for each shadow, offsets and optionally blurs a silhouette layer (filled shapes and/or strokes recolored to the shadow's fill/opacity), composited beneath the real paint via `saveLayer`. */
309
+ applyShadows(shadows, shapes, fills, strokes, resolveBounds) {
310
+ if (shadows.length === 0)
311
+ return;
312
+ const canvas = this.getCanvas();
313
+ const paint = this.getPaint();
314
+ const hasFill = fills.length > 0 && fills.some(f => (f.opacity ?? 1) > 0);
315
+ const hasStrokes = strokes.length > 0;
316
+ for (const shadow of shadows) {
317
+ const dx = shadow.dx ?? 0;
318
+ const dy = shadow.dy ?? 0;
319
+ const opacity = shadow.fill.opacity !== undefined ? shadow.fill.opacity : 1.0;
320
+ const layerPaint = new this.canvasKit.Paint();
321
+ layerPaint.setAlphaf(opacity);
322
+ if (shadow.blur > 0) {
323
+ const sigma = shadow.blur / 2;
324
+ const filter = this.canvasKit.ImageFilter.MakeBlur(sigma, sigma, this.canvasKit.TileMode.Decal, null);
325
+ layerPaint.setImageFilter(filter);
326
+ }
327
+ canvas.save();
328
+ // Scene coords are Y-up; the canvas is Y-down, so negate dy to keep
329
+ // a positive dy nudging the shadow upward.
330
+ canvas.translate(dx, -dy);
331
+ canvas.saveLayer(layerPaint);
332
+ paint.setAlphaf(1.0);
333
+ if (resolveBounds)
334
+ resolveBounds(shadow.fill, null);
335
+ FillRenderRegistry.applyPaint(shadow.fill, this.fills.buildRendererCtx(paint));
336
+ if (hasFill) {
337
+ paint.setStyle(this.canvasKit.PaintStyle.Fill);
338
+ paint.setPathEffect(null);
339
+ for (const shape of shapes)
340
+ shape.draw(paint);
341
+ }
342
+ if (hasStrokes) {
343
+ paint.setStyle(this.canvasKit.PaintStyle.Stroke);
344
+ for (const stroke of strokes) {
345
+ const weight = stroke.weight ?? 1;
346
+ const { sx, sy } = this.deviceMetrics(canvas);
347
+ const { logical, intDeviceWidth } = this.resolveStrokeWidth(weight, Math.max(sx, sy));
348
+ paint.setStrokeWidth(logical);
349
+ paint.setPathEffect(null);
350
+ // Shadow strokes inherit the shadow's silhouette colour set
351
+ // above — they are not painted with the real stroke fill.
352
+ for (const shape of shapes) {
353
+ const snapped = this.snapPath(canvas, intDeviceWidth, shape.ckPath);
354
+ this.drawStroke(canvas, paint, shape, stroke, logical, intDeviceWidth);
355
+ if (snapped)
356
+ canvas.restore();
357
+ }
358
+ }
359
+ }
360
+ canvas.restore();
361
+ canvas.restore();
362
+ layerPaint.delete();
363
+ }
364
+ paint.setStyle(this.canvasKit.PaintStyle.Fill);
365
+ paint.setPathEffect(null);
366
+ paint.setBlendMode(this.canvasKit.BlendMode.SrcOver);
367
+ paint.setAlphaf(1.0);
368
+ paint.setShader(null);
369
+ }
370
+ }
371
+ //# sourceMappingURL=stroke-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stroke-handler.js","sourceRoot":"","sources":["../../src/stroke/stroke-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ export type ExtractSource = string | Blob;
2
+ export interface DecodedBitmap {
3
+ tsUs: number;
4
+ bitmap: ImageBitmap;
5
+ width: number;
6
+ height: number;
7
+ }
8
+ export type FrameListener = (frame: DecodedBitmap) => void;
9
+ /**
10
+ * A live decoding session against a single video source. Frames are not
11
+ * pre-decoded — instead, callers request ranges around the current playhead
12
+ * via {@link decodeRange}, and a worker pumps frames into the registered
13
+ * listener as they arrive.
14
+ *
15
+ * Cancelling: a fresh `decodeRange` call bumps an internal generation; the
16
+ * worker's in-flight loop notices on its next yield and stops. This makes
17
+ * playhead jumps cheap — no need to wait out the previous range.
18
+ */
19
+ export declare class VideoStream {
20
+ private readonly jobId;
21
+ private readonly src;
22
+ private readonly targetWidth?;
23
+ private readonly targetHeight?;
24
+ private listener;
25
+ /**
26
+ * Worker uses generation numbers to discard stale frames mid-decode.
27
+ * `expectedGeneration` is the latest one we asked for; any frame whose
28
+ * generation matches is delivered to the listener, others are closed.
29
+ */
30
+ private expectedGeneration;
31
+ private openPromise;
32
+ private rangeWaiters;
33
+ private closed;
34
+ constructor(src: ExtractSource, targetWidth?: number, targetHeight?: number);
35
+ onFrame(cb: FrameListener): void;
36
+ open(): Promise<{
37
+ width: number;
38
+ height: number;
39
+ }>;
40
+ /**
41
+ * Ask the worker to decode the half-open range [startSeconds, endSeconds)
42
+ * at `fps`. Cancels any prior in-flight range for this stream. Returns a
43
+ * promise that resolves when this range is fully decoded.
44
+ *
45
+ * `skipBeforeTsUs`/`skipAfterTsUs` define a [from, to] microsecond window
46
+ * the caller already has cached — frames inside that window are decoded
47
+ * but not transferred to save bus traffic and GPU memory.
48
+ */
49
+ decodeRange(startSeconds: number, endSeconds: number, fps: number, skipBeforeTsUs?: number, skipAfterTsUs?: number): Promise<void>;
50
+ /** Abort the current range without starting a new one. */
51
+ cancel(): void;
52
+ close(): void;
53
+ }
54
+ //# sourceMappingURL=extract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/video/extract.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,IAAI,CAAC;AAE1C,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAqE3D;;;;;;;;;GASG;AACH,qBAAa,WAAW;IACpB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAgB;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAS;IACvC,OAAO,CAAC,QAAQ,CAA8B;IAE9C;;;;OAIG;IACH,OAAO,CAAC,kBAAkB,CAAa;IACvC,OAAO,CAAC,WAAW,CAA2D;IAC9E,OAAO,CAAC,YAAY,CAAsF;IAE1G,OAAO,CAAC,MAAM,CAAkB;gBAEpB,GAAG,EAAE,aAAa,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM;IAO3E,OAAO,CAAC,EAAE,EAAE,aAAa,GAAG,IAAI;IAIhC,IAAI,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IA0ClD;;;;;;;;OAQG;IACH,WAAW,CACP,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,cAAc,CAAC,EAAE,MAAM,EACvB,aAAa,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,IAAI,CAAC;IAmBhB,0DAA0D;IAC1D,MAAM,IAAI,IAAI;IAMd,KAAK,IAAI,IAAI;CAYhB"}
@@ -0,0 +1,192 @@
1
+ // ─── Shared worker plumbing ──────────────────────────────────────────────────
2
+ let sharedWorker = null;
3
+ let workerUnavailable = false;
4
+ let nextJobId = 1;
5
+ const routers = new Map();
6
+ function getWorker() {
7
+ if (workerUnavailable)
8
+ return null;
9
+ if (sharedWorker)
10
+ return sharedWorker;
11
+ try {
12
+ sharedWorker = new Worker(new URL("./extract.worker.js", import.meta.url), {
13
+ type: "module",
14
+ });
15
+ sharedWorker.addEventListener("message", (event) => {
16
+ const msg = event.data;
17
+ const router = routers.get(msg.jobId);
18
+ if (!router) {
19
+ if (msg.type === "frame")
20
+ msg.bitmap.close();
21
+ return;
22
+ }
23
+ switch (msg.type) {
24
+ case "opened":
25
+ router.onOpened?.(msg.sourceWidth, msg.sourceHeight);
26
+ break;
27
+ case "frame":
28
+ router.onFrame?.(msg.tsUs, msg.bitmap, msg.width, msg.height, msg.generation);
29
+ break;
30
+ case "range-done":
31
+ router.onRangeDone?.(msg.generation);
32
+ break;
33
+ case "error":
34
+ router.onError?.(msg.message);
35
+ break;
36
+ }
37
+ });
38
+ sharedWorker.addEventListener("error", (err) => {
39
+ workerUnavailable = true;
40
+ const message = err.message || "extract worker crashed";
41
+ for (const [, router] of routers)
42
+ router.onError?.(message);
43
+ routers.clear();
44
+ sharedWorker = null;
45
+ });
46
+ return sharedWorker;
47
+ }
48
+ catch {
49
+ workerUnavailable = true;
50
+ return null;
51
+ }
52
+ }
53
+ function send(req, transfer) {
54
+ const w = getWorker();
55
+ if (!w)
56
+ return false;
57
+ w.postMessage(req, transfer ?? []);
58
+ return true;
59
+ }
60
+ // ─── VideoStream: one per source ─────────────────────────────────────────────
61
+ /**
62
+ * A live decoding session against a single video source. Frames are not
63
+ * pre-decoded — instead, callers request ranges around the current playhead
64
+ * via {@link decodeRange}, and a worker pumps frames into the registered
65
+ * listener as they arrive.
66
+ *
67
+ * Cancelling: a fresh `decodeRange` call bumps an internal generation; the
68
+ * worker's in-flight loop notices on its next yield and stops. This makes
69
+ * playhead jumps cheap — no need to wait out the previous range.
70
+ */
71
+ export class VideoStream {
72
+ jobId;
73
+ src;
74
+ targetWidth;
75
+ targetHeight;
76
+ listener = null;
77
+ /**
78
+ * Worker uses generation numbers to discard stale frames mid-decode.
79
+ * `expectedGeneration` is the latest one we asked for; any frame whose
80
+ * generation matches is delivered to the listener, others are closed.
81
+ */
82
+ expectedGeneration = 0;
83
+ openPromise = null;
84
+ rangeWaiters = [];
85
+ closed = false;
86
+ constructor(src, targetWidth, targetHeight) {
87
+ this.jobId = nextJobId++;
88
+ this.src = src;
89
+ this.targetWidth = targetWidth;
90
+ this.targetHeight = targetHeight;
91
+ }
92
+ onFrame(cb) {
93
+ this.listener = cb;
94
+ }
95
+ open() {
96
+ if (this.openPromise)
97
+ return this.openPromise;
98
+ this.openPromise = new Promise((resolve, reject) => {
99
+ const router = {
100
+ onOpened: (w, h) => resolve({ width: w, height: h }),
101
+ onFrame: (tsUs, bitmap, width, height, generation) => {
102
+ if (this.closed || generation !== this.expectedGeneration) {
103
+ bitmap.close();
104
+ return;
105
+ }
106
+ this.listener?.({ tsUs, bitmap, width, height });
107
+ },
108
+ onRangeDone: (generation) => {
109
+ if (generation !== this.expectedGeneration)
110
+ return;
111
+ const waiters = this.rangeWaiters;
112
+ this.rangeWaiters = [];
113
+ for (const w of waiters) {
114
+ if (w.generation === generation)
115
+ w.resolve();
116
+ else
117
+ this.rangeWaiters.push(w);
118
+ }
119
+ },
120
+ onError: (message) => {
121
+ const err = new Error(message);
122
+ const waiters = this.rangeWaiters;
123
+ this.rangeWaiters = [];
124
+ for (const w of waiters)
125
+ w.reject(err);
126
+ reject(err);
127
+ },
128
+ };
129
+ routers.set(this.jobId, router);
130
+ const sent = send({
131
+ type: "open",
132
+ jobId: this.jobId,
133
+ src: this.src,
134
+ targetWidth: this.targetWidth,
135
+ targetHeight: this.targetHeight,
136
+ });
137
+ if (!sent)
138
+ reject(new Error("video extract worker unavailable"));
139
+ });
140
+ return this.openPromise;
141
+ }
142
+ /**
143
+ * Ask the worker to decode the half-open range [startSeconds, endSeconds)
144
+ * at `fps`. Cancels any prior in-flight range for this stream. Returns a
145
+ * promise that resolves when this range is fully decoded.
146
+ *
147
+ * `skipBeforeTsUs`/`skipAfterTsUs` define a [from, to] microsecond window
148
+ * the caller already has cached — frames inside that window are decoded
149
+ * but not transferred to save bus traffic and GPU memory.
150
+ */
151
+ decodeRange(startSeconds, endSeconds, fps, skipBeforeTsUs, skipAfterTsUs) {
152
+ if (this.closed)
153
+ return Promise.resolve();
154
+ const generation = ++this.expectedGeneration;
155
+ const done = new Promise((resolve, reject) => {
156
+ this.rangeWaiters.push({ generation, resolve, reject });
157
+ });
158
+ send({
159
+ type: "decode-range",
160
+ jobId: this.jobId,
161
+ generation,
162
+ startSeconds,
163
+ endSeconds,
164
+ fps,
165
+ skipBeforeTsUs,
166
+ skipAfterTsUs,
167
+ });
168
+ return done;
169
+ }
170
+ /** Abort the current range without starting a new one. */
171
+ cancel() {
172
+ if (this.closed)
173
+ return;
174
+ this.expectedGeneration++;
175
+ send({ type: "cancel-range", jobId: this.jobId });
176
+ }
177
+ close() {
178
+ if (this.closed)
179
+ return;
180
+ this.closed = true;
181
+ this.expectedGeneration++;
182
+ send({ type: "close", jobId: this.jobId });
183
+ routers.delete(this.jobId);
184
+ // Reject any outstanding waiters so callers don't hang.
185
+ const waiters = this.rangeWaiters;
186
+ this.rangeWaiters = [];
187
+ for (const w of waiters)
188
+ w.resolve();
189
+ this.listener = null;
190
+ }
191
+ }
192
+ //# sourceMappingURL=extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.js","sourceRoot":"","sources":["../../src/video/extract.ts"],"names":[],"mappings":"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"}