@remotion/web-renderer 4.0.396 → 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.
- package/dist/compose.js +11 -5
- package/dist/create-scaffold.js +10 -3
- package/dist/drawing/border-radius.js +9 -32
- package/dist/drawing/calculate-transforms.d.ts +9 -3
- package/dist/drawing/calculate-transforms.js +31 -9
- package/dist/drawing/clamp-rect-to-parent-bounds.d.ts +4 -0
- package/dist/drawing/clamp-rect-to-parent-bounds.js +11 -0
- package/dist/drawing/do-rects-intersect.d.ts +1 -0
- package/dist/drawing/do-rects-intersect.js +6 -0
- package/dist/drawing/draw-box-shadow.d.ts +18 -0
- package/dist/drawing/draw-box-shadow.js +103 -0
- package/dist/drawing/draw-element.d.ts +4 -1
- package/dist/drawing/draw-element.js +37 -6
- package/dist/drawing/draw-outline.js +9 -32
- package/dist/drawing/draw-rounded.d.ts +9 -0
- package/dist/drawing/draw-rounded.js +34 -0
- package/dist/drawing/get-pretransform-rect.js +5 -0
- package/dist/drawing/handle-3d-transform.d.ts +9 -8
- package/dist/drawing/handle-3d-transform.js +11 -25
- package/dist/drawing/handle-mask.d.ts +8 -0
- package/dist/drawing/handle-mask.js +19 -0
- package/dist/drawing/mask-image.d.ts +3 -0
- package/dist/drawing/mask-image.js +14 -0
- package/dist/drawing/parse-linear-gradient.d.ts +14 -0
- package/dist/drawing/parse-linear-gradient.js +260 -0
- package/dist/drawing/precompose.d.ts +11 -0
- package/dist/drawing/precompose.js +13 -0
- package/dist/drawing/process-node.d.ts +4 -3
- package/dist/drawing/process-node.js +89 -14
- package/dist/drawing/round-to-expand-rect.d.ts +1 -0
- package/dist/drawing/round-to-expand-rect.js +7 -0
- package/dist/drawing/text/draw-text.d.ts +5 -1
- package/dist/drawing/text/draw-text.js +10 -5
- package/dist/drawing/text/find-line-breaks.text.d.ts +1 -1
- package/dist/drawing/text/find-line-breaks.text.js +2 -2
- package/dist/drawing/text/handle-text-node.d.ts +2 -1
- package/dist/drawing/text/handle-text-node.js +3 -2
- package/dist/drawing/transform-in-3d.d.ts +3 -1
- package/dist/drawing/transform-in-3d.js +30 -28
- package/dist/drawing/transform.d.ts +2 -1
- package/dist/drawing/transform.js +6 -2
- package/dist/esm/index.mjs +788 -211
- package/dist/get-biggest-bounding-client-rect.js +19 -4
- package/dist/internal-state.d.ts +2 -2
- package/dist/internal-state.js +7 -7
- package/dist/render-media-on-web.d.ts +1 -0
- package/dist/render-media-on-web.js +22 -14
- package/package.json +6 -6
package/dist/compose.js
CHANGED
|
@@ -2,7 +2,7 @@ import { drawDomElement } from './drawing/draw-dom-element';
|
|
|
2
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, logLevel, parentRect, internalState, }) => {
|
|
5
|
+
const walkOverNode = ({ node, context, logLevel, parentRect, internalState, rootElement, }) => {
|
|
6
6
|
if (node instanceof HTMLElement || node instanceof SVGElement) {
|
|
7
7
|
return processNode({
|
|
8
8
|
element: node,
|
|
@@ -11,6 +11,7 @@ const walkOverNode = ({ node, context, logLevel, parentRect, internalState, }) =
|
|
|
11
11
|
logLevel,
|
|
12
12
|
parentRect,
|
|
13
13
|
internalState,
|
|
14
|
+
rootElement,
|
|
14
15
|
});
|
|
15
16
|
}
|
|
16
17
|
if (node instanceof Text) {
|
|
@@ -20,6 +21,7 @@ const walkOverNode = ({ node, context, logLevel, parentRect, internalState, }) =
|
|
|
20
21
|
logLevel,
|
|
21
22
|
parentRect,
|
|
22
23
|
internalState,
|
|
24
|
+
rootElement,
|
|
23
25
|
});
|
|
24
26
|
}
|
|
25
27
|
throw new Error('Unknown node type');
|
|
@@ -58,6 +60,7 @@ export const compose = async ({ element, context, logLevel, parentRect, internal
|
|
|
58
60
|
logLevel,
|
|
59
61
|
parentRect,
|
|
60
62
|
internalState,
|
|
63
|
+
rootElement: element,
|
|
61
64
|
});
|
|
62
65
|
if (val.type === 'skip-children') {
|
|
63
66
|
if (!skipToNextNonDescendant(treeWalker)) {
|
|
@@ -65,10 +68,13 @@ export const compose = async ({ element, context, logLevel, parentRect, internal
|
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
70
|
else {
|
|
68
|
-
cleanupAfterChildren
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
+
}
|
|
72
78
|
if (!treeWalker.nextNode()) {
|
|
73
79
|
break;
|
|
74
80
|
}
|
package/dist/create-scaffold.js
CHANGED
|
@@ -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
|
|
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,
|
|
@@ -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('%')) {
|
|
@@ -114,38 +115,14 @@ export function setBorderRadius({ ctx, rect, borderRadius, forceClipEvenWhenZero
|
|
|
114
115
|
return () => { };
|
|
115
116
|
}
|
|
116
117
|
ctx.save();
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
ctx.ellipse(rect.left + rect.width - borderRadius.topRight.horizontal, rect.top + borderRadius.topRight.vertical, borderRadius.topRight.horizontal, borderRadius.topRight.vertical, 0, -Math.PI / 2, 0);
|
|
126
|
-
}
|
|
127
|
-
// Right edge to bottom-right corner
|
|
128
|
-
ctx.lineTo(rect.left + rect.width, rect.top + rect.height - borderRadius.bottomRight.vertical);
|
|
129
|
-
// Bottom-right corner (elliptical arc)
|
|
130
|
-
if (borderRadius.bottomRight.horizontal > 0 ||
|
|
131
|
-
borderRadius.bottomRight.vertical > 0) {
|
|
132
|
-
ctx.ellipse(rect.left + rect.width - borderRadius.bottomRight.horizontal, rect.top + rect.height - borderRadius.bottomRight.vertical, borderRadius.bottomRight.horizontal, borderRadius.bottomRight.vertical, 0, 0, Math.PI / 2);
|
|
133
|
-
}
|
|
134
|
-
// Bottom edge to bottom-left corner
|
|
135
|
-
ctx.lineTo(rect.left + borderRadius.bottomLeft.horizontal, rect.top + rect.height);
|
|
136
|
-
// Bottom-left corner (elliptical arc)
|
|
137
|
-
if (borderRadius.bottomLeft.horizontal > 0 ||
|
|
138
|
-
borderRadius.bottomLeft.vertical > 0) {
|
|
139
|
-
ctx.ellipse(rect.left + borderRadius.bottomLeft.horizontal, rect.top + rect.height - borderRadius.bottomLeft.vertical, borderRadius.bottomLeft.horizontal, borderRadius.bottomLeft.vertical, 0, Math.PI / 2, Math.PI);
|
|
140
|
-
}
|
|
141
|
-
// Left edge to top-left corner
|
|
142
|
-
ctx.lineTo(rect.left, rect.top + borderRadius.topLeft.vertical);
|
|
143
|
-
// Top-left corner (elliptical arc)
|
|
144
|
-
if (borderRadius.topLeft.horizontal > 0 ||
|
|
145
|
-
borderRadius.topLeft.vertical > 0) {
|
|
146
|
-
ctx.ellipse(rect.left + borderRadius.topLeft.horizontal, rect.top + borderRadius.topLeft.vertical, borderRadius.topLeft.horizontal, borderRadius.topLeft.vertical, 0, Math.PI, (Math.PI * 3) / 2);
|
|
147
|
-
}
|
|
148
|
-
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
|
+
});
|
|
149
126
|
ctx.clip();
|
|
150
127
|
return () => {
|
|
151
128
|
ctx.restore();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import type { LinearGradientInfo } from './parse-linear-gradient';
|
|
2
|
+
export declare const calculateTransforms: ({ element, rootElement, }: {
|
|
2
3
|
element: HTMLElement | SVGElement;
|
|
3
|
-
|
|
4
|
-
offsetTop: number;
|
|
4
|
+
rootElement: HTMLElement | SVGElement;
|
|
5
5
|
}) => {
|
|
6
6
|
dimensions: DOMRect;
|
|
7
7
|
totalMatrix: DOMMatrix;
|
|
@@ -12,4 +12,10 @@ export declare const calculateTransforms: ({ element, offsetLeft, offsetTop, }:
|
|
|
12
12
|
};
|
|
13
13
|
computedStyle: CSSStyleDeclaration;
|
|
14
14
|
opacity: number;
|
|
15
|
+
maskImageInfo: LinearGradientInfo | null;
|
|
16
|
+
precompositing: {
|
|
17
|
+
needs3DTransformViaWebGL: boolean;
|
|
18
|
+
needsMaskImage: LinearGradientInfo | null;
|
|
19
|
+
needsPrecompositing: boolean;
|
|
20
|
+
};
|
|
15
21
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hasAnyTransformCssValue, hasTransformCssValue } from './has-transform';
|
|
2
|
+
import { getMaskImageValue, parseMaskImage } from './mask-image';
|
|
2
3
|
import { parseTransformOrigin } from './parse-transform-origin';
|
|
3
4
|
const getInternalTransformOrigin = (transform) => {
|
|
4
5
|
var _a;
|
|
@@ -10,23 +11,37 @@ const getInternalTransformOrigin = (transform) => {
|
|
|
10
11
|
};
|
|
11
12
|
return origin;
|
|
12
13
|
};
|
|
13
|
-
const getGlobalTransformOrigin = ({ transform
|
|
14
|
+
const getGlobalTransformOrigin = ({ transform }) => {
|
|
14
15
|
const { x: originX, y: originY } = getInternalTransformOrigin(transform);
|
|
15
16
|
return {
|
|
16
|
-
x: originX + transform.boundingClientRect.left
|
|
17
|
-
y: originY + transform.boundingClientRect.top
|
|
17
|
+
x: originX + transform.boundingClientRect.left,
|
|
18
|
+
y: originY + transform.boundingClientRect.top,
|
|
18
19
|
};
|
|
19
20
|
};
|
|
20
|
-
export const calculateTransforms = ({ element,
|
|
21
|
+
export const calculateTransforms = ({ element, rootElement, }) => {
|
|
21
22
|
// Compute the cumulative transform by traversing parent nodes
|
|
22
23
|
let parent = element;
|
|
23
24
|
const transforms = [];
|
|
24
25
|
const toReset = [];
|
|
26
|
+
let opacity = 1;
|
|
25
27
|
let elementComputedStyle = null;
|
|
28
|
+
let maskImageInfo = null;
|
|
26
29
|
while (parent) {
|
|
27
30
|
const computedStyle = getComputedStyle(parent);
|
|
28
31
|
if (parent === element) {
|
|
29
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
|
+
});
|
|
30
45
|
}
|
|
31
46
|
if (hasAnyTransformCssValue(computedStyle) || parent === element) {
|
|
32
47
|
const toParse = hasTransformCssValue(computedStyle)
|
|
@@ -63,6 +78,9 @@ export const calculateTransforms = ({ element, offsetLeft, offsetTop, }) => {
|
|
|
63
78
|
parentRef.style.rotate = rotate;
|
|
64
79
|
});
|
|
65
80
|
}
|
|
81
|
+
if (parent === rootElement) {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
66
84
|
parent = parent.parentElement;
|
|
67
85
|
}
|
|
68
86
|
for (const transform of transforms) {
|
|
@@ -75,8 +93,6 @@ export const calculateTransforms = ({ element, offsetLeft, offsetTop, }) => {
|
|
|
75
93
|
for (const matrix of transform.matrices) {
|
|
76
94
|
const globalTransformOrigin = getGlobalTransformOrigin({
|
|
77
95
|
transform,
|
|
78
|
-
offsetLeft,
|
|
79
|
-
offsetTop,
|
|
80
96
|
});
|
|
81
97
|
const transformMatrix = new DOMMatrix()
|
|
82
98
|
.translate(globalTransformOrigin.x, globalTransformOrigin.y)
|
|
@@ -88,6 +104,8 @@ export const calculateTransforms = ({ element, offsetLeft, offsetTop, }) => {
|
|
|
88
104
|
if (!elementComputedStyle) {
|
|
89
105
|
throw new Error('Element computed style not found');
|
|
90
106
|
}
|
|
107
|
+
const needs3DTransformViaWebGL = !totalMatrix.is2D;
|
|
108
|
+
const needsMaskImage = maskImageInfo !== null;
|
|
91
109
|
return {
|
|
92
110
|
dimensions,
|
|
93
111
|
totalMatrix,
|
|
@@ -98,8 +116,12 @@ export const calculateTransforms = ({ element, offsetLeft, offsetTop, }) => {
|
|
|
98
116
|
},
|
|
99
117
|
nativeTransformOrigin,
|
|
100
118
|
computedStyle: elementComputedStyle,
|
|
101
|
-
opacity
|
|
102
|
-
|
|
103
|
-
|
|
119
|
+
opacity,
|
|
120
|
+
maskImageInfo,
|
|
121
|
+
precompositing: {
|
|
122
|
+
needs3DTransformViaWebGL,
|
|
123
|
+
needsMaskImage: maskImageInfo,
|
|
124
|
+
needsPrecompositing: Boolean(needs3DTransformViaWebGL || needsMaskImage),
|
|
125
|
+
},
|
|
104
126
|
};
|
|
105
127
|
};
|
|
@@ -2,3 +2,7 @@ export declare const getNarrowerRect: ({ firstRect, secondRect, }: {
|
|
|
2
2
|
firstRect: DOMRect;
|
|
3
3
|
secondRect: DOMRect;
|
|
4
4
|
}) => DOMRect;
|
|
5
|
+
export declare const getWiderRectAndExpand: ({ firstRect, secondRect, }: {
|
|
6
|
+
firstRect: DOMRect | null;
|
|
7
|
+
secondRect: DOMRect;
|
|
8
|
+
}) => DOMRect;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { roundToExpandRect } from './round-to-expand-rect';
|
|
1
2
|
export const getNarrowerRect = ({ firstRect, secondRect, }) => {
|
|
2
3
|
const left = Math.max(firstRect.left, secondRect.left);
|
|
3
4
|
const top = Math.max(firstRect.top, secondRect.top);
|
|
@@ -5,3 +6,13 @@ export const getNarrowerRect = ({ firstRect, secondRect, }) => {
|
|
|
5
6
|
const right = Math.min(firstRect.right, secondRect.right);
|
|
6
7
|
return new DOMRect(left, top, right - left, bottom - top);
|
|
7
8
|
};
|
|
9
|
+
export const getWiderRectAndExpand = ({ firstRect, secondRect, }) => {
|
|
10
|
+
if (firstRect === null) {
|
|
11
|
+
return roundToExpandRect(secondRect);
|
|
12
|
+
}
|
|
13
|
+
const left = Math.min(firstRect.left, secondRect.left);
|
|
14
|
+
const top = Math.min(firstRect.top, secondRect.top);
|
|
15
|
+
const bottom = Math.max(firstRect.bottom, secondRect.bottom);
|
|
16
|
+
const right = Math.max(firstRect.right, secondRect.right);
|
|
17
|
+
return roundToExpandRect(new DOMRect(left, top, right - left, bottom - top));
|
|
18
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function doRectsIntersect(rect1: DOMRect, rect2: DOMRect): boolean;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { LogLevel } from 'remotion';
|
|
2
|
+
import type { BorderRadiusCorners } from './border-radius';
|
|
3
|
+
interface BoxShadow {
|
|
4
|
+
offsetX: number;
|
|
5
|
+
offsetY: number;
|
|
6
|
+
blurRadius: number;
|
|
7
|
+
color: string;
|
|
8
|
+
inset: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare const parseBoxShadow: (boxShadowValue: string) => BoxShadow[];
|
|
11
|
+
export declare const setBoxShadow: ({ ctx, rect, borderRadius, computedStyle, logLevel, }: {
|
|
12
|
+
ctx: OffscreenCanvasRenderingContext2D;
|
|
13
|
+
rect: DOMRect;
|
|
14
|
+
borderRadius: BorderRadiusCorners;
|
|
15
|
+
computedStyle: CSSStyleDeclaration;
|
|
16
|
+
logLevel: LogLevel;
|
|
17
|
+
}) => void;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Internals } from 'remotion';
|
|
2
|
+
import { drawRoundedRectPath } from './draw-rounded';
|
|
3
|
+
export const parseBoxShadow = (boxShadowValue) => {
|
|
4
|
+
if (!boxShadowValue || boxShadowValue === 'none') {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
const shadows = [];
|
|
8
|
+
// Split by comma, but respect rgba() colors
|
|
9
|
+
const shadowStrings = boxShadowValue.split(/,(?![^(]*\))/);
|
|
10
|
+
for (const shadowStr of shadowStrings) {
|
|
11
|
+
const trimmed = shadowStr.trim();
|
|
12
|
+
if (!trimmed || trimmed === 'none') {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const shadow = {
|
|
16
|
+
offsetX: 0,
|
|
17
|
+
offsetY: 0,
|
|
18
|
+
blurRadius: 0,
|
|
19
|
+
color: 'rgba(0, 0, 0, 0.5)',
|
|
20
|
+
inset: false,
|
|
21
|
+
};
|
|
22
|
+
// Check for inset
|
|
23
|
+
shadow.inset = /\binset\b/i.test(trimmed);
|
|
24
|
+
// Remove 'inset' keyword
|
|
25
|
+
let remaining = trimmed.replace(/\binset\b/gi, '').trim();
|
|
26
|
+
// Extract color (can be rgb(), rgba(), hsl(), hsla(), hex, or named color)
|
|
27
|
+
const colorMatch = remaining.match(/(rgba?\([^)]+\)|hsla?\([^)]+\)|#[0-9a-f]{3,8}|[a-z]+)/i);
|
|
28
|
+
if (colorMatch) {
|
|
29
|
+
shadow.color = colorMatch[0];
|
|
30
|
+
remaining = remaining.replace(colorMatch[0], '').trim();
|
|
31
|
+
}
|
|
32
|
+
// Parse remaining numeric values (offset-x offset-y blur spread)
|
|
33
|
+
const numbers = remaining.match(/[+-]?\d*\.?\d+(?:px|em|rem|%)?/gi) || [];
|
|
34
|
+
const values = numbers.map((n) => parseFloat(n) || 0);
|
|
35
|
+
if (values.length >= 2) {
|
|
36
|
+
shadow.offsetX = values[0];
|
|
37
|
+
shadow.offsetY = values[1];
|
|
38
|
+
if (values.length >= 3) {
|
|
39
|
+
shadow.blurRadius = Math.max(0, values[2]); // Blur cannot be negative
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
shadows.push(shadow);
|
|
43
|
+
}
|
|
44
|
+
return shadows;
|
|
45
|
+
};
|
|
46
|
+
export const setBoxShadow = ({ ctx, rect, borderRadius, computedStyle, logLevel, }) => {
|
|
47
|
+
const shadows = parseBoxShadow(computedStyle.boxShadow);
|
|
48
|
+
if (shadows.length === 0) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Draw shadows from last to first (so first shadow appears on top)
|
|
52
|
+
for (let i = shadows.length - 1; i >= 0; i--) {
|
|
53
|
+
const shadow = shadows[i];
|
|
54
|
+
const newLeft = rect.left + Math.min(shadow.offsetX, 0) - shadow.blurRadius;
|
|
55
|
+
const newRight = rect.right + Math.max(shadow.offsetX, 0) + shadow.blurRadius;
|
|
56
|
+
const newTop = rect.top + Math.min(shadow.offsetY, 0) - shadow.blurRadius;
|
|
57
|
+
const newBottom = rect.bottom + Math.max(shadow.offsetY, 0) + shadow.blurRadius;
|
|
58
|
+
const newRect = new DOMRect(newLeft, newTop, newRight - newLeft, newBottom - newTop);
|
|
59
|
+
const leftOffset = rect.left - newLeft;
|
|
60
|
+
const topOffset = rect.top - newTop;
|
|
61
|
+
const newCanvas = new OffscreenCanvas(newRect.width, newRect.height);
|
|
62
|
+
const newCtx = newCanvas.getContext('2d');
|
|
63
|
+
if (!newCtx) {
|
|
64
|
+
throw new Error('Failed to get context');
|
|
65
|
+
}
|
|
66
|
+
if (shadow.inset) {
|
|
67
|
+
// TODO: Only warn once per render.
|
|
68
|
+
Internals.Log.warn({
|
|
69
|
+
logLevel,
|
|
70
|
+
tag: '@remotion/web-renderer',
|
|
71
|
+
}, 'Detected "box-shadow" with "inset". This is not yet supported in @remotion/web-renderer');
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
// Apply shadow properties to canvas
|
|
75
|
+
newCtx.shadowBlur = shadow.blurRadius;
|
|
76
|
+
newCtx.shadowColor = shadow.color;
|
|
77
|
+
newCtx.shadowOffsetX = shadow.offsetX;
|
|
78
|
+
newCtx.shadowOffsetY = shadow.offsetY;
|
|
79
|
+
newCtx.fillStyle = 'black';
|
|
80
|
+
drawRoundedRectPath({
|
|
81
|
+
ctx: newCtx,
|
|
82
|
+
x: leftOffset,
|
|
83
|
+
y: topOffset,
|
|
84
|
+
width: rect.width,
|
|
85
|
+
height: rect.height,
|
|
86
|
+
borderRadius,
|
|
87
|
+
});
|
|
88
|
+
newCtx.fill();
|
|
89
|
+
// Cut out the shape, leaving only shadow
|
|
90
|
+
newCtx.shadowColor = 'transparent';
|
|
91
|
+
newCtx.globalCompositeOperation = 'destination-out';
|
|
92
|
+
drawRoundedRectPath({
|
|
93
|
+
ctx: newCtx,
|
|
94
|
+
x: leftOffset,
|
|
95
|
+
y: topOffset,
|
|
96
|
+
width: rect.width,
|
|
97
|
+
height: rect.height,
|
|
98
|
+
borderRadius,
|
|
99
|
+
});
|
|
100
|
+
newCtx.fill();
|
|
101
|
+
ctx.drawImage(newCanvas, rect.left - leftOffset, rect.top - topOffset);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import type { LogLevel } from 'remotion';
|
|
1
2
|
import type { DrawFn } from './drawn-fn';
|
|
2
|
-
export declare const drawElement: ({ rect, computedStyle, context, draw, opacity, totalMatrix, }: {
|
|
3
|
+
export declare const drawElement: ({ rect, computedStyle, context, draw, opacity, totalMatrix, parentRect, logLevel, }: {
|
|
3
4
|
rect: DOMRect;
|
|
4
5
|
computedStyle: CSSStyleDeclaration;
|
|
5
6
|
context: OffscreenCanvasRenderingContext2D;
|
|
6
7
|
opacity: number;
|
|
7
8
|
totalMatrix: DOMMatrix;
|
|
8
9
|
draw: DrawFn;
|
|
10
|
+
parentRect: DOMRect;
|
|
11
|
+
logLevel: LogLevel;
|
|
9
12
|
}) => Promise<{
|
|
10
13
|
cleanupAfterChildren: () => void;
|
|
11
14
|
}>;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { parseBorderRadius, setBorderRadius } from './border-radius';
|
|
2
2
|
import { drawBorder } from './draw-border';
|
|
3
|
+
import { setBoxShadow } from './draw-box-shadow';
|
|
3
4
|
import { drawOutline } from './draw-outline';
|
|
4
5
|
import { setOpacity } from './opacity';
|
|
5
6
|
import { setOverflowHidden } from './overflow';
|
|
7
|
+
import { createCanvasGradient, parseLinearGradient, } from './parse-linear-gradient';
|
|
6
8
|
import { setTransform } from './transform';
|
|
7
|
-
export const drawElement = async ({ rect, computedStyle, context, draw, opacity, totalMatrix, }) => {
|
|
9
|
+
export const drawElement = async ({ rect, computedStyle, context, draw, opacity, totalMatrix, parentRect, logLevel, }) => {
|
|
8
10
|
const background = computedStyle.backgroundColor;
|
|
11
|
+
const { backgroundImage } = computedStyle;
|
|
9
12
|
const borderRadius = parseBorderRadius({
|
|
10
13
|
borderRadius: computedStyle.borderRadius,
|
|
11
14
|
width: rect.width,
|
|
@@ -14,18 +17,46 @@ export const drawElement = async ({ rect, computedStyle, context, draw, opacity,
|
|
|
14
17
|
const finishTransform = setTransform({
|
|
15
18
|
ctx: context,
|
|
16
19
|
transform: totalMatrix,
|
|
20
|
+
parentRect,
|
|
17
21
|
});
|
|
18
|
-
const
|
|
22
|
+
const finishOpacity = setOpacity({
|
|
19
23
|
ctx: context,
|
|
24
|
+
opacity,
|
|
25
|
+
});
|
|
26
|
+
// Draw box shadow before border radius clip and background
|
|
27
|
+
setBoxShadow({
|
|
28
|
+
ctx: context,
|
|
29
|
+
computedStyle,
|
|
20
30
|
rect,
|
|
21
31
|
borderRadius,
|
|
22
|
-
|
|
32
|
+
logLevel,
|
|
23
33
|
});
|
|
24
|
-
const
|
|
34
|
+
const finishBorderRadius = setBorderRadius({
|
|
25
35
|
ctx: context,
|
|
26
|
-
|
|
36
|
+
rect,
|
|
37
|
+
borderRadius,
|
|
38
|
+
forceClipEvenWhenZero: false,
|
|
27
39
|
});
|
|
28
|
-
|
|
40
|
+
// Try to draw linear gradient first
|
|
41
|
+
let gradientDrawn = false;
|
|
42
|
+
if (backgroundImage && backgroundImage !== 'none') {
|
|
43
|
+
const gradientInfo = parseLinearGradient(backgroundImage);
|
|
44
|
+
if (gradientInfo) {
|
|
45
|
+
const gradient = createCanvasGradient({
|
|
46
|
+
ctx: context,
|
|
47
|
+
rect,
|
|
48
|
+
gradientInfo,
|
|
49
|
+
});
|
|
50
|
+
const originalFillStyle = context.fillStyle;
|
|
51
|
+
context.fillStyle = gradient;
|
|
52
|
+
context.fillRect(rect.left, rect.top, rect.width, rect.height);
|
|
53
|
+
context.fillStyle = originalFillStyle;
|
|
54
|
+
gradientDrawn = true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Fallback to solid background color if no gradient was drawn
|
|
58
|
+
if (!gradientDrawn &&
|
|
59
|
+
background &&
|
|
29
60
|
background !== 'transparent' &&
|
|
30
61
|
!(background.startsWith('rgba') &&
|
|
31
62
|
(background.endsWith(', 0)') || background.endsWith(',0')))) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { drawRoundedRectPath } from './draw-rounded';
|
|
1
2
|
export const parseOutlineWidth = (value) => {
|
|
2
3
|
return parseFloat(value) || 0;
|
|
3
4
|
};
|
|
@@ -28,7 +29,6 @@ export const drawOutline = ({ ctx, rect, borderRadius, computedStyle, }) => {
|
|
|
28
29
|
const originalStrokeStyle = ctx.strokeStyle;
|
|
29
30
|
const originalLineWidth = ctx.lineWidth;
|
|
30
31
|
const originalLineDash = ctx.getLineDash();
|
|
31
|
-
ctx.beginPath();
|
|
32
32
|
ctx.strokeStyle = outlineColor;
|
|
33
33
|
ctx.lineWidth = outlineWidth;
|
|
34
34
|
ctx.setLineDash(getLineDashPattern(outlineStyle, outlineWidth));
|
|
@@ -77,37 +77,14 @@ export const drawOutline = ({ ctx, rect, borderRadius, computedStyle, }) => {
|
|
|
77
77
|
: Math.max(0, borderRadius.bottomLeft.vertical + offset),
|
|
78
78
|
},
|
|
79
79
|
};
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
adjustedBorderRadius
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
// Right edge
|
|
90
|
-
ctx.lineTo(outlineX + outlineW, outlineY + outlineH - adjustedBorderRadius.bottomRight.vertical);
|
|
91
|
-
// Bottom-right corner
|
|
92
|
-
if (adjustedBorderRadius.bottomRight.horizontal > 0 ||
|
|
93
|
-
adjustedBorderRadius.bottomRight.vertical > 0) {
|
|
94
|
-
ctx.ellipse(outlineX + outlineW - adjustedBorderRadius.bottomRight.horizontal, outlineY + outlineH - adjustedBorderRadius.bottomRight.vertical, adjustedBorderRadius.bottomRight.horizontal, adjustedBorderRadius.bottomRight.vertical, 0, 0, Math.PI / 2);
|
|
95
|
-
}
|
|
96
|
-
// Bottom edge
|
|
97
|
-
ctx.lineTo(outlineX + adjustedBorderRadius.bottomLeft.horizontal, outlineY + outlineH);
|
|
98
|
-
// Bottom-left corner
|
|
99
|
-
if (adjustedBorderRadius.bottomLeft.horizontal > 0 ||
|
|
100
|
-
adjustedBorderRadius.bottomLeft.vertical > 0) {
|
|
101
|
-
ctx.ellipse(outlineX + adjustedBorderRadius.bottomLeft.horizontal, outlineY + outlineH - adjustedBorderRadius.bottomLeft.vertical, adjustedBorderRadius.bottomLeft.horizontal, adjustedBorderRadius.bottomLeft.vertical, 0, Math.PI / 2, Math.PI);
|
|
102
|
-
}
|
|
103
|
-
// Left edge
|
|
104
|
-
ctx.lineTo(outlineX, outlineY + adjustedBorderRadius.topLeft.vertical);
|
|
105
|
-
// Top-left corner
|
|
106
|
-
if (adjustedBorderRadius.topLeft.horizontal > 0 ||
|
|
107
|
-
adjustedBorderRadius.topLeft.vertical > 0) {
|
|
108
|
-
ctx.ellipse(outlineX + adjustedBorderRadius.topLeft.horizontal, outlineY + adjustedBorderRadius.topLeft.vertical, adjustedBorderRadius.topLeft.horizontal, adjustedBorderRadius.topLeft.vertical, 0, Math.PI, (Math.PI * 3) / 2);
|
|
109
|
-
}
|
|
110
|
-
ctx.closePath();
|
|
80
|
+
drawRoundedRectPath({
|
|
81
|
+
ctx,
|
|
82
|
+
x: outlineX,
|
|
83
|
+
y: outlineY,
|
|
84
|
+
width: outlineW,
|
|
85
|
+
height: outlineH,
|
|
86
|
+
borderRadius: adjustedBorderRadius,
|
|
87
|
+
});
|
|
111
88
|
ctx.stroke();
|
|
112
89
|
// Restore original canvas state
|
|
113
90
|
ctx.strokeStyle = originalStrokeStyle;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BorderRadiusCorners } from './border-radius';
|
|
2
|
+
export declare const drawRoundedRectPath: ({ ctx, x, y, width, height, borderRadius, }: {
|
|
3
|
+
ctx: OffscreenCanvasRenderingContext2D;
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
borderRadius: BorderRadiusCorners;
|
|
9
|
+
}) => void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const drawRoundedRectPath = ({ ctx, x, y, width, height, borderRadius, }) => {
|
|
2
|
+
ctx.beginPath();
|
|
3
|
+
// Start from top-left corner, after the radius
|
|
4
|
+
ctx.moveTo(x + borderRadius.topLeft.horizontal, y);
|
|
5
|
+
// Top edge
|
|
6
|
+
ctx.lineTo(x + width - borderRadius.topRight.horizontal, y);
|
|
7
|
+
// Top-right corner
|
|
8
|
+
if (borderRadius.topRight.horizontal > 0 ||
|
|
9
|
+
borderRadius.topRight.vertical > 0) {
|
|
10
|
+
ctx.ellipse(x + width - borderRadius.topRight.horizontal, y + borderRadius.topRight.vertical, borderRadius.topRight.horizontal, borderRadius.topRight.vertical, 0, -Math.PI / 2, 0);
|
|
11
|
+
}
|
|
12
|
+
// Right edge
|
|
13
|
+
ctx.lineTo(x + width, y + height - borderRadius.bottomRight.vertical);
|
|
14
|
+
// Bottom-right corner
|
|
15
|
+
if (borderRadius.bottomRight.horizontal > 0 ||
|
|
16
|
+
borderRadius.bottomRight.vertical > 0) {
|
|
17
|
+
ctx.ellipse(x + width - borderRadius.bottomRight.horizontal, y + height - borderRadius.bottomRight.vertical, borderRadius.bottomRight.horizontal, borderRadius.bottomRight.vertical, 0, 0, Math.PI / 2);
|
|
18
|
+
}
|
|
19
|
+
// Bottom edge
|
|
20
|
+
ctx.lineTo(x + borderRadius.bottomLeft.horizontal, y + height);
|
|
21
|
+
// Bottom-left corner
|
|
22
|
+
if (borderRadius.bottomLeft.horizontal > 0 ||
|
|
23
|
+
borderRadius.bottomLeft.vertical > 0) {
|
|
24
|
+
ctx.ellipse(x + borderRadius.bottomLeft.horizontal, y + height - borderRadius.bottomLeft.vertical, borderRadius.bottomLeft.horizontal, borderRadius.bottomLeft.vertical, 0, Math.PI / 2, Math.PI);
|
|
25
|
+
}
|
|
26
|
+
// Left edge
|
|
27
|
+
ctx.lineTo(x, y + borderRadius.topLeft.vertical);
|
|
28
|
+
// Top-left corner
|
|
29
|
+
if (borderRadius.topLeft.horizontal > 0 ||
|
|
30
|
+
borderRadius.topLeft.vertical > 0) {
|
|
31
|
+
ctx.ellipse(x + borderRadius.topLeft.horizontal, y + borderRadius.topLeft.vertical, borderRadius.topLeft.horizontal, borderRadius.topLeft.vertical, 0, Math.PI, (Math.PI * 3) / 2);
|
|
32
|
+
}
|
|
33
|
+
ctx.closePath();
|
|
34
|
+
};
|
|
@@ -16,6 +16,11 @@ export function getPreTransformRect(targetRect, matrix) {
|
|
|
16
16
|
origin.y, // e, f (translation)
|
|
17
17
|
]);
|
|
18
18
|
const inverse2D = effective2D.inverse();
|
|
19
|
+
const wasNotInvertible = isNaN(inverse2D.m11);
|
|
20
|
+
// For example, a 90 degree rotation, is not being rendered
|
|
21
|
+
if (wasNotInvertible) {
|
|
22
|
+
return new DOMRect(0, 0, 0, 0);
|
|
23
|
+
}
|
|
19
24
|
// 4. Transform target rect corners using the 2D inverse
|
|
20
25
|
const corners = [
|
|
21
26
|
new DOMPoint(targetRect.x, targetRect.y),
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
import type { InternalState } from '../internal-state';
|
|
3
|
-
export declare const handle3dTransform: ({ element, matrix, parentRect, context, logLevel, internalState, }: {
|
|
1
|
+
export declare const getPrecomposeRectFor3DTransform: ({ element, parentRect, matrix, }: {
|
|
4
2
|
element: HTMLElement | SVGElement;
|
|
5
|
-
matrix: DOMMatrix;
|
|
6
3
|
parentRect: DOMRect;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
matrix: DOMMatrix;
|
|
5
|
+
}) => DOMRect;
|
|
6
|
+
export declare const handle3dTransform: ({ matrix, precomposeRect, tempCanvas, rectAfterTransforms, }: {
|
|
7
|
+
matrix: DOMMatrix;
|
|
8
|
+
precomposeRect: DOMRect;
|
|
9
|
+
tempCanvas: OffscreenCanvas;
|
|
10
|
+
rectAfterTransforms: DOMRect;
|
|
11
|
+
}) => readonly [OffscreenCanvas, () => void] | null;
|