@remotion/web-renderer 4.0.398 → 4.0.399
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/calculate-transforms.d.ts +0 -2
- package/dist/calculate-transforms.js +0 -17
- package/dist/composable.d.ts +8 -2
- package/dist/compose-canvas.js +4 -28
- package/dist/compose.d.ts +2 -1
- package/dist/compose.js +34 -35
- package/dist/drawing/border-radius.d.ts +3 -1
- package/dist/drawing/border-radius.js +27 -6
- package/dist/drawing/calculate-object-fit.d.ts +40 -0
- package/dist/drawing/calculate-object-fit.js +208 -0
- package/dist/drawing/draw-background.d.ts +13 -0
- package/dist/drawing/draw-background.js +62 -0
- package/dist/drawing/draw-box-shadow.d.ts +1 -1
- package/dist/drawing/draw-box-shadow.js +1 -1
- package/dist/drawing/draw-dom-element.js +78 -10
- package/dist/drawing/draw-element-to-canvas.d.ts +1 -2
- package/dist/drawing/draw-element-to-canvas.js +36 -8
- package/dist/drawing/draw-element.d.ts +4 -1
- package/dist/drawing/draw-element.js +21 -35
- package/dist/drawing/fit-svg-into-its-dimensions.d.ts +12 -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.d.ts +1 -0
- package/dist/drawing/get-padding-box.js +30 -0
- package/dist/drawing/get-pretransform-rect.js +13 -0
- package/dist/drawing/go-rects-intersect.d.ts +1 -0
- package/dist/drawing/go-rects-intersect.js +6 -0
- package/dist/drawing/handle-3d-transform.d.ts +4 -2
- package/dist/drawing/handle-3d-transform.js +4 -3
- package/dist/drawing/handle-mask.js +2 -0
- package/dist/drawing/overflow.d.ts +3 -1
- package/dist/drawing/overflow.js +3 -1
- package/dist/drawing/parse-linear-gradient.d.ts +3 -1
- package/dist/drawing/parse-linear-gradient.js +3 -3
- package/dist/drawing/precompose.js +1 -0
- package/dist/drawing/process-node.js +5 -6
- package/dist/drawing/text/draw-text.d.ts +2 -1
- package/dist/drawing/text/draw-text.js +9 -3
- package/dist/drawing/text/get-base-height.d.ts +1 -0
- package/dist/drawing/text/get-base-height.js +13 -0
- package/dist/drawing/text/handle-text-node.d.ts +2 -1
- package/dist/drawing/text/handle-text-node.js +2 -2
- package/dist/drawing/transform-in-3d.d.ts +3 -2
- package/dist/drawing/transform-in-3d.js +106 -87
- package/dist/esm/index.mjs +678 -168
- package/dist/find-capturable-elements.d.ts +1 -1
- package/dist/find-capturable-elements.js +22 -20
- package/dist/internal-state.d.ts +19 -0
- package/dist/internal-state.js +9 -0
- package/dist/render-still-on-web.js +1 -0
- package/dist/send-telemetry-event.js +1 -1
- package/dist/take-screenshot.js +1 -0
- package/dist/tree-walker-cleanup-after-children.d.ts +5 -0
- package/dist/tree-walker-cleanup-after-children.js +33 -0
- package/package.json +9 -8
- package/dist/border-radius.d.ts +0 -31
- package/dist/border-radius.js +0 -152
- package/dist/drawing/canvas-offset-from-rect.d.ts +0 -8
- package/dist/drawing/canvas-offset-from-rect.js +0 -12
- package/dist/drawing/compose-canvas.d.ts +0 -1
- package/dist/drawing/compose-canvas.js +0 -36
- package/dist/drawing/compose-svg.d.ts +0 -1
- package/dist/drawing/compose-svg.js +0 -34
- package/dist/drawing/compose.d.ts +0 -5
- package/dist/drawing/compose.js +0 -6
- package/dist/drawing/get-bounding-box-including-shadow.d.ts +0 -1
- package/dist/drawing/get-bounding-box-including-shadow.js +0 -6
- package/dist/drawing/get-computed-style-cache.d.ts +0 -0
- package/dist/drawing/get-computed-style-cache.js +0 -1
- package/dist/find-canvas-elements.d.ts +0 -1
- package/dist/find-canvas-elements.js +0 -13
- package/dist/opacity.d.ts +0 -4
- package/dist/opacity.js +0 -7
- package/dist/transform.d.ts +0 -4
- package/dist/transform.js +0 -6
|
@@ -1,17 +1,85 @@
|
|
|
1
|
+
import { calculateObjectFit, parseObjectFit } from './calculate-object-fit';
|
|
2
|
+
import { fitSvgIntoItsContainer } from './fit-svg-into-its-dimensions';
|
|
1
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
|
+
};
|
|
2
55
|
export const drawDomElement = (node) => {
|
|
3
|
-
const domDrawFn = async ({ dimensions, contextToDraw }) => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
: node instanceof HTMLCanvasElement
|
|
9
|
-
? node
|
|
10
|
-
: null);
|
|
11
|
-
if (!drawable) {
|
|
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 });
|
|
12
61
|
return;
|
|
13
62
|
}
|
|
14
|
-
|
|
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
|
+
}
|
|
15
83
|
};
|
|
16
84
|
return domDrawFn;
|
|
17
85
|
};
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import type { LogLevel } from 'remotion';
|
|
2
2
|
import type { DrawFn } from './drawn-fn';
|
|
3
3
|
export type DrawElementToCanvasReturnValue = 'continue' | 'skip-children';
|
|
4
|
-
export declare const drawElementToCanvas: ({ element, context, draw, offsetLeft, offsetTop, logLevel,
|
|
4
|
+
export declare const drawElementToCanvas: ({ element, context, draw, offsetLeft, offsetTop, logLevel, }: {
|
|
5
5
|
element: HTMLElement | SVGElement;
|
|
6
6
|
context: OffscreenCanvasRenderingContext2D;
|
|
7
7
|
draw: DrawFn;
|
|
8
8
|
offsetLeft: number;
|
|
9
9
|
offsetTop: number;
|
|
10
10
|
logLevel: LogLevel;
|
|
11
|
-
parentRect: DOMRect;
|
|
12
11
|
}) => Promise<DrawElementToCanvasReturnValue>;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { Internals } from 'remotion';
|
|
2
|
+
import { compose } from '../compose';
|
|
3
|
+
import { getBiggestBoundingClientRect } from '../get-biggest-bounding-client-rect';
|
|
1
4
|
import { calculateTransforms } from './calculate-transforms';
|
|
2
5
|
import { drawElement } from './draw-element';
|
|
3
|
-
import {
|
|
4
|
-
export const drawElementToCanvas = async ({ element, context, draw, offsetLeft, offsetTop, logLevel,
|
|
5
|
-
const
|
|
6
|
-
const { totalMatrix, reset, dimensions, opacity, computedStyle } = transforms;
|
|
6
|
+
import { transformIn3d } from './transform-in-3d';
|
|
7
|
+
export const drawElementToCanvas = async ({ element, context, draw, offsetLeft, offsetTop, logLevel, }) => {
|
|
8
|
+
const { totalMatrix, reset, dimensions, opacity, computedStyle } = calculateTransforms(element);
|
|
7
9
|
if (opacity === 0) {
|
|
8
10
|
reset();
|
|
9
11
|
return 'continue';
|
|
@@ -13,14 +15,40 @@ export const drawElementToCanvas = async ({ element, context, draw, offsetLeft,
|
|
|
13
15
|
return 'continue';
|
|
14
16
|
}
|
|
15
17
|
if (!totalMatrix.is2D) {
|
|
16
|
-
|
|
18
|
+
const biggestBoundingClientRect = getBiggestBoundingClientRect(element);
|
|
19
|
+
const canvasOffsetLeft = Math.min(biggestBoundingClientRect.left, 0);
|
|
20
|
+
const canvasOffsetTop = Math.min(biggestBoundingClientRect.top, 0);
|
|
21
|
+
const tempCanvasWidth = Math.max(biggestBoundingClientRect.width, biggestBoundingClientRect.right);
|
|
22
|
+
const tempCanvasHeight = Math.max(biggestBoundingClientRect.height, biggestBoundingClientRect.bottom);
|
|
23
|
+
const start = Date.now();
|
|
24
|
+
const tempCanvas = new OffscreenCanvas(tempCanvasWidth, tempCanvasHeight);
|
|
25
|
+
const context2 = tempCanvas.getContext('2d');
|
|
26
|
+
if (!context2) {
|
|
27
|
+
throw new Error('Could not get context');
|
|
28
|
+
}
|
|
29
|
+
await compose({
|
|
17
30
|
element,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
31
|
+
context: context2,
|
|
32
|
+
offsetLeft: canvasOffsetLeft,
|
|
33
|
+
offsetTop: canvasOffsetTop,
|
|
21
34
|
logLevel,
|
|
22
35
|
});
|
|
36
|
+
const afterCompose = Date.now();
|
|
37
|
+
const transformed = transformIn3d({
|
|
38
|
+
canvasWidth: tempCanvasWidth,
|
|
39
|
+
canvasHeight: tempCanvasHeight,
|
|
40
|
+
matrix: totalMatrix,
|
|
41
|
+
sourceCanvas: tempCanvas,
|
|
42
|
+
offsetLeft: canvasOffsetLeft,
|
|
43
|
+
offsetTop: canvasOffsetTop,
|
|
44
|
+
});
|
|
45
|
+
context.drawImage(transformed, 0, 0);
|
|
46
|
+
const afterDraw = Date.now();
|
|
23
47
|
reset();
|
|
48
|
+
Internals.Log.trace({
|
|
49
|
+
logLevel,
|
|
50
|
+
tag: '@remotion/web-renderer',
|
|
51
|
+
}, `Transforming element in 3D - canvas size: ${tempCanvasWidth}x${tempCanvasHeight} - compose: ${afterCompose - start}ms - draw: ${afterDraw - afterCompose}ms`);
|
|
24
52
|
return 'skip-children';
|
|
25
53
|
}
|
|
26
54
|
await drawElement({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { LogLevel } from 'remotion';
|
|
2
|
+
import type { InternalState } from '../internal-state';
|
|
2
3
|
import type { DrawFn } from './drawn-fn';
|
|
3
|
-
export declare const drawElement: ({ rect, computedStyle, context, draw, opacity, totalMatrix, parentRect, logLevel, }: {
|
|
4
|
+
export declare const drawElement: ({ rect, computedStyle, context, draw, opacity, totalMatrix, parentRect, logLevel, element, internalState, }: {
|
|
4
5
|
rect: DOMRect;
|
|
5
6
|
computedStyle: CSSStyleDeclaration;
|
|
6
7
|
context: OffscreenCanvasRenderingContext2D;
|
|
@@ -9,6 +10,8 @@ export declare const drawElement: ({ rect, computedStyle, context, draw, opacity
|
|
|
9
10
|
draw: DrawFn;
|
|
10
11
|
parentRect: DOMRect;
|
|
11
12
|
logLevel: LogLevel;
|
|
13
|
+
element: HTMLElement | SVGElement;
|
|
14
|
+
internalState: InternalState;
|
|
12
15
|
}) => Promise<{
|
|
13
16
|
cleanupAfterChildren: () => void;
|
|
14
17
|
}>;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { parseBorderRadius, setBorderRadius } from './border-radius';
|
|
2
|
+
import { drawBackground } from './draw-background';
|
|
2
3
|
import { drawBorder } from './draw-border';
|
|
3
|
-
import {
|
|
4
|
+
import { drawBorderRadius } from './draw-box-shadow';
|
|
4
5
|
import { drawOutline } from './draw-outline';
|
|
5
6
|
import { setOpacity } from './opacity';
|
|
6
7
|
import { setOverflowHidden } from './overflow';
|
|
7
|
-
import { createCanvasGradient, parseLinearGradient, } from './parse-linear-gradient';
|
|
8
8
|
import { setTransform } from './transform';
|
|
9
|
-
export const drawElement = async ({ rect, computedStyle, context, draw, opacity, totalMatrix, parentRect, logLevel, }) => {
|
|
10
|
-
const
|
|
11
|
-
const { backgroundImage } = computedStyle;
|
|
9
|
+
export const drawElement = async ({ rect, computedStyle, context, draw, opacity, totalMatrix, parentRect, logLevel, element, internalState, }) => {
|
|
10
|
+
const { backgroundImage, backgroundColor, backgroundClip } = computedStyle;
|
|
12
11
|
const borderRadius = parseBorderRadius({
|
|
13
12
|
borderRadius: computedStyle.borderRadius,
|
|
14
13
|
width: rect.width,
|
|
@@ -24,7 +23,7 @@ export const drawElement = async ({ rect, computedStyle, context, draw, opacity,
|
|
|
24
23
|
opacity,
|
|
25
24
|
});
|
|
26
25
|
// Draw box shadow before border radius clip and background
|
|
27
|
-
|
|
26
|
+
drawBorderRadius({
|
|
28
27
|
ctx: context,
|
|
29
28
|
computedStyle,
|
|
30
29
|
rect,
|
|
@@ -36,43 +35,28 @@ export const drawElement = async ({ rect, computedStyle, context, draw, opacity,
|
|
|
36
35
|
rect,
|
|
37
36
|
borderRadius,
|
|
38
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,
|
|
39
51
|
});
|
|
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 &&
|
|
60
|
-
background !== 'transparent' &&
|
|
61
|
-
!(background.startsWith('rgba') &&
|
|
62
|
-
(background.endsWith(', 0)') || background.endsWith(',0')))) {
|
|
63
|
-
const originalFillStyle = context.fillStyle;
|
|
64
|
-
context.fillStyle = background;
|
|
65
|
-
context.fillRect(rect.left, rect.top, rect.width, rect.height);
|
|
66
|
-
context.fillStyle = originalFillStyle;
|
|
67
|
-
}
|
|
68
52
|
await draw({ dimensions: rect, computedStyle, contextToDraw: context });
|
|
53
|
+
finishBorderRadius();
|
|
69
54
|
drawBorder({
|
|
70
55
|
ctx: context,
|
|
71
56
|
rect,
|
|
72
57
|
borderRadius,
|
|
73
58
|
computedStyle,
|
|
74
59
|
});
|
|
75
|
-
finishBorderRadius();
|
|
76
60
|
// Drawing outline ignores overflow: hidden, finishing it and starting a new one for the outline
|
|
77
61
|
drawOutline({
|
|
78
62
|
ctx: context,
|
|
@@ -85,6 +69,8 @@ export const drawElement = async ({ rect, computedStyle, context, draw, opacity,
|
|
|
85
69
|
rect,
|
|
86
70
|
borderRadius,
|
|
87
71
|
overflowHidden: computedStyle.overflow === 'hidden',
|
|
72
|
+
computedStyle,
|
|
73
|
+
backgroundClip,
|
|
88
74
|
});
|
|
89
75
|
finishTransform();
|
|
90
76
|
return {
|
|
@@ -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 @@
|
|
|
1
|
+
export declare const getBoxBasedOnBackgroundClip: (rect: DOMRect, computedStyle: CSSStyleDeclaration, backgroundClip: string | undefined) => DOMRect;
|
|
@@ -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
|
+
};
|
|
@@ -1,3 +1,7 @@
|
|
|
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;
|
|
1
5
|
export function getPreTransformRect(targetRect, matrix) {
|
|
2
6
|
// 1. Determine the effective 2D transformation by transforming basis vectors
|
|
3
7
|
const origin = new DOMPoint(0, 0).matrixTransform(matrix);
|
|
@@ -6,6 +10,15 @@ export function getPreTransformRect(targetRect, matrix) {
|
|
|
6
10
|
// 2. Compute the 2D basis vectors after transformation
|
|
7
11
|
const basisX = { x: unitX.x - origin.x, y: unitX.y - origin.y };
|
|
8
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
|
+
}
|
|
9
22
|
// 3. Build the effective 2D matrix and invert it
|
|
10
23
|
const effective2D = new DOMMatrix([
|
|
11
24
|
basisX.x,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function doRectsIntersect(rect1: DOMRect, rect2: DOMRect): boolean;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import type { InternalState } from '../internal-state';
|
|
1
2
|
export declare const getPrecomposeRectFor3DTransform: ({ element, parentRect, matrix, }: {
|
|
2
3
|
element: HTMLElement | SVGElement;
|
|
3
4
|
parentRect: DOMRect;
|
|
4
5
|
matrix: DOMMatrix;
|
|
5
6
|
}) => DOMRect;
|
|
6
|
-
export declare const handle3dTransform: ({ matrix, precomposeRect, tempCanvas, rectAfterTransforms, }: {
|
|
7
|
+
export declare const handle3dTransform: ({ matrix, precomposeRect, tempCanvas, rectAfterTransforms, internalState, }: {
|
|
7
8
|
matrix: DOMMatrix;
|
|
8
9
|
precomposeRect: DOMRect;
|
|
9
10
|
tempCanvas: OffscreenCanvas;
|
|
10
11
|
rectAfterTransforms: DOMRect;
|
|
11
|
-
|
|
12
|
+
internalState: InternalState;
|
|
13
|
+
}) => OffscreenCanvas | null;
|
|
@@ -11,15 +11,16 @@ export const getPrecomposeRectFor3DTransform = ({ element, parentRect, matrix, }
|
|
|
11
11
|
});
|
|
12
12
|
return preTransformRect;
|
|
13
13
|
};
|
|
14
|
-
export const handle3dTransform = ({ matrix, precomposeRect, tempCanvas, rectAfterTransforms, }) => {
|
|
15
|
-
const { canvas: transformed, rect: transformedRect
|
|
14
|
+
export const handle3dTransform = ({ matrix, precomposeRect, tempCanvas, rectAfterTransforms, internalState, }) => {
|
|
15
|
+
const { canvas: transformed, rect: transformedRect } = transformIn3d({
|
|
16
16
|
untransformedRect: precomposeRect,
|
|
17
17
|
matrix,
|
|
18
18
|
sourceCanvas: tempCanvas,
|
|
19
19
|
rectAfterTransforms,
|
|
20
|
+
internalState,
|
|
20
21
|
});
|
|
21
22
|
if (transformedRect.width <= 0 || transformedRect.height <= 0) {
|
|
22
23
|
return null;
|
|
23
24
|
}
|
|
24
|
-
return
|
|
25
|
+
return transformed;
|
|
25
26
|
};
|
|
@@ -12,6 +12,8 @@ export const handleMask = ({ gradientInfo, rect, precomposeRect, tempContext, })
|
|
|
12
12
|
ctx: tempContext,
|
|
13
13
|
rect: rectToFill,
|
|
14
14
|
gradientInfo,
|
|
15
|
+
offsetLeft: 0,
|
|
16
|
+
offsetTop: 0,
|
|
15
17
|
});
|
|
16
18
|
tempContext.globalCompositeOperation = 'destination-in';
|
|
17
19
|
tempContext.fillStyle = gradient;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { BorderRadiusCorners } from './border-radius';
|
|
2
|
-
export declare const setOverflowHidden: ({ ctx, rect, borderRadius, overflowHidden, }: {
|
|
2
|
+
export declare const setOverflowHidden: ({ ctx, rect, borderRadius, overflowHidden, computedStyle, backgroundClip, }: {
|
|
3
3
|
ctx: OffscreenCanvasRenderingContext2D;
|
|
4
4
|
rect: DOMRect;
|
|
5
5
|
borderRadius: BorderRadiusCorners;
|
|
6
6
|
overflowHidden: boolean;
|
|
7
|
+
computedStyle: CSSStyleDeclaration;
|
|
8
|
+
backgroundClip: string;
|
|
7
9
|
}) => () => void;
|
package/dist/drawing/overflow.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { setBorderRadius } from './border-radius';
|
|
2
|
-
export const setOverflowHidden = ({ ctx, rect, borderRadius, overflowHidden, }) => {
|
|
2
|
+
export const setOverflowHidden = ({ ctx, rect, borderRadius, overflowHidden, computedStyle, backgroundClip, }) => {
|
|
3
3
|
if (!overflowHidden) {
|
|
4
4
|
return () => { };
|
|
5
5
|
}
|
|
@@ -8,5 +8,7 @@ export const setOverflowHidden = ({ ctx, rect, borderRadius, overflowHidden, })
|
|
|
8
8
|
rect,
|
|
9
9
|
borderRadius,
|
|
10
10
|
forceClipEvenWhenZero: true,
|
|
11
|
+
computedStyle,
|
|
12
|
+
backgroundClip,
|
|
11
13
|
});
|
|
12
14
|
};
|
|
@@ -7,8 +7,10 @@ export interface LinearGradientInfo {
|
|
|
7
7
|
colorStops: ColorStop[];
|
|
8
8
|
}
|
|
9
9
|
export declare const parseLinearGradient: (backgroundImage: string) => LinearGradientInfo | null;
|
|
10
|
-
export declare const createCanvasGradient: ({ ctx, rect, gradientInfo, }: {
|
|
10
|
+
export declare const createCanvasGradient: ({ ctx, rect, gradientInfo, offsetLeft, offsetTop, }: {
|
|
11
11
|
ctx: OffscreenCanvasRenderingContext2D;
|
|
12
12
|
rect: DOMRect;
|
|
13
13
|
gradientInfo: LinearGradientInfo;
|
|
14
|
+
offsetLeft: number;
|
|
15
|
+
offsetTop: number;
|
|
14
16
|
}) => CanvasGradient;
|
|
@@ -226,13 +226,13 @@ export const parseLinearGradient = (backgroundImage) => {
|
|
|
226
226
|
colorStops,
|
|
227
227
|
};
|
|
228
228
|
};
|
|
229
|
-
export const createCanvasGradient = ({ ctx, rect, gradientInfo, }) => {
|
|
229
|
+
export const createCanvasGradient = ({ ctx, rect, gradientInfo, offsetLeft, offsetTop, }) => {
|
|
230
230
|
// Convert angle to radians
|
|
231
231
|
// CSS angles: 0deg = to top, 90deg = to right, 180deg = to bottom, 270deg = to left
|
|
232
232
|
// We need to calculate the gradient line that spans the rectangle at the given angle
|
|
233
233
|
const angleRad = ((gradientInfo.angle - 90) * Math.PI) / 180;
|
|
234
|
-
const centerX = rect.left + rect.width / 2;
|
|
235
|
-
const centerY = rect.top + rect.height / 2;
|
|
234
|
+
const centerX = rect.left - offsetLeft + rect.width / 2;
|
|
235
|
+
const centerY = rect.top - offsetTop + rect.height / 2;
|
|
236
236
|
// Calculate gradient line endpoints
|
|
237
237
|
// The gradient line passes through the center and has the specified angle
|
|
238
238
|
const cos = Math.cos(angleRad);
|
|
@@ -57,7 +57,6 @@ export const processNode = async ({ element, context, draw, logLevel, parentRect
|
|
|
57
57
|
internalState,
|
|
58
58
|
});
|
|
59
59
|
let drawable = tempCanvas;
|
|
60
|
-
let cleanupWebGL = () => { };
|
|
61
60
|
const rectAfterTransforms = roundToExpandRect(transformDOMRect({
|
|
62
61
|
rect: precomposeRect,
|
|
63
62
|
matrix: totalMatrix,
|
|
@@ -76,17 +75,16 @@ export const processNode = async ({ element, context, draw, logLevel, parentRect
|
|
|
76
75
|
precomposeRect,
|
|
77
76
|
tempCanvas: drawable,
|
|
78
77
|
rectAfterTransforms,
|
|
78
|
+
internalState,
|
|
79
79
|
});
|
|
80
80
|
if (t) {
|
|
81
|
-
|
|
82
|
-
drawable = transformed;
|
|
83
|
-
cleanupWebGL = cleanup;
|
|
81
|
+
drawable = t;
|
|
84
82
|
}
|
|
85
83
|
}
|
|
86
84
|
const previousTransform = context.getTransform();
|
|
87
85
|
if (drawable) {
|
|
88
86
|
context.setTransform(new DOMMatrix());
|
|
89
|
-
context.drawImage(drawable, rectAfterTransforms.left - parentRect.x, rectAfterTransforms.top - parentRect.y, rectAfterTransforms.width, rectAfterTransforms.height);
|
|
87
|
+
context.drawImage(drawable, 0, drawable.height - rectAfterTransforms.height, rectAfterTransforms.width, rectAfterTransforms.height, rectAfterTransforms.left - parentRect.x, rectAfterTransforms.top - parentRect.y, rectAfterTransforms.width, rectAfterTransforms.height);
|
|
90
88
|
context.setTransform(previousTransform);
|
|
91
89
|
Internals.Log.trace({
|
|
92
90
|
logLevel,
|
|
@@ -98,7 +96,6 @@ export const processNode = async ({ element, context, draw, logLevel, parentRect
|
|
|
98
96
|
});
|
|
99
97
|
}
|
|
100
98
|
reset();
|
|
101
|
-
cleanupWebGL();
|
|
102
99
|
return { type: 'skip-children' };
|
|
103
100
|
}
|
|
104
101
|
const { cleanupAfterChildren } = await drawElement({
|
|
@@ -110,6 +107,8 @@ export const processNode = async ({ element, context, draw, logLevel, parentRect
|
|
|
110
107
|
totalMatrix,
|
|
111
108
|
parentRect,
|
|
112
109
|
logLevel,
|
|
110
|
+
element,
|
|
111
|
+
internalState,
|
|
113
112
|
});
|
|
114
113
|
reset();
|
|
115
114
|
return { type: 'continue', cleanupAfterChildren };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { LogLevel } from 'remotion';
|
|
2
2
|
import type { DrawFn } from '../drawn-fn';
|
|
3
|
-
export declare const drawText: ({ span, logLevel, }: {
|
|
3
|
+
export declare const drawText: ({ span, logLevel, onlyBackgroundClip, }: {
|
|
4
4
|
span: HTMLSpanElement;
|
|
5
5
|
logLevel: LogLevel;
|
|
6
|
+
onlyBackgroundClip: boolean;
|
|
6
7
|
}) => DrawFn;
|
|
@@ -2,9 +2,9 @@ import { Internals } from 'remotion';
|
|
|
2
2
|
import { applyTextTransform } from './apply-text-transform';
|
|
3
3
|
import { findLineBreaks } from './find-line-breaks.text';
|
|
4
4
|
import { getCollapsedText } from './get-collapsed-text';
|
|
5
|
-
export const drawText = ({ span, logLevel, }) => {
|
|
5
|
+
export const drawText = ({ span, logLevel, onlyBackgroundClip, }) => {
|
|
6
6
|
const drawFn = ({ dimensions: rect, computedStyle, contextToDraw }) => {
|
|
7
|
-
const { fontFamily, fontSize, fontWeight,
|
|
7
|
+
const { fontFamily, fontSize, fontWeight, direction, writingMode, letterSpacing, textTransform, webkitTextFillColor, } = computedStyle;
|
|
8
8
|
const isVertical = writingMode !== 'horizontal-tb';
|
|
9
9
|
if (isVertical) {
|
|
10
10
|
// TODO: Only warn once per render.
|
|
@@ -17,7 +17,13 @@ export const drawText = ({ span, logLevel, }) => {
|
|
|
17
17
|
contextToDraw.save();
|
|
18
18
|
const fontSizePx = parseFloat(fontSize);
|
|
19
19
|
contextToDraw.font = `${fontWeight} ${fontSizePx}px ${fontFamily}`;
|
|
20
|
-
contextToDraw.fillStyle =
|
|
20
|
+
contextToDraw.fillStyle =
|
|
21
|
+
// If text is being applied with backgroundClipText, we need to use a solid color otherwise it won't get
|
|
22
|
+
// applied in canvas
|
|
23
|
+
onlyBackgroundClip
|
|
24
|
+
? 'black'
|
|
25
|
+
: // -webkit-text-fill-color overrides color, and defaults to the value of `color`
|
|
26
|
+
webkitTextFillColor;
|
|
21
27
|
contextToDraw.letterSpacing = letterSpacing;
|
|
22
28
|
const isRTL = direction === 'rtl';
|
|
23
29
|
contextToDraw.textAlign = isRTL ? 'right' : 'left';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getOpticalHeight: (span: HTMLSpanElement) => number;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// A text with e.g. font-size: 12px does not always get rendered with 12px
|
|
2
|
+
// With "Arial", it gets rendered with 13.5px height, with SF Pro, it gets
|
|
3
|
+
// rendered with 14px height. I therefore conclude there is no logic
|
|
4
|
+
// and we need to measure it. I rendered the string "1", "a" and "qË" and
|
|
5
|
+
// they all have the same height, so it doesn't matter if certain characters
|
|
6
|
+
// go low or high.
|
|
7
|
+
export const getOpticalHeight = (span) => {
|
|
8
|
+
const originalText = span.textContent;
|
|
9
|
+
span.textContent = '1';
|
|
10
|
+
const { height } = span.getBoundingClientRect();
|
|
11
|
+
span.textContent = originalText;
|
|
12
|
+
return height;
|
|
13
|
+
};
|