@remotion/web-renderer 4.0.395 → 4.0.397

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 (94) hide show
  1. package/dist/border-radius.d.ts +31 -0
  2. package/dist/border-radius.js +152 -0
  3. package/dist/calculate-transforms.d.ts +2 -0
  4. package/dist/calculate-transforms.js +17 -0
  5. package/dist/composable.d.ts +2 -8
  6. package/dist/compose-canvas.js +28 -4
  7. package/dist/compose.d.ts +4 -3
  8. package/dist/compose.js +45 -12
  9. package/dist/create-scaffold.js +10 -3
  10. package/dist/drawing/border-radius.d.ts +3 -5
  11. package/dist/drawing/border-radius.js +12 -34
  12. package/dist/drawing/calculate-transforms.d.ts +12 -2
  13. package/dist/drawing/calculate-transforms.js +38 -19
  14. package/dist/drawing/canvas-offset-from-rect.d.ts +8 -0
  15. package/dist/drawing/canvas-offset-from-rect.js +12 -0
  16. package/dist/drawing/clamp-rect-to-parent-bounds.d.ts +8 -0
  17. package/dist/drawing/clamp-rect-to-parent-bounds.js +18 -0
  18. package/dist/drawing/compose-canvas.d.ts +1 -0
  19. package/dist/drawing/compose-canvas.js +36 -0
  20. package/dist/drawing/compose-svg.d.ts +1 -0
  21. package/dist/drawing/compose-svg.js +34 -0
  22. package/dist/drawing/compose.d.ts +5 -0
  23. package/dist/drawing/compose.js +6 -0
  24. package/dist/drawing/do-rects-intersect.d.ts +1 -0
  25. package/dist/drawing/do-rects-intersect.js +6 -0
  26. package/dist/drawing/draw-border.d.ts +2 -5
  27. package/dist/drawing/draw-border.js +37 -37
  28. package/dist/drawing/draw-box-shadow.d.ts +18 -0
  29. package/dist/drawing/draw-box-shadow.js +103 -0
  30. package/dist/drawing/draw-element-to-canvas.d.ts +2 -1
  31. package/dist/drawing/draw-element-to-canvas.js +8 -36
  32. package/dist/drawing/draw-element.d.ts +8 -3
  33. package/dist/drawing/draw-element.js +64 -18
  34. package/dist/drawing/draw-outline.d.ts +9 -0
  35. package/dist/drawing/draw-outline.js +93 -0
  36. package/dist/drawing/draw-rounded.d.ts +9 -0
  37. package/dist/drawing/draw-rounded.js +34 -0
  38. package/dist/drawing/get-bounding-box-including-shadow.d.ts +1 -0
  39. package/dist/drawing/get-bounding-box-including-shadow.js +6 -0
  40. package/dist/drawing/get-computed-style-cache.d.ts +0 -0
  41. package/dist/drawing/get-computed-style-cache.js +1 -0
  42. package/dist/drawing/get-pretransform-rect.d.ts +1 -0
  43. package/dist/drawing/get-pretransform-rect.js +36 -0
  44. package/dist/drawing/handle-3d-transform.d.ts +11 -0
  45. package/dist/drawing/handle-3d-transform.js +25 -0
  46. package/dist/drawing/handle-mask.d.ts +8 -0
  47. package/dist/drawing/handle-mask.js +19 -0
  48. package/dist/drawing/has-transform.d.ts +4 -0
  49. package/dist/drawing/has-transform.js +14 -0
  50. package/dist/drawing/mask-image.d.ts +3 -0
  51. package/dist/drawing/mask-image.js +14 -0
  52. package/dist/drawing/overflow.d.ts +7 -0
  53. package/dist/drawing/overflow.js +12 -0
  54. package/dist/drawing/parse-linear-gradient.d.ts +14 -0
  55. package/dist/drawing/parse-linear-gradient.js +260 -0
  56. package/dist/drawing/precompose.d.ts +11 -0
  57. package/dist/drawing/precompose.js +13 -0
  58. package/dist/drawing/process-node.d.ts +18 -0
  59. package/dist/drawing/process-node.js +116 -0
  60. package/dist/drawing/round-to-expand-rect.d.ts +1 -0
  61. package/dist/drawing/round-to-expand-rect.js +7 -0
  62. package/dist/drawing/text/draw-text.d.ts +5 -1
  63. package/dist/drawing/text/draw-text.js +10 -5
  64. package/dist/drawing/text/find-line-breaks.text.d.ts +1 -1
  65. package/dist/drawing/text/find-line-breaks.text.js +2 -2
  66. package/dist/drawing/text/handle-text-node.d.ts +7 -5
  67. package/dist/drawing/text/handle-text-node.js +7 -6
  68. package/dist/drawing/transform-in-3d.d.ts +9 -7
  69. package/dist/drawing/transform-in-3d.js +41 -31
  70. package/dist/drawing/transform-rect-with-matrix.d.ts +4 -0
  71. package/dist/drawing/transform-rect-with-matrix.js +19 -0
  72. package/dist/drawing/transform.d.ts +2 -1
  73. package/dist/drawing/transform.js +6 -2
  74. package/dist/esm/index.mjs +1138 -278
  75. package/dist/find-canvas-elements.d.ts +1 -0
  76. package/dist/find-canvas-elements.js +13 -0
  77. package/dist/find-capturable-elements.d.ts +1 -1
  78. package/dist/find-capturable-elements.js +20 -22
  79. package/dist/get-biggest-bounding-client-rect.js +29 -4
  80. package/dist/internal-state.d.ts +9 -0
  81. package/dist/internal-state.js +12 -0
  82. package/dist/opacity.d.ts +4 -0
  83. package/dist/opacity.js +7 -0
  84. package/dist/render-media-on-web.d.ts +3 -0
  85. package/dist/render-media-on-web.js +27 -14
  86. package/dist/render-still-on-web.d.ts +5 -1
  87. package/dist/render-still-on-web.js +4 -1
  88. package/dist/take-screenshot.d.ts +5 -2
  89. package/dist/take-screenshot.js +16 -4
  90. package/dist/transform.d.ts +4 -0
  91. package/dist/transform.js +6 -0
  92. package/package.json +6 -6
  93. package/dist/drawing/text/get-base-height.d.ts +0 -1
  94. package/dist/drawing/text/get-base-height.js +0 -13
@@ -0,0 +1,31 @@
1
+ export type BorderRadiusCorners = {
2
+ topLeft: {
3
+ horizontal: number;
4
+ vertical: number;
5
+ };
6
+ topRight: {
7
+ horizontal: number;
8
+ vertical: number;
9
+ };
10
+ bottomRight: {
11
+ horizontal: number;
12
+ vertical: number;
13
+ };
14
+ bottomLeft: {
15
+ horizontal: number;
16
+ vertical: number;
17
+ };
18
+ };
19
+ export declare function parseBorderRadius({ borderRadius, width, height, }: {
20
+ borderRadius: string;
21
+ width: number;
22
+ height: number;
23
+ }): BorderRadiusCorners;
24
+ export declare function setBorderRadius({ ctx, x, y, width, height, borderRadius, }: {
25
+ ctx: OffscreenCanvasRenderingContext2D;
26
+ x: number;
27
+ y: number;
28
+ width: number;
29
+ height: number;
30
+ borderRadius: BorderRadiusCorners;
31
+ }): () => void;
@@ -0,0 +1,152 @@
1
+ function parseValue({ value, reference, }) {
2
+ value = value.trim();
3
+ if (value.endsWith('%')) {
4
+ const percentage = parseFloat(value);
5
+ return (percentage / 100) * reference;
6
+ }
7
+ if (value.endsWith('px')) {
8
+ return parseFloat(value);
9
+ }
10
+ // If no unit, assume pixels
11
+ return parseFloat(value);
12
+ }
13
+ function expandShorthand(values) {
14
+ if (values.length === 1) {
15
+ // All corners the same
16
+ return [values[0], values[0], values[0], values[0]];
17
+ }
18
+ if (values.length === 2) {
19
+ // [0] = top-left & bottom-right, [1] = top-right & bottom-left
20
+ return [values[0], values[1], values[0], values[1]];
21
+ }
22
+ if (values.length === 3) {
23
+ // [0] = top-left, [1] = top-right & bottom-left, [2] = bottom-right
24
+ return [values[0], values[1], values[2], values[1]];
25
+ }
26
+ // 4 values: top-left, top-right, bottom-right, bottom-left
27
+ return [values[0], values[1], values[2], values[3]];
28
+ }
29
+ function clampBorderRadius({ borderRadius, width, height, }) {
30
+ // According to CSS spec, if the sum of border radii on adjacent corners
31
+ // exceeds the length of the edge, they should be proportionally reduced
32
+ const clamped = {
33
+ topLeft: { ...borderRadius.topLeft },
34
+ topRight: { ...borderRadius.topRight },
35
+ bottomRight: { ...borderRadius.bottomRight },
36
+ bottomLeft: { ...borderRadius.bottomLeft },
37
+ };
38
+ // Check top edge
39
+ const topSum = clamped.topLeft.horizontal + clamped.topRight.horizontal;
40
+ if (topSum > width) {
41
+ const factor = width / topSum;
42
+ clamped.topLeft.horizontal *= factor;
43
+ clamped.topRight.horizontal *= factor;
44
+ }
45
+ // Check right edge
46
+ const rightSum = clamped.topRight.vertical + clamped.bottomRight.vertical;
47
+ if (rightSum > height) {
48
+ const factor = height / rightSum;
49
+ clamped.topRight.vertical *= factor;
50
+ clamped.bottomRight.vertical *= factor;
51
+ }
52
+ // Check bottom edge
53
+ const bottomSum = clamped.bottomRight.horizontal + clamped.bottomLeft.horizontal;
54
+ if (bottomSum > width) {
55
+ const factor = width / bottomSum;
56
+ clamped.bottomRight.horizontal *= factor;
57
+ clamped.bottomLeft.horizontal *= factor;
58
+ }
59
+ // Check left edge
60
+ const leftSum = clamped.bottomLeft.vertical + clamped.topLeft.vertical;
61
+ if (leftSum > height) {
62
+ const factor = height / leftSum;
63
+ clamped.bottomLeft.vertical *= factor;
64
+ clamped.topLeft.vertical *= factor;
65
+ }
66
+ return clamped;
67
+ }
68
+ export function parseBorderRadius({ borderRadius, width, height, }) {
69
+ // Split by '/' to separate horizontal and vertical radii
70
+ const parts = borderRadius.split('/').map((part) => part.trim());
71
+ const horizontalPart = parts[0];
72
+ const verticalPart = parts[1];
73
+ // Split each part into individual values
74
+ const horizontalValues = horizontalPart.split(/\s+/).filter((v) => v);
75
+ const verticalValues = verticalPart
76
+ ? verticalPart.split(/\s+/).filter((v) => v)
77
+ : horizontalValues; // If no '/', use horizontal values for vertical
78
+ // Expand shorthand to 4 values
79
+ const [hTopLeft, hTopRight, hBottomRight, hBottomLeft] = expandShorthand(horizontalValues);
80
+ const [vTopLeft, vTopRight, vBottomRight, vBottomLeft] = expandShorthand(verticalValues);
81
+ return clampBorderRadius({
82
+ borderRadius: {
83
+ topLeft: {
84
+ horizontal: parseValue({ value: hTopLeft, reference: width }),
85
+ vertical: parseValue({ value: vTopLeft, reference: height }),
86
+ },
87
+ topRight: {
88
+ horizontal: parseValue({ value: hTopRight, reference: width }),
89
+ vertical: parseValue({ value: vTopRight, reference: height }),
90
+ },
91
+ bottomRight: {
92
+ horizontal: parseValue({ value: hBottomRight, reference: width }),
93
+ vertical: parseValue({ value: vBottomRight, reference: height }),
94
+ },
95
+ bottomLeft: {
96
+ horizontal: parseValue({ value: hBottomLeft, reference: width }),
97
+ vertical: parseValue({ value: vBottomLeft, reference: height }),
98
+ },
99
+ },
100
+ width,
101
+ height,
102
+ });
103
+ }
104
+ export function setBorderRadius({ ctx, x, y, width, height, borderRadius, }) {
105
+ if (borderRadius.topLeft.horizontal === 0 &&
106
+ borderRadius.topLeft.vertical === 0 &&
107
+ borderRadius.topRight.horizontal === 0 &&
108
+ borderRadius.topRight.vertical === 0 &&
109
+ borderRadius.bottomRight.horizontal === 0 &&
110
+ borderRadius.bottomRight.vertical === 0 &&
111
+ borderRadius.bottomLeft.horizontal === 0 &&
112
+ borderRadius.bottomLeft.vertical === 0) {
113
+ return () => { };
114
+ }
115
+ ctx.save();
116
+ ctx.beginPath();
117
+ // Start at top-left corner, after the horizontal radius
118
+ ctx.moveTo(x + borderRadius.topLeft.horizontal, y);
119
+ // Top edge to top-right corner
120
+ ctx.lineTo(x + width - borderRadius.topRight.horizontal, y);
121
+ // Top-right corner (elliptical arc)
122
+ if (borderRadius.topRight.horizontal > 0 ||
123
+ borderRadius.topRight.vertical > 0) {
124
+ ctx.ellipse(x + width - borderRadius.topRight.horizontal, y + borderRadius.topRight.vertical, borderRadius.topRight.horizontal, borderRadius.topRight.vertical, 0, -Math.PI / 2, 0);
125
+ }
126
+ // Right edge to bottom-right corner
127
+ ctx.lineTo(x + width, y + height - borderRadius.bottomRight.vertical);
128
+ // Bottom-right corner (elliptical arc)
129
+ if (borderRadius.bottomRight.horizontal > 0 ||
130
+ borderRadius.bottomRight.vertical > 0) {
131
+ ctx.ellipse(x + width - borderRadius.bottomRight.horizontal, y + height - borderRadius.bottomRight.vertical, borderRadius.bottomRight.horizontal, borderRadius.bottomRight.vertical, 0, 0, Math.PI / 2);
132
+ }
133
+ // Bottom edge to bottom-left corner
134
+ ctx.lineTo(x + borderRadius.bottomLeft.horizontal, y + height);
135
+ // Bottom-left corner (elliptical arc)
136
+ if (borderRadius.bottomLeft.horizontal > 0 ||
137
+ borderRadius.bottomLeft.vertical > 0) {
138
+ ctx.ellipse(x + borderRadius.bottomLeft.horizontal, y + height - borderRadius.bottomLeft.vertical, borderRadius.bottomLeft.horizontal, borderRadius.bottomLeft.vertical, 0, Math.PI / 2, Math.PI);
139
+ }
140
+ // Left edge to top-left corner
141
+ ctx.lineTo(x, y + borderRadius.topLeft.vertical);
142
+ // Top-left corner (elliptical arc)
143
+ if (borderRadius.topLeft.horizontal > 0 ||
144
+ borderRadius.topLeft.vertical > 0) {
145
+ ctx.ellipse(x + borderRadius.topLeft.horizontal, y + borderRadius.topLeft.vertical, borderRadius.topLeft.horizontal, borderRadius.topLeft.vertical, 0, Math.PI, (Math.PI * 3) / 2);
146
+ }
147
+ ctx.closePath();
148
+ ctx.clip();
149
+ return () => {
150
+ ctx.restore();
151
+ };
152
+ }
@@ -6,4 +6,6 @@ export declare const calculateTransforms: (element: HTMLElement | SVGSVGElement)
6
6
  x: number;
7
7
  y: number;
8
8
  };
9
+ borderRadius: import("./drawing/border-radius").BorderRadiusCorners;
10
+ opacity: number;
9
11
  };
@@ -1,3 +1,4 @@
1
+ import { parseBorderRadius } from './drawing/border-radius';
1
2
  import { parseTransformOrigin } from './parse-transform-origin';
2
3
  const getInternalTransformOrigin = (transform) => {
3
4
  var _a;
@@ -21,8 +22,18 @@ export const calculateTransforms = (element) => {
21
22
  let parent = element;
22
23
  const transforms = [];
23
24
  const toReset = [];
25
+ let borderRadius = '';
26
+ let opacity = 1;
24
27
  while (parent) {
25
28
  const computedStyle = getComputedStyle(parent);
29
+ if (parent === element) {
30
+ borderRadius = computedStyle.borderRadius;
31
+ }
32
+ // Multiply opacity values from element and all parents
33
+ const parentOpacity = computedStyle.opacity;
34
+ if (parentOpacity && parentOpacity !== '') {
35
+ opacity *= parseFloat(parentOpacity);
36
+ }
26
37
  if ((computedStyle.transform && computedStyle.transform !== 'none') ||
27
38
  parent === element) {
28
39
  const toParse = computedStyle.transform === 'none' || computedStyle.transform === ''
@@ -70,5 +81,11 @@ export const calculateTransforms = (element) => {
70
81
  }
71
82
  },
72
83
  nativeTransformOrigin,
84
+ borderRadius: parseBorderRadius({
85
+ borderRadius,
86
+ width: dimensions.width,
87
+ height: dimensions.height,
88
+ }),
89
+ opacity,
73
90
  };
74
91
  };
@@ -1,10 +1,4 @@
1
1
  export type Composable = {
2
- type: 'canvas';
3
- element: HTMLCanvasElement;
4
- } | {
5
- type: 'svg';
6
- element: SVGSVGElement;
7
- } | {
8
- type: 'img';
9
- element: HTMLImageElement;
2
+ type: 'element';
3
+ element: HTMLElement | SVGElement;
10
4
  };
@@ -1,12 +1,36 @@
1
+ import { setBorderRadius } from './border-radius';
1
2
  import { calculateTransforms } from './calculate-transforms';
2
- import { turnSvgIntoDrawable } from './compose-svg';
3
+ import { turnSvgIntoDrawable } from './drawing/compose-svg';
4
+ import { setOpacity } from './drawing/opacity';
5
+ import { setTransform } from './drawing/transform';
3
6
  export const composeCanvas = async (canvas, context) => {
4
- const { totalMatrix, reset, dimensions } = calculateTransforms(canvas);
5
- context.setTransform(totalMatrix);
7
+ const { totalMatrix, reset, dimensions, borderRadius, opacity } = calculateTransforms(canvas);
8
+ if (opacity === 0) {
9
+ reset();
10
+ return;
11
+ }
6
12
  const drawable = canvas instanceof SVGSVGElement
7
13
  ? await turnSvgIntoDrawable(canvas)
8
14
  : canvas;
15
+ const finishTransform = setTransform({
16
+ ctx: context,
17
+ transform: totalMatrix,
18
+ });
19
+ const finishBorderRadius = setBorderRadius({
20
+ ctx: context,
21
+ x: dimensions.left,
22
+ y: dimensions.top,
23
+ width: dimensions.width,
24
+ height: dimensions.height,
25
+ borderRadius,
26
+ });
27
+ const finishOpacity = setOpacity({
28
+ ctx: context,
29
+ opacity,
30
+ });
9
31
  context.drawImage(drawable, dimensions.left, dimensions.top, dimensions.width, dimensions.height);
10
- context.setTransform(new DOMMatrix());
32
+ finishOpacity();
33
+ finishBorderRadius();
34
+ finishTransform();
11
35
  reset();
12
36
  };
package/dist/compose.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import type { LogLevel } from 'remotion';
2
- export declare const compose: ({ element, context, offsetLeft, offsetTop, logLevel, }: {
2
+ import type { InternalState } from './internal-state';
3
+ export declare const compose: ({ element, context, logLevel, parentRect, internalState, }: {
3
4
  element: HTMLElement | SVGElement;
4
5
  context: OffscreenCanvasRenderingContext2D;
5
- offsetLeft: number;
6
- offsetTop: number;
7
6
  logLevel: LogLevel;
7
+ parentRect: DOMRect;
8
+ internalState: InternalState;
8
9
  }) => Promise<void>;
package/dist/compose.js CHANGED
@@ -1,24 +1,33 @@
1
1
  import { drawDomElement } from './drawing/draw-dom-element';
2
- import { drawElementToCanvas } from './drawing/draw-element-to-canvas';
2
+ import { processNode } from './drawing/process-node';
3
3
  import { handleTextNode } from './drawing/text/handle-text-node';
4
4
  import { skipToNextNonDescendant } from './walk-tree';
5
- const walkOverNode = ({ node, context, offsetLeft, offsetTop, logLevel, }) => {
5
+ const walkOverNode = ({ node, context, logLevel, parentRect, internalState, rootElement, }) => {
6
6
  if (node instanceof HTMLElement || node instanceof SVGElement) {
7
- return drawElementToCanvas({
7
+ return processNode({
8
8
  element: node,
9
9
  context,
10
10
  draw: drawDomElement(node),
11
- offsetLeft,
12
- offsetTop,
13
11
  logLevel,
12
+ parentRect,
13
+ internalState,
14
+ rootElement,
14
15
  });
15
16
  }
16
17
  if (node instanceof Text) {
17
- return handleTextNode({ node, context, offsetLeft, offsetTop, logLevel });
18
+ return handleTextNode({
19
+ node,
20
+ context,
21
+ logLevel,
22
+ parentRect,
23
+ internalState,
24
+ rootElement,
25
+ });
18
26
  }
19
27
  throw new Error('Unknown node type');
20
28
  };
21
- export const compose = async ({ element, context, offsetLeft, offsetTop, logLevel, }) => {
29
+ export const compose = async ({ element, context, logLevel, parentRect, internalState, }) => {
30
+ const cleanupAfterChildren = [];
22
31
  const treeWalker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, (node) => {
23
32
  if (node instanceof Element) {
24
33
  // SVG does have children, but we process SVG elements in its
@@ -34,20 +43,44 @@ export const compose = async ({ element, context, offsetLeft, offsetTop, logLeve
34
43
  return NodeFilter.FILTER_ACCEPT;
35
44
  });
36
45
  while (true) {
46
+ for (let i = 0; i < cleanupAfterChildren.length;) {
47
+ const cleanup = cleanupAfterChildren[i];
48
+ if (!(cleanup.element === treeWalker.currentNode ||
49
+ cleanup.element.contains(treeWalker.currentNode))) {
50
+ cleanup.cleanupFn();
51
+ cleanupAfterChildren.splice(i, 1);
52
+ }
53
+ else {
54
+ i++;
55
+ }
56
+ }
37
57
  const val = await walkOverNode({
38
58
  node: treeWalker.currentNode,
39
59
  context,
40
- offsetLeft,
41
- offsetTop,
42
60
  logLevel,
61
+ parentRect,
62
+ internalState,
63
+ rootElement: element,
43
64
  });
44
- if (val === 'skip-children') {
65
+ if (val.type === 'skip-children') {
45
66
  if (!skipToNextNonDescendant(treeWalker)) {
46
67
  break;
47
68
  }
48
69
  }
49
- else if (!treeWalker.nextNode()) {
50
- break;
70
+ else {
71
+ if (val.cleanupAfterChildren) {
72
+ // Last registered must be cleaned up first
73
+ cleanupAfterChildren.unshift({
74
+ element: treeWalker.currentNode,
75
+ cleanupFn: val.cleanupAfterChildren,
76
+ });
77
+ }
78
+ if (!treeWalker.nextNode()) {
79
+ break;
80
+ }
51
81
  }
52
82
  }
83
+ for (const cleanup of cleanupAfterChildren) {
84
+ cleanup.cleanupFn();
85
+ }
53
86
  };
@@ -10,17 +10,23 @@ export async function createScaffold({ width, height, delayRenderTimeoutInMillis
10
10
  throw new Error('@remotion/web-renderer requires React 18 or higher');
11
11
  }
12
12
  const div = document.createElement('div');
13
- // Match same behavior as renderEntry.tsx
13
+ // Match same behavior as in portal-node.ts
14
+ div.style.position = 'fixed';
14
15
  div.style.display = 'flex';
16
+ div.style.flexDirection = 'column';
15
17
  div.style.backgroundColor = 'transparent';
16
- div.style.position = 'fixed';
17
18
  div.style.width = `${width}px`;
18
19
  div.style.height = `${height}px`;
19
20
  div.style.zIndex = '-9999';
20
21
  div.style.top = '0';
21
- div.style.visibility = 'hidden';
22
22
  div.style.left = '0';
23
+ div.style.right = '0';
24
+ div.style.bottom = '0';
25
+ div.style.visibility = 'hidden';
23
26
  div.style.pointerEvents = 'none';
27
+ const scaffoldClassName = `remotion-scaffold-${Math.random().toString(36).substring(2, 15)}`;
28
+ div.className = scaffoldClassName;
29
+ const cleanupCSS = Internals.CSSUtils.injectCSS(Internals.CSSUtils.makeDefaultPreviewCSS(`.${scaffoldClassName}`, 'white'));
24
30
  document.body.appendChild(div);
25
31
  const { promise, resolve, reject } = withResolvers();
26
32
  // TODO: This might not work in React 18
@@ -90,6 +96,7 @@ export async function createScaffold({ width, height, delayRenderTimeoutInMillis
90
96
  cleanupScaffold: () => {
91
97
  root.unmount();
92
98
  div.remove();
99
+ cleanupCSS();
93
100
  },
94
101
  timeUpdater,
95
102
  collectAssets,
@@ -21,11 +21,9 @@ export declare function parseBorderRadius({ borderRadius, width, height, }: {
21
21
  width: number;
22
22
  height: number;
23
23
  }): BorderRadiusCorners;
24
- export declare function setBorderRadius({ ctx, x, y, width, height, borderRadius, }: {
24
+ export declare function setBorderRadius({ ctx, rect, borderRadius, forceClipEvenWhenZero, }: {
25
25
  ctx: OffscreenCanvasRenderingContext2D;
26
- x: number;
27
- y: number;
28
- width: number;
29
- height: number;
26
+ rect: DOMRect;
30
27
  borderRadius: BorderRadiusCorners;
28
+ forceClipEvenWhenZero: boolean;
31
29
  }): () => void;
@@ -1,3 +1,4 @@
1
+ import { drawRoundedRectPath } from './draw-rounded';
1
2
  function parseValue({ value, reference, }) {
2
3
  value = value.trim();
3
4
  if (value.endsWith('%')) {
@@ -101,7 +102,7 @@ export function parseBorderRadius({ borderRadius, width, height, }) {
101
102
  height,
102
103
  });
103
104
  }
104
- export function setBorderRadius({ ctx, x, y, width, height, borderRadius, }) {
105
+ export function setBorderRadius({ ctx, rect, borderRadius, forceClipEvenWhenZero = false, }) {
105
106
  if (borderRadius.topLeft.horizontal === 0 &&
106
107
  borderRadius.topLeft.vertical === 0 &&
107
108
  borderRadius.topRight.horizontal === 0 &&
@@ -109,42 +110,19 @@ export function setBorderRadius({ ctx, x, y, width, height, borderRadius, }) {
109
110
  borderRadius.bottomRight.horizontal === 0 &&
110
111
  borderRadius.bottomRight.vertical === 0 &&
111
112
  borderRadius.bottomLeft.horizontal === 0 &&
112
- borderRadius.bottomLeft.vertical === 0) {
113
+ borderRadius.bottomLeft.vertical === 0 &&
114
+ !forceClipEvenWhenZero) {
113
115
  return () => { };
114
116
  }
115
117
  ctx.save();
116
- ctx.beginPath();
117
- // Start at top-left corner, after the horizontal radius
118
- ctx.moveTo(x + borderRadius.topLeft.horizontal, y);
119
- // Top edge to top-right corner
120
- ctx.lineTo(x + width - borderRadius.topRight.horizontal, y);
121
- // Top-right corner (elliptical arc)
122
- if (borderRadius.topRight.horizontal > 0 ||
123
- borderRadius.topRight.vertical > 0) {
124
- ctx.ellipse(x + width - borderRadius.topRight.horizontal, y + borderRadius.topRight.vertical, borderRadius.topRight.horizontal, borderRadius.topRight.vertical, 0, -Math.PI / 2, 0);
125
- }
126
- // Right edge to bottom-right corner
127
- ctx.lineTo(x + width, y + height - borderRadius.bottomRight.vertical);
128
- // Bottom-right corner (elliptical arc)
129
- if (borderRadius.bottomRight.horizontal > 0 ||
130
- borderRadius.bottomRight.vertical > 0) {
131
- ctx.ellipse(x + width - borderRadius.bottomRight.horizontal, y + height - borderRadius.bottomRight.vertical, borderRadius.bottomRight.horizontal, borderRadius.bottomRight.vertical, 0, 0, Math.PI / 2);
132
- }
133
- // Bottom edge to bottom-left corner
134
- ctx.lineTo(x + borderRadius.bottomLeft.horizontal, y + height);
135
- // Bottom-left corner (elliptical arc)
136
- if (borderRadius.bottomLeft.horizontal > 0 ||
137
- borderRadius.bottomLeft.vertical > 0) {
138
- ctx.ellipse(x + borderRadius.bottomLeft.horizontal, y + height - borderRadius.bottomLeft.vertical, borderRadius.bottomLeft.horizontal, borderRadius.bottomLeft.vertical, 0, Math.PI / 2, Math.PI);
139
- }
140
- // Left edge to top-left corner
141
- ctx.lineTo(x, y + borderRadius.topLeft.vertical);
142
- // Top-left corner (elliptical arc)
143
- if (borderRadius.topLeft.horizontal > 0 ||
144
- borderRadius.topLeft.vertical > 0) {
145
- ctx.ellipse(x + borderRadius.topLeft.horizontal, y + borderRadius.topLeft.vertical, borderRadius.topLeft.horizontal, borderRadius.topLeft.vertical, 0, Math.PI, (Math.PI * 3) / 2);
146
- }
147
- ctx.closePath();
118
+ drawRoundedRectPath({
119
+ ctx,
120
+ x: rect.left,
121
+ y: rect.top,
122
+ width: rect.width,
123
+ height: rect.height,
124
+ borderRadius,
125
+ });
148
126
  ctx.clip();
149
127
  return () => {
150
128
  ctx.restore();
@@ -1,4 +1,8 @@
1
- export declare const calculateTransforms: (element: HTMLElement | SVGElement) => {
1
+ import type { LinearGradientInfo } from './parse-linear-gradient';
2
+ export declare const calculateTransforms: ({ element, rootElement, }: {
3
+ element: HTMLElement | SVGElement;
4
+ rootElement: HTMLElement | SVGElement;
5
+ }) => {
2
6
  dimensions: DOMRect;
3
7
  totalMatrix: DOMMatrix;
4
8
  reset: () => void;
@@ -6,6 +10,12 @@ export declare const calculateTransforms: (element: HTMLElement | SVGElement) =>
6
10
  x: number;
7
11
  y: number;
8
12
  };
9
- opacity: number;
10
13
  computedStyle: CSSStyleDeclaration;
14
+ opacity: number;
15
+ maskImageInfo: LinearGradientInfo | null;
16
+ precompositing: {
17
+ needs3DTransformViaWebGL: boolean;
18
+ needsMaskImage: LinearGradientInfo | null;
19
+ needsPrecompositing: boolean;
20
+ };
11
21
  };
@@ -1,3 +1,5 @@
1
+ import { hasAnyTransformCssValue, hasTransformCssValue } from './has-transform';
2
+ import { getMaskImageValue, parseMaskImage } from './mask-image';
1
3
  import { parseTransformOrigin } from './parse-transform-origin';
2
4
  const getInternalTransformOrigin = (transform) => {
3
5
  var _a;
@@ -9,35 +11,42 @@ const getInternalTransformOrigin = (transform) => {
9
11
  };
10
12
  return origin;
11
13
  };
12
- const getGlobalTransformOrigin = (transform) => {
14
+ const getGlobalTransformOrigin = ({ transform }) => {
13
15
  const { x: originX, y: originY } = getInternalTransformOrigin(transform);
14
16
  return {
15
17
  x: originX + transform.boundingClientRect.left,
16
18
  y: originY + transform.boundingClientRect.top,
17
19
  };
18
20
  };
19
- export const calculateTransforms = (element) => {
21
+ export const calculateTransforms = ({ element, rootElement, }) => {
20
22
  // Compute the cumulative transform by traversing parent nodes
21
23
  let parent = element;
22
24
  const transforms = [];
23
25
  const toReset = [];
24
26
  let opacity = 1;
25
27
  let elementComputedStyle = null;
28
+ let maskImageInfo = null;
26
29
  while (parent) {
27
30
  const computedStyle = getComputedStyle(parent);
28
- // Multiply opacity values from element and all parents
29
- const parentOpacity = computedStyle.opacity;
30
- if (parentOpacity && parentOpacity !== '') {
31
- opacity *= parseFloat(parentOpacity);
32
- }
33
31
  if (parent === element) {
34
32
  elementComputedStyle = computedStyle;
33
+ opacity = parseFloat(computedStyle.opacity);
34
+ const maskImageValue = getMaskImageValue(computedStyle);
35
+ maskImageInfo = maskImageValue ? parseMaskImage(maskImageValue) : null;
36
+ const originalMaskImage = parent.style.maskImage;
37
+ const originalWebkitMaskImage = parent.style.webkitMaskImage;
38
+ parent.style.maskImage = 'none';
39
+ parent.style.webkitMaskImage = 'none';
40
+ const parentRef = parent;
41
+ toReset.push(() => {
42
+ parentRef.style.maskImage = originalMaskImage;
43
+ parentRef.style.webkitMaskImage = originalWebkitMaskImage;
44
+ });
35
45
  }
36
- if ((computedStyle.transform && computedStyle.transform !== 'none') ||
37
- parent === element) {
38
- const toParse = computedStyle.transform === 'none' || computedStyle.transform === ''
39
- ? undefined
40
- : computedStyle.transform;
46
+ if (hasAnyTransformCssValue(computedStyle) || parent === element) {
47
+ const toParse = hasTransformCssValue(computedStyle)
48
+ ? computedStyle.transform
49
+ : undefined;
41
50
  const matrix = new DOMMatrix(toParse);
42
51
  const { transform, scale, rotate } = parent.style;
43
52
  const additionalMatrices = [];
@@ -57,7 +66,7 @@ export const calculateTransforms = (element) => {
57
66
  parent.style.scale = 'none';
58
67
  parent.style.rotate = 'none';
59
68
  transforms.push({
60
- rect: parent,
69
+ element: parent,
61
70
  transformOrigin: computedStyle.transformOrigin,
62
71
  boundingClientRect: null,
63
72
  matrices: additionalMatrices,
@@ -69,20 +78,22 @@ export const calculateTransforms = (element) => {
69
78
  parentRef.style.rotate = rotate;
70
79
  });
71
80
  }
81
+ if (parent === rootElement) {
82
+ break;
83
+ }
72
84
  parent = parent.parentElement;
73
85
  }
74
86
  for (const transform of transforms) {
75
- transform.boundingClientRect = transform.rect.getBoundingClientRect();
87
+ transform.boundingClientRect = transform.element.getBoundingClientRect();
76
88
  }
77
89
  const dimensions = transforms[0].boundingClientRect;
78
90
  const nativeTransformOrigin = getInternalTransformOrigin(transforms[0]);
79
91
  const totalMatrix = new DOMMatrix();
80
92
  for (const transform of transforms.slice().reverse()) {
81
- if (!transform.boundingClientRect) {
82
- throw new Error('Bounding client rect not found');
83
- }
84
93
  for (const matrix of transform.matrices) {
85
- const globalTransformOrigin = getGlobalTransformOrigin(transform);
94
+ const globalTransformOrigin = getGlobalTransformOrigin({
95
+ transform,
96
+ });
86
97
  const transformMatrix = new DOMMatrix()
87
98
  .translate(globalTransformOrigin.x, globalTransformOrigin.y)
88
99
  .multiply(matrix)
@@ -93,6 +104,8 @@ export const calculateTransforms = (element) => {
93
104
  if (!elementComputedStyle) {
94
105
  throw new Error('Element computed style not found');
95
106
  }
107
+ const needs3DTransformViaWebGL = !totalMatrix.is2D;
108
+ const needsMaskImage = maskImageInfo !== null;
96
109
  return {
97
110
  dimensions,
98
111
  totalMatrix,
@@ -102,7 +115,13 @@ export const calculateTransforms = (element) => {
102
115
  }
103
116
  },
104
117
  nativeTransformOrigin,
105
- opacity,
106
118
  computedStyle: elementComputedStyle,
119
+ opacity,
120
+ maskImageInfo,
121
+ precompositing: {
122
+ needs3DTransformViaWebGL,
123
+ needsMaskImage: maskImageInfo,
124
+ needsPrecompositing: Boolean(needs3DTransformViaWebGL || needsMaskImage),
125
+ },
107
126
  };
108
127
  };
@@ -0,0 +1,8 @@
1
+ export declare const canvasOffsetFromRect: ({ rect }: {
2
+ rect: DOMRect;
3
+ }) => {
4
+ offsetLeft: number;
5
+ offsetTop: number;
6
+ canvasWidth: number;
7
+ canvasHeight: number;
8
+ };