@remotion/web-renderer 4.0.429 → 4.0.430
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/add-sample.js +20 -0
- package/dist/artifact.js +56 -0
- package/dist/audio.js +42 -0
- package/dist/can-use-webfs-target.js +19 -0
- package/dist/compose.js +85 -0
- package/dist/create-scaffold.js +104 -0
- package/dist/drawing/border-radius.js +151 -0
- package/dist/drawing/calculate-object-fit.js +208 -0
- package/dist/drawing/calculate-transforms.js +127 -0
- package/dist/drawing/clamp-rect-to-parent-bounds.js +18 -0
- package/dist/drawing/do-rects-intersect.js +6 -0
- package/dist/drawing/draw-background.js +62 -0
- package/dist/drawing/draw-border.js +353 -0
- package/dist/drawing/draw-box-shadow.js +103 -0
- package/dist/drawing/draw-dom-element.js +85 -0
- package/dist/drawing/draw-element.js +84 -0
- package/dist/drawing/draw-outline.js +93 -0
- package/dist/drawing/draw-rounded.js +34 -0
- package/dist/drawing/drawn-fn.js +1 -0
- package/dist/drawing/fit-svg-into-its-dimensions.js +35 -0
- package/dist/drawing/get-clipped-background.d.ts +8 -0
- package/dist/drawing/get-clipped-background.js +14 -0
- package/dist/drawing/get-padding-box.js +30 -0
- package/dist/drawing/get-pretransform-rect.js +49 -0
- package/dist/drawing/handle-3d-transform.js +26 -0
- package/dist/drawing/handle-mask.js +21 -0
- package/dist/drawing/has-transform.js +14 -0
- package/dist/drawing/mask-image.js +14 -0
- package/dist/drawing/opacity.js +7 -0
- package/dist/drawing/overflow.js +14 -0
- package/dist/drawing/parse-linear-gradient.js +260 -0
- package/dist/drawing/parse-transform-origin.js +7 -0
- package/dist/drawing/precompose.d.ts +11 -0
- package/dist/drawing/precompose.js +14 -0
- package/dist/drawing/process-node.js +122 -0
- package/dist/drawing/round-to-expand-rect.js +7 -0
- package/dist/drawing/text/apply-text-transform.js +12 -0
- package/dist/drawing/text/draw-text.js +53 -0
- package/dist/drawing/text/find-line-breaks.text.js +118 -0
- package/dist/drawing/text/get-collapsed-text.d.ts +1 -0
- package/dist/drawing/text/get-collapsed-text.js +46 -0
- package/dist/drawing/text/handle-text-node.js +24 -0
- package/dist/drawing/text/parse-paint-order.d.ts +8 -0
- package/dist/drawing/transform-in-3d.js +177 -0
- package/dist/drawing/transform-rect-with-matrix.js +19 -0
- package/dist/drawing/transform.js +10 -0
- package/dist/drawing/turn-svg-into-drawable.js +41 -0
- package/dist/esm/index.mjs +34 -2
- package/dist/frame-range.js +15 -0
- package/dist/get-audio-encoding-config.js +18 -0
- package/dist/get-biggest-bounding-client-rect.js +43 -0
- package/dist/index.js +2 -0
- package/dist/internal-state.js +36 -0
- package/dist/mediabunny-mappings.js +63 -0
- package/dist/output-target.js +1 -0
- package/dist/props-if-has-props.js +1 -0
- package/dist/render-media-on-web.js +304 -0
- package/dist/render-operations-queue.js +3 -0
- package/dist/render-still-on-web.js +110 -0
- package/dist/send-telemetry-event.js +22 -0
- package/dist/take-screenshot.js +30 -0
- package/dist/throttle-progress.js +43 -0
- package/dist/tree-walker-cleanup-after-children.js +33 -0
- package/dist/update-time.js +17 -0
- package/dist/validate-video-frame.js +34 -0
- package/dist/wait-for-ready.js +39 -0
- package/dist/walk-tree.js +14 -0
- package/dist/web-fs-target.js +41 -0
- package/dist/with-resolvers.js +9 -0
- package/package.json +3 -2
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { calculateObjectFit, parseObjectFit } from './calculate-object-fit';
|
|
2
|
+
import { fitSvgIntoItsContainer } from './fit-svg-into-its-dimensions';
|
|
3
|
+
import { turnSvgIntoDrawable } from './turn-svg-into-drawable';
|
|
4
|
+
const getReadableImageError = (err, node) => {
|
|
5
|
+
if (!(err instanceof DOMException)) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
if (err.name === 'SecurityError') {
|
|
9
|
+
return new Error(`Could not draw image with src="${node.src}" to canvas: ` +
|
|
10
|
+
`The image is tainted due to CORS restrictions. ` +
|
|
11
|
+
`The server hosting this image must respond with the "Access-Control-Allow-Origin" header. ` +
|
|
12
|
+
`See: https://remotion.dev/docs/client-side-rendering/migration`);
|
|
13
|
+
}
|
|
14
|
+
if (err.name === 'InvalidStateError') {
|
|
15
|
+
return new Error(`Could not draw image with src="${node.src}" to canvas: ` +
|
|
16
|
+
`The image is in a broken state. ` +
|
|
17
|
+
`This usually means the image failed to load - check that the URL is valid and accessible.`);
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Draw an SVG element using "contain" behavior (the default for SVGs).
|
|
23
|
+
*/
|
|
24
|
+
const drawSvg = ({ drawable, dimensions, contextToDraw, }) => {
|
|
25
|
+
const fitted = fitSvgIntoItsContainer({
|
|
26
|
+
containerSize: dimensions,
|
|
27
|
+
elementSize: {
|
|
28
|
+
width: drawable.width,
|
|
29
|
+
height: drawable.height,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
contextToDraw.drawImage(drawable, fitted.left, fitted.top, fitted.width, fitted.height);
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Draw an image or canvas element using the object-fit CSS property.
|
|
36
|
+
*/
|
|
37
|
+
const drawReplacedElement = ({ drawable, dimensions, computedStyle, contextToDraw, }) => {
|
|
38
|
+
const objectFit = parseObjectFit(computedStyle.objectFit);
|
|
39
|
+
const intrinsicSize = drawable instanceof HTMLImageElement
|
|
40
|
+
? { width: drawable.naturalWidth, height: drawable.naturalHeight }
|
|
41
|
+
: { width: drawable.width, height: drawable.height };
|
|
42
|
+
const result = calculateObjectFit({
|
|
43
|
+
objectFit,
|
|
44
|
+
containerSize: {
|
|
45
|
+
width: dimensions.width,
|
|
46
|
+
height: dimensions.height,
|
|
47
|
+
left: dimensions.left,
|
|
48
|
+
top: dimensions.top,
|
|
49
|
+
},
|
|
50
|
+
intrinsicSize,
|
|
51
|
+
});
|
|
52
|
+
// Use the 9-argument drawImage to support source cropping (for cover mode)
|
|
53
|
+
contextToDraw.drawImage(drawable, result.sourceX, result.sourceY, result.sourceWidth, result.sourceHeight, result.destX, result.destY, result.destWidth, result.destHeight);
|
|
54
|
+
};
|
|
55
|
+
export const drawDomElement = (node) => {
|
|
56
|
+
const domDrawFn = async ({ dimensions, contextToDraw, computedStyle, }) => {
|
|
57
|
+
// Handle SVG elements separately - they use "contain" behavior by default
|
|
58
|
+
if (node instanceof SVGSVGElement) {
|
|
59
|
+
const drawable = await turnSvgIntoDrawable(node);
|
|
60
|
+
drawSvg({ drawable, dimensions, contextToDraw });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Handle replaced elements (img, canvas) with object-fit support
|
|
64
|
+
if (node instanceof HTMLImageElement || node instanceof HTMLCanvasElement) {
|
|
65
|
+
try {
|
|
66
|
+
drawReplacedElement({
|
|
67
|
+
drawable: node,
|
|
68
|
+
dimensions,
|
|
69
|
+
computedStyle,
|
|
70
|
+
contextToDraw,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
if (node instanceof HTMLImageElement) {
|
|
75
|
+
const readableError = getReadableImageError(err, node);
|
|
76
|
+
if (readableError) {
|
|
77
|
+
throw readableError;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
return domDrawFn;
|
|
85
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { parseBorderRadius, setBorderRadius } from './border-radius';
|
|
2
|
+
import { drawBackground } from './draw-background';
|
|
3
|
+
import { drawBorder } from './draw-border';
|
|
4
|
+
import { drawBorderRadius } from './draw-box-shadow';
|
|
5
|
+
import { drawOutline } from './draw-outline';
|
|
6
|
+
import { setOpacity } from './opacity';
|
|
7
|
+
import { setOverflowHidden } from './overflow';
|
|
8
|
+
import { setTransform } from './transform';
|
|
9
|
+
export const drawElement = async ({ rect, computedStyle, context, draw, opacity, totalMatrix, parentRect, logLevel, element, internalState, }) => {
|
|
10
|
+
const { backgroundImage, backgroundColor, backgroundClip } = computedStyle;
|
|
11
|
+
const borderRadius = parseBorderRadius({
|
|
12
|
+
borderRadius: computedStyle.borderRadius,
|
|
13
|
+
width: rect.width,
|
|
14
|
+
height: rect.height,
|
|
15
|
+
});
|
|
16
|
+
const finishTransform = setTransform({
|
|
17
|
+
ctx: context,
|
|
18
|
+
transform: totalMatrix,
|
|
19
|
+
parentRect,
|
|
20
|
+
});
|
|
21
|
+
const finishOpacity = setOpacity({
|
|
22
|
+
ctx: context,
|
|
23
|
+
opacity,
|
|
24
|
+
});
|
|
25
|
+
// Draw box shadow before border radius clip and background
|
|
26
|
+
drawBorderRadius({
|
|
27
|
+
ctx: context,
|
|
28
|
+
computedStyle,
|
|
29
|
+
rect,
|
|
30
|
+
borderRadius,
|
|
31
|
+
logLevel,
|
|
32
|
+
});
|
|
33
|
+
const finishBorderRadius = setBorderRadius({
|
|
34
|
+
ctx: context,
|
|
35
|
+
rect,
|
|
36
|
+
borderRadius,
|
|
37
|
+
forceClipEvenWhenZero: false,
|
|
38
|
+
computedStyle,
|
|
39
|
+
backgroundClip,
|
|
40
|
+
});
|
|
41
|
+
await drawBackground({
|
|
42
|
+
backgroundImage,
|
|
43
|
+
context,
|
|
44
|
+
rect,
|
|
45
|
+
backgroundColor,
|
|
46
|
+
backgroundClip,
|
|
47
|
+
element,
|
|
48
|
+
logLevel,
|
|
49
|
+
internalState,
|
|
50
|
+
computedStyle,
|
|
51
|
+
offsetLeft: parentRect.left,
|
|
52
|
+
offsetTop: parentRect.top,
|
|
53
|
+
});
|
|
54
|
+
await draw({ dimensions: rect, computedStyle, contextToDraw: context });
|
|
55
|
+
finishBorderRadius();
|
|
56
|
+
drawBorder({
|
|
57
|
+
ctx: context,
|
|
58
|
+
rect,
|
|
59
|
+
borderRadius,
|
|
60
|
+
computedStyle,
|
|
61
|
+
});
|
|
62
|
+
// Drawing outline ignores overflow: hidden, finishing it and starting a new one for the outline
|
|
63
|
+
drawOutline({
|
|
64
|
+
ctx: context,
|
|
65
|
+
rect,
|
|
66
|
+
borderRadius,
|
|
67
|
+
computedStyle,
|
|
68
|
+
});
|
|
69
|
+
const finishOverflowHidden = setOverflowHidden({
|
|
70
|
+
ctx: context,
|
|
71
|
+
rect,
|
|
72
|
+
borderRadius,
|
|
73
|
+
overflowHidden: computedStyle.overflow === 'hidden',
|
|
74
|
+
computedStyle,
|
|
75
|
+
backgroundClip,
|
|
76
|
+
});
|
|
77
|
+
finishTransform();
|
|
78
|
+
return {
|
|
79
|
+
cleanupAfterChildren: () => {
|
|
80
|
+
finishOpacity();
|
|
81
|
+
finishOverflowHidden();
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { drawRoundedRectPath } from './draw-rounded';
|
|
2
|
+
export const parseOutlineWidth = (value) => {
|
|
3
|
+
return parseFloat(value) || 0;
|
|
4
|
+
};
|
|
5
|
+
export const parseOutlineOffset = (value) => {
|
|
6
|
+
return parseFloat(value) || 0;
|
|
7
|
+
};
|
|
8
|
+
const getLineDashPattern = (style, width) => {
|
|
9
|
+
if (style === 'dashed') {
|
|
10
|
+
return [width * 2, width];
|
|
11
|
+
}
|
|
12
|
+
if (style === 'dotted') {
|
|
13
|
+
return [width, width];
|
|
14
|
+
}
|
|
15
|
+
return [];
|
|
16
|
+
};
|
|
17
|
+
export const drawOutline = ({ ctx, rect, borderRadius, computedStyle, }) => {
|
|
18
|
+
const outlineWidth = parseOutlineWidth(computedStyle.outlineWidth);
|
|
19
|
+
const { outlineStyle } = computedStyle;
|
|
20
|
+
const outlineColor = computedStyle.outlineColor || 'black';
|
|
21
|
+
const outlineOffset = parseOutlineOffset(computedStyle.outlineOffset);
|
|
22
|
+
// Check if we have a visible outline
|
|
23
|
+
if (outlineWidth <= 0 ||
|
|
24
|
+
outlineStyle === 'none' ||
|
|
25
|
+
outlineStyle === 'hidden') {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Save original canvas state
|
|
29
|
+
const originalStrokeStyle = ctx.strokeStyle;
|
|
30
|
+
const originalLineWidth = ctx.lineWidth;
|
|
31
|
+
const originalLineDash = ctx.getLineDash();
|
|
32
|
+
ctx.strokeStyle = outlineColor;
|
|
33
|
+
ctx.lineWidth = outlineWidth;
|
|
34
|
+
ctx.setLineDash(getLineDashPattern(outlineStyle, outlineWidth));
|
|
35
|
+
// Calculate outline position
|
|
36
|
+
// Outline is drawn outside the border edge, offset by outlineOffset
|
|
37
|
+
const halfWidth = outlineWidth / 2;
|
|
38
|
+
const offset = outlineOffset + halfWidth;
|
|
39
|
+
const outlineX = rect.left - offset;
|
|
40
|
+
const outlineY = rect.top - offset;
|
|
41
|
+
const outlineW = rect.width + offset * 2;
|
|
42
|
+
const outlineH = rect.height + offset * 2;
|
|
43
|
+
// Adjust border radius for the outline offset
|
|
44
|
+
// When outline-offset is positive, we need to expand the radius
|
|
45
|
+
// When outline-offset is negative, we need to shrink the radius
|
|
46
|
+
const adjustedBorderRadius = {
|
|
47
|
+
topLeft: {
|
|
48
|
+
horizontal: borderRadius.topLeft.horizontal === 0
|
|
49
|
+
? 0
|
|
50
|
+
: Math.max(0, borderRadius.topLeft.horizontal + offset),
|
|
51
|
+
vertical: borderRadius.topLeft.vertical === 0
|
|
52
|
+
? 0
|
|
53
|
+
: Math.max(0, borderRadius.topLeft.vertical + offset),
|
|
54
|
+
},
|
|
55
|
+
topRight: {
|
|
56
|
+
horizontal: borderRadius.topRight.horizontal === 0
|
|
57
|
+
? 0
|
|
58
|
+
: Math.max(0, borderRadius.topRight.horizontal + offset),
|
|
59
|
+
vertical: borderRadius.topRight.vertical === 0
|
|
60
|
+
? 0
|
|
61
|
+
: Math.max(0, borderRadius.topRight.vertical + offset),
|
|
62
|
+
},
|
|
63
|
+
bottomRight: {
|
|
64
|
+
horizontal: borderRadius.bottomRight.horizontal === 0
|
|
65
|
+
? 0
|
|
66
|
+
: Math.max(0, borderRadius.bottomRight.horizontal + offset),
|
|
67
|
+
vertical: borderRadius.bottomRight.vertical === 0
|
|
68
|
+
? 0
|
|
69
|
+
: Math.max(0, borderRadius.bottomRight.vertical + offset),
|
|
70
|
+
},
|
|
71
|
+
bottomLeft: {
|
|
72
|
+
horizontal: borderRadius.bottomLeft.horizontal === 0
|
|
73
|
+
? 0
|
|
74
|
+
: Math.max(0, borderRadius.bottomLeft.horizontal + offset),
|
|
75
|
+
vertical: borderRadius.bottomLeft.vertical === 0
|
|
76
|
+
? 0
|
|
77
|
+
: Math.max(0, borderRadius.bottomLeft.vertical + offset),
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
drawRoundedRectPath({
|
|
81
|
+
ctx,
|
|
82
|
+
x: outlineX,
|
|
83
|
+
y: outlineY,
|
|
84
|
+
width: outlineW,
|
|
85
|
+
height: outlineH,
|
|
86
|
+
borderRadius: adjustedBorderRadius,
|
|
87
|
+
});
|
|
88
|
+
ctx.stroke();
|
|
89
|
+
// Restore original canvas state
|
|
90
|
+
ctx.strokeStyle = originalStrokeStyle;
|
|
91
|
+
ctx.lineWidth = originalLineWidth;
|
|
92
|
+
ctx.setLineDash(originalLineDash);
|
|
93
|
+
};
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// The bitmap created from the SVG height and width might not be what we expect.
|
|
2
|
+
// Adjust the dimensions
|
|
3
|
+
export const fitSvgIntoItsContainer = ({ containerSize, elementSize, }) => {
|
|
4
|
+
// If was already fitting, no need to calculate and lose precision
|
|
5
|
+
if (Math.round(containerSize.width) === Math.round(elementSize.width) &&
|
|
6
|
+
Math.round(containerSize.height) === Math.round(elementSize.height)) {
|
|
7
|
+
return {
|
|
8
|
+
width: containerSize.width,
|
|
9
|
+
height: containerSize.height,
|
|
10
|
+
top: containerSize.top,
|
|
11
|
+
left: containerSize.left,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
if (containerSize.width <= 0 || containerSize.height <= 0) {
|
|
15
|
+
throw new Error(`Container must have positive dimensions, but got ${containerSize.width}x${containerSize.height}`);
|
|
16
|
+
}
|
|
17
|
+
if (elementSize.width <= 0 || elementSize.height <= 0) {
|
|
18
|
+
throw new Error(`Element must have positive dimensions, but got ${elementSize.width}x${elementSize.height}`);
|
|
19
|
+
}
|
|
20
|
+
const heightRatio = containerSize.height / elementSize.height;
|
|
21
|
+
const widthRatio = containerSize.width / elementSize.width;
|
|
22
|
+
const ratio = Math.min(heightRatio, widthRatio);
|
|
23
|
+
const newWidth = elementSize.width * ratio;
|
|
24
|
+
const newHeight = elementSize.height * ratio;
|
|
25
|
+
if (newWidth > containerSize.width + 0.000001 ||
|
|
26
|
+
newHeight > containerSize.height + 0.000001) {
|
|
27
|
+
throw new Error(`Element is too big to fit into the container. Max size: ${containerSize.width}x${containerSize.height}, element size: ${newWidth}x${newHeight}`);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
width: newWidth,
|
|
31
|
+
height: newHeight,
|
|
32
|
+
top: (containerSize.height - newHeight) / 2 + containerSize.top,
|
|
33
|
+
left: (containerSize.width - newWidth) / 2 + containerSize.left,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LogLevel } from 'remotion';
|
|
2
|
+
import type { InternalState } from '../internal-state';
|
|
3
|
+
export declare const getClippedBackground: ({ element, boundingRect, logLevel, internalState, }: {
|
|
4
|
+
element: HTMLElement | SVGElement;
|
|
5
|
+
boundingRect: DOMRect;
|
|
6
|
+
logLevel: LogLevel;
|
|
7
|
+
internalState: InternalState;
|
|
8
|
+
}) => Promise<OffscreenCanvasRenderingContext2D>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { compose } from '../compose';
|
|
2
|
+
export const getClippedBackground = async ({ element, boundingRect, logLevel, internalState, }) => {
|
|
3
|
+
const tempCanvas = new OffscreenCanvas(boundingRect.width, boundingRect.height);
|
|
4
|
+
const tempContext = tempCanvas.getContext('2d');
|
|
5
|
+
await compose({
|
|
6
|
+
element,
|
|
7
|
+
context: tempContext,
|
|
8
|
+
logLevel,
|
|
9
|
+
parentRect: boundingRect,
|
|
10
|
+
internalState,
|
|
11
|
+
onlyBackgroundClip: true,
|
|
12
|
+
});
|
|
13
|
+
return tempContext;
|
|
14
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const getPaddingBox = (rect, computedStyle) => {
|
|
2
|
+
const borderLeft = parseFloat(computedStyle.borderLeftWidth);
|
|
3
|
+
const borderRight = parseFloat(computedStyle.borderRightWidth);
|
|
4
|
+
const borderTop = parseFloat(computedStyle.borderTopWidth);
|
|
5
|
+
const borderBottom = parseFloat(computedStyle.borderBottomWidth);
|
|
6
|
+
return new DOMRect(rect.left + borderLeft, rect.top + borderTop, rect.width - borderLeft - borderRight, rect.height - borderTop - borderBottom);
|
|
7
|
+
};
|
|
8
|
+
const getContentBox = (rect, computedStyle) => {
|
|
9
|
+
const paddingBox = getPaddingBox(rect, computedStyle);
|
|
10
|
+
const paddingLeft = parseFloat(computedStyle.paddingLeft);
|
|
11
|
+
const paddingRight = parseFloat(computedStyle.paddingRight);
|
|
12
|
+
const paddingTop = parseFloat(computedStyle.paddingTop);
|
|
13
|
+
const paddingBottom = parseFloat(computedStyle.paddingBottom);
|
|
14
|
+
return new DOMRect(paddingBox.left + paddingLeft, paddingBox.top + paddingTop, paddingBox.width - paddingLeft - paddingRight, paddingBox.height - paddingTop - paddingBottom);
|
|
15
|
+
};
|
|
16
|
+
export const getBoxBasedOnBackgroundClip = (rect, computedStyle, backgroundClip) => {
|
|
17
|
+
if (!backgroundClip) {
|
|
18
|
+
return rect;
|
|
19
|
+
}
|
|
20
|
+
if (backgroundClip.includes('text')) {
|
|
21
|
+
return rect;
|
|
22
|
+
}
|
|
23
|
+
if (backgroundClip.includes('padding-box')) {
|
|
24
|
+
return getPaddingBox(rect, computedStyle);
|
|
25
|
+
}
|
|
26
|
+
if (backgroundClip.includes('content-box')) {
|
|
27
|
+
return getContentBox(rect, computedStyle);
|
|
28
|
+
}
|
|
29
|
+
return rect;
|
|
30
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// In some cases, we get a matrix that is too compressed:
|
|
2
|
+
// e.g. https://github.com/remotion-dev/remotion/issues/6185
|
|
3
|
+
// > You're rotating around the X-axis by ~89.96°, which means the Y-axis gets compressed to cos(89.96°) ≈ 0.000691 of its original size in the viewport.
|
|
4
|
+
const MAX_SCALE_FACTOR = 100;
|
|
5
|
+
export function getPreTransformRect(targetRect, matrix) {
|
|
6
|
+
// 1. Determine the effective 2D transformation by transforming basis vectors
|
|
7
|
+
const origin = new DOMPoint(0, 0).matrixTransform(matrix);
|
|
8
|
+
const unitX = new DOMPoint(1, 0).matrixTransform(matrix);
|
|
9
|
+
const unitY = new DOMPoint(0, 1).matrixTransform(matrix);
|
|
10
|
+
// 2. Compute the 2D basis vectors after transformation
|
|
11
|
+
const basisX = { x: unitX.x - origin.x, y: unitX.y - origin.y };
|
|
12
|
+
const basisY = { x: unitY.x - origin.x, y: unitY.y - origin.y };
|
|
13
|
+
// Check effective scale in each axis
|
|
14
|
+
const scaleX = Math.hypot(basisX.x, basisX.y);
|
|
15
|
+
const scaleY = Math.hypot(basisY.x, basisY.y);
|
|
16
|
+
// If either axis is too compressed, the inverse will explode
|
|
17
|
+
const minScale = Math.min(scaleX, scaleY);
|
|
18
|
+
if (minScale < 1 / MAX_SCALE_FACTOR) {
|
|
19
|
+
// Content is nearly invisible, e.g. 89.96deg X rotation (edge-on view)
|
|
20
|
+
return new DOMRect(0, 0, 0, 0);
|
|
21
|
+
}
|
|
22
|
+
// 3. Build the effective 2D matrix and invert it
|
|
23
|
+
const effective2D = new DOMMatrix([
|
|
24
|
+
basisX.x,
|
|
25
|
+
basisX.y, // a, b (first column)
|
|
26
|
+
basisY.x,
|
|
27
|
+
basisY.y, // c, d (second column)
|
|
28
|
+
origin.x,
|
|
29
|
+
origin.y, // e, f (translation)
|
|
30
|
+
]);
|
|
31
|
+
const inverse2D = effective2D.inverse();
|
|
32
|
+
const wasNotInvertible = isNaN(inverse2D.m11);
|
|
33
|
+
// For example, a 90 degree rotation, is not being rendered
|
|
34
|
+
if (wasNotInvertible) {
|
|
35
|
+
return new DOMRect(0, 0, 0, 0);
|
|
36
|
+
}
|
|
37
|
+
// 4. Transform target rect corners using the 2D inverse
|
|
38
|
+
const corners = [
|
|
39
|
+
new DOMPoint(targetRect.x, targetRect.y),
|
|
40
|
+
new DOMPoint(targetRect.x + targetRect.width, targetRect.y),
|
|
41
|
+
new DOMPoint(targetRect.x + targetRect.width, targetRect.y + targetRect.height),
|
|
42
|
+
new DOMPoint(targetRect.x, targetRect.y + targetRect.height),
|
|
43
|
+
];
|
|
44
|
+
const transformedCorners = corners.map((c) => c.matrixTransform(inverse2D));
|
|
45
|
+
// 5. Compute bounding box
|
|
46
|
+
const xs = transformedCorners.map((p) => p.x);
|
|
47
|
+
const ys = transformedCorners.map((p) => p.y);
|
|
48
|
+
return new DOMRect(Math.min(...xs), Math.min(...ys), Math.max(...xs) - Math.min(...xs), Math.max(...ys) - Math.min(...ys));
|
|
49
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getBiggestBoundingClientRect } from '../get-biggest-bounding-client-rect';
|
|
2
|
+
import { getNarrowerRect } from './clamp-rect-to-parent-bounds';
|
|
3
|
+
import { getPreTransformRect } from './get-pretransform-rect';
|
|
4
|
+
import { transformIn3d } from './transform-in-3d';
|
|
5
|
+
export const getPrecomposeRectFor3DTransform = ({ element, parentRect, matrix, }) => {
|
|
6
|
+
const unclampedBiggestBoundingClientRect = getBiggestBoundingClientRect(element);
|
|
7
|
+
const biggestPossiblePretransformRect = getPreTransformRect(parentRect, matrix);
|
|
8
|
+
const preTransformRect = getNarrowerRect({
|
|
9
|
+
firstRect: unclampedBiggestBoundingClientRect,
|
|
10
|
+
secondRect: biggestPossiblePretransformRect,
|
|
11
|
+
});
|
|
12
|
+
return preTransformRect;
|
|
13
|
+
};
|
|
14
|
+
export const handle3dTransform = ({ matrix, precomposeRect, tempCanvas, rectAfterTransforms, internalState, }) => {
|
|
15
|
+
const { canvas: transformed, rect: transformedRect } = transformIn3d({
|
|
16
|
+
untransformedRect: precomposeRect,
|
|
17
|
+
matrix,
|
|
18
|
+
sourceCanvas: tempCanvas,
|
|
19
|
+
rectAfterTransforms,
|
|
20
|
+
internalState,
|
|
21
|
+
});
|
|
22
|
+
if (transformedRect.width <= 0 || transformedRect.height <= 0) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return transformed;
|
|
26
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getBiggestBoundingClientRect } from '../get-biggest-bounding-client-rect';
|
|
2
|
+
import { createCanvasGradient } from './parse-linear-gradient';
|
|
3
|
+
export const getPrecomposeRectForMask = (element) => {
|
|
4
|
+
const boundingRect = getBiggestBoundingClientRect(element);
|
|
5
|
+
return boundingRect;
|
|
6
|
+
};
|
|
7
|
+
export const handleMask = ({ gradientInfo, rect, precomposeRect, tempContext, }) => {
|
|
8
|
+
const rectOffsetX = rect.left - precomposeRect.left;
|
|
9
|
+
const rectOffsetY = rect.top - precomposeRect.top;
|
|
10
|
+
const rectToFill = new DOMRect(rectOffsetX, rectOffsetY, rect.width, rect.height);
|
|
11
|
+
const gradient = createCanvasGradient({
|
|
12
|
+
ctx: tempContext,
|
|
13
|
+
rect: rectToFill,
|
|
14
|
+
gradientInfo,
|
|
15
|
+
offsetLeft: 0,
|
|
16
|
+
offsetTop: 0,
|
|
17
|
+
});
|
|
18
|
+
tempContext.globalCompositeOperation = 'destination-in';
|
|
19
|
+
tempContext.fillStyle = gradient;
|
|
20
|
+
tempContext.fillRect(rectToFill.left, rectToFill.top, rectToFill.width, rectToFill.height);
|
|
21
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const hasTransformCssValue = (style) => {
|
|
2
|
+
return style.transform !== 'none' && style.transform !== '';
|
|
3
|
+
};
|
|
4
|
+
export const hasRotateCssValue = (style) => {
|
|
5
|
+
return style.rotate !== 'none' && style.rotate !== '';
|
|
6
|
+
};
|
|
7
|
+
export const hasScaleCssValue = (style) => {
|
|
8
|
+
return style.scale !== 'none' && style.scale !== '';
|
|
9
|
+
};
|
|
10
|
+
export const hasAnyTransformCssValue = (style) => {
|
|
11
|
+
return (hasTransformCssValue(style) ||
|
|
12
|
+
hasRotateCssValue(style) ||
|
|
13
|
+
hasScaleCssValue(style));
|
|
14
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { parseLinearGradient } from './parse-linear-gradient';
|
|
2
|
+
export const getMaskImageValue = (computedStyle) => {
|
|
3
|
+
// Check both standard and webkit-prefixed properties
|
|
4
|
+
const { maskImage, webkitMaskImage } = computedStyle;
|
|
5
|
+
const value = maskImage || webkitMaskImage;
|
|
6
|
+
if (!value || value === 'none') {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
};
|
|
11
|
+
export const parseMaskImage = (maskImageValue) => {
|
|
12
|
+
// Only linear gradients are supported for now
|
|
13
|
+
return parseLinearGradient(maskImageValue);
|
|
14
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { setBorderRadius } from './border-radius';
|
|
2
|
+
export const setOverflowHidden = ({ ctx, rect, borderRadius, overflowHidden, computedStyle, backgroundClip, }) => {
|
|
3
|
+
if (!overflowHidden) {
|
|
4
|
+
return () => { };
|
|
5
|
+
}
|
|
6
|
+
return setBorderRadius({
|
|
7
|
+
ctx,
|
|
8
|
+
rect,
|
|
9
|
+
borderRadius,
|
|
10
|
+
forceClipEvenWhenZero: true,
|
|
11
|
+
computedStyle,
|
|
12
|
+
backgroundClip,
|
|
13
|
+
});
|
|
14
|
+
};
|