@remotion/web-renderer 4.0.395 → 4.0.397

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/border-radius.d.ts +31 -0
  2. package/dist/border-radius.js +152 -0
  3. package/dist/calculate-transforms.d.ts +2 -0
  4. package/dist/calculate-transforms.js +17 -0
  5. package/dist/composable.d.ts +2 -8
  6. package/dist/compose-canvas.js +28 -4
  7. package/dist/compose.d.ts +4 -3
  8. package/dist/compose.js +45 -12
  9. package/dist/create-scaffold.js +10 -3
  10. package/dist/drawing/border-radius.d.ts +3 -5
  11. package/dist/drawing/border-radius.js +12 -34
  12. package/dist/drawing/calculate-transforms.d.ts +12 -2
  13. package/dist/drawing/calculate-transforms.js +38 -19
  14. package/dist/drawing/canvas-offset-from-rect.d.ts +8 -0
  15. package/dist/drawing/canvas-offset-from-rect.js +12 -0
  16. package/dist/drawing/clamp-rect-to-parent-bounds.d.ts +8 -0
  17. package/dist/drawing/clamp-rect-to-parent-bounds.js +18 -0
  18. package/dist/drawing/compose-canvas.d.ts +1 -0
  19. package/dist/drawing/compose-canvas.js +36 -0
  20. package/dist/drawing/compose-svg.d.ts +1 -0
  21. package/dist/drawing/compose-svg.js +34 -0
  22. package/dist/drawing/compose.d.ts +5 -0
  23. package/dist/drawing/compose.js +6 -0
  24. package/dist/drawing/do-rects-intersect.d.ts +1 -0
  25. package/dist/drawing/do-rects-intersect.js +6 -0
  26. package/dist/drawing/draw-border.d.ts +2 -5
  27. package/dist/drawing/draw-border.js +37 -37
  28. package/dist/drawing/draw-box-shadow.d.ts +18 -0
  29. package/dist/drawing/draw-box-shadow.js +103 -0
  30. package/dist/drawing/draw-element-to-canvas.d.ts +2 -1
  31. package/dist/drawing/draw-element-to-canvas.js +8 -36
  32. package/dist/drawing/draw-element.d.ts +8 -3
  33. package/dist/drawing/draw-element.js +64 -18
  34. package/dist/drawing/draw-outline.d.ts +9 -0
  35. package/dist/drawing/draw-outline.js +93 -0
  36. package/dist/drawing/draw-rounded.d.ts +9 -0
  37. package/dist/drawing/draw-rounded.js +34 -0
  38. package/dist/drawing/get-bounding-box-including-shadow.d.ts +1 -0
  39. package/dist/drawing/get-bounding-box-including-shadow.js +6 -0
  40. package/dist/drawing/get-computed-style-cache.d.ts +0 -0
  41. package/dist/drawing/get-computed-style-cache.js +1 -0
  42. package/dist/drawing/get-pretransform-rect.d.ts +1 -0
  43. package/dist/drawing/get-pretransform-rect.js +36 -0
  44. package/dist/drawing/handle-3d-transform.d.ts +11 -0
  45. package/dist/drawing/handle-3d-transform.js +25 -0
  46. package/dist/drawing/handle-mask.d.ts +8 -0
  47. package/dist/drawing/handle-mask.js +19 -0
  48. package/dist/drawing/has-transform.d.ts +4 -0
  49. package/dist/drawing/has-transform.js +14 -0
  50. package/dist/drawing/mask-image.d.ts +3 -0
  51. package/dist/drawing/mask-image.js +14 -0
  52. package/dist/drawing/overflow.d.ts +7 -0
  53. package/dist/drawing/overflow.js +12 -0
  54. package/dist/drawing/parse-linear-gradient.d.ts +14 -0
  55. package/dist/drawing/parse-linear-gradient.js +260 -0
  56. package/dist/drawing/precompose.d.ts +11 -0
  57. package/dist/drawing/precompose.js +13 -0
  58. package/dist/drawing/process-node.d.ts +18 -0
  59. package/dist/drawing/process-node.js +116 -0
  60. package/dist/drawing/round-to-expand-rect.d.ts +1 -0
  61. package/dist/drawing/round-to-expand-rect.js +7 -0
  62. package/dist/drawing/text/draw-text.d.ts +5 -1
  63. package/dist/drawing/text/draw-text.js +10 -5
  64. package/dist/drawing/text/find-line-breaks.text.d.ts +1 -1
  65. package/dist/drawing/text/find-line-breaks.text.js +2 -2
  66. package/dist/drawing/text/handle-text-node.d.ts +7 -5
  67. package/dist/drawing/text/handle-text-node.js +7 -6
  68. package/dist/drawing/transform-in-3d.d.ts +9 -7
  69. package/dist/drawing/transform-in-3d.js +41 -31
  70. package/dist/drawing/transform-rect-with-matrix.d.ts +4 -0
  71. package/dist/drawing/transform-rect-with-matrix.js +19 -0
  72. package/dist/drawing/transform.d.ts +2 -1
  73. package/dist/drawing/transform.js +6 -2
  74. package/dist/esm/index.mjs +1138 -278
  75. package/dist/find-canvas-elements.d.ts +1 -0
  76. package/dist/find-canvas-elements.js +13 -0
  77. package/dist/find-capturable-elements.d.ts +1 -1
  78. package/dist/find-capturable-elements.js +20 -22
  79. package/dist/get-biggest-bounding-client-rect.js +29 -4
  80. package/dist/internal-state.d.ts +9 -0
  81. package/dist/internal-state.js +12 -0
  82. package/dist/opacity.d.ts +4 -0
  83. package/dist/opacity.js +7 -0
  84. package/dist/render-media-on-web.d.ts +3 -0
  85. package/dist/render-media-on-web.js +27 -14
  86. package/dist/render-still-on-web.d.ts +5 -1
  87. package/dist/render-still-on-web.js +4 -1
  88. package/dist/take-screenshot.d.ts +5 -2
  89. package/dist/take-screenshot.js +16 -4
  90. package/dist/transform.d.ts +4 -0
  91. package/dist/transform.js +6 -0
  92. package/package.json +6 -6
  93. package/dist/drawing/text/get-base-height.d.ts +0 -1
  94. package/dist/drawing/text/get-base-height.js +0 -13
@@ -0,0 +1,12 @@
1
+ export const canvasOffsetFromRect = ({ rect }) => {
2
+ const canvasOffsetLeft = Math.min(rect.left, 0);
3
+ const canvasOffsetTop = Math.min(rect.top, 0);
4
+ const canvasWidth = Math.max(rect.width, rect.right);
5
+ const canvasHeight = Math.max(rect.height, rect.bottom);
6
+ return {
7
+ offsetLeft: canvasOffsetLeft,
8
+ offsetTop: canvasOffsetTop,
9
+ canvasWidth,
10
+ canvasHeight,
11
+ };
12
+ };
@@ -0,0 +1,8 @@
1
+ export declare const getNarrowerRect: ({ firstRect, secondRect, }: {
2
+ firstRect: DOMRect;
3
+ secondRect: DOMRect;
4
+ }) => DOMRect;
5
+ export declare const getWiderRectAndExpand: ({ firstRect, secondRect, }: {
6
+ firstRect: DOMRect | null;
7
+ secondRect: DOMRect;
8
+ }) => DOMRect;
@@ -0,0 +1,18 @@
1
+ import { roundToExpandRect } from './round-to-expand-rect';
2
+ export const getNarrowerRect = ({ firstRect, secondRect, }) => {
3
+ const left = Math.max(firstRect.left, secondRect.left);
4
+ const top = Math.max(firstRect.top, secondRect.top);
5
+ const bottom = Math.min(firstRect.bottom, secondRect.bottom);
6
+ const right = Math.min(firstRect.right, secondRect.right);
7
+ return new DOMRect(left, top, right - left, bottom - top);
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 const drawElementToCanvas: (canvas: HTMLCanvasElement | HTMLImageElement | SVGSVGElement, context: OffscreenCanvasRenderingContext2D) => Promise<void>;
@@ -0,0 +1,36 @@
1
+ import { setBorderRadius } from './border-radius';
2
+ import { calculateTransforms } from './calculate-transforms';
3
+ import { turnSvgIntoDrawable } from './compose-svg';
4
+ import { setOpacity } from './opacity';
5
+ import { setTransform } from './transform';
6
+ export const drawElementToCanvas = async (canvas, context) => {
7
+ const { totalMatrix, reset, dimensions, borderRadius, opacity } = calculateTransforms(canvas);
8
+ if (opacity === 0) {
9
+ reset();
10
+ return;
11
+ }
12
+ const drawable = canvas instanceof SVGSVGElement
13
+ ? await turnSvgIntoDrawable(canvas)
14
+ : canvas;
15
+ const finishTransform = setTransform({
16
+ ctx: context,
17
+ transform: totalMatrix,
18
+ });
19
+ const finishBorderRadius = setBorderRadius({
20
+ ctx: context,
21
+ x: dimensions.left,
22
+ y: dimensions.top,
23
+ width: dimensions.width,
24
+ height: dimensions.height,
25
+ borderRadius,
26
+ });
27
+ const finishOpacity = setOpacity({
28
+ ctx: context,
29
+ opacity,
30
+ });
31
+ context.drawImage(drawable, dimensions.left, dimensions.top, dimensions.width, dimensions.height);
32
+ finishOpacity();
33
+ finishBorderRadius();
34
+ finishTransform();
35
+ reset();
36
+ };
@@ -0,0 +1 @@
1
+ export declare const turnSvgIntoDrawable: (svg: SVGSVGElement) => Promise<HTMLImageElement>;
@@ -0,0 +1,34 @@
1
+ export const turnSvgIntoDrawable = (svg) => {
2
+ const originalTransform = svg.style.transform;
3
+ const originalTransformOrigin = svg.style.transformOrigin;
4
+ const originalMarginLeft = svg.style.marginLeft;
5
+ const originalMarginRight = svg.style.marginRight;
6
+ const originalMarginTop = svg.style.marginTop;
7
+ const originalMarginBottom = svg.style.marginBottom;
8
+ svg.style.transform = 'none';
9
+ svg.style.transformOrigin = '';
10
+ // Margins were already included in the positioning calculation,
11
+ // so we need to remove them to avoid double counting.
12
+ svg.style.marginLeft = '0';
13
+ svg.style.marginRight = '0';
14
+ svg.style.marginTop = '0';
15
+ svg.style.marginBottom = '0';
16
+ const svgData = new XMLSerializer().serializeToString(svg);
17
+ svg.style.marginLeft = originalMarginLeft;
18
+ svg.style.marginRight = originalMarginRight;
19
+ svg.style.marginTop = originalMarginTop;
20
+ svg.style.marginBottom = originalMarginBottom;
21
+ svg.style.transform = originalTransform;
22
+ svg.style.transformOrigin = originalTransformOrigin;
23
+ return new Promise((resolve, reject) => {
24
+ const image = new Image();
25
+ const url = `data:image/svg+xml;base64,${btoa(svgData)}`;
26
+ image.onload = function () {
27
+ resolve(image);
28
+ };
29
+ image.onerror = () => {
30
+ reject(new Error('Failed to convert SVG to image'));
31
+ };
32
+ image.src = url;
33
+ });
34
+ };
@@ -0,0 +1,5 @@
1
+ import type { Composable } from '../composable';
2
+ export declare const compose: ({ composables, context, }: {
3
+ composables: Composable[];
4
+ context: OffscreenCanvasRenderingContext2D;
5
+ }) => Promise<void>;
@@ -0,0 +1,6 @@
1
+ import { drawElementToCanvas } from './draw-element-to-canvas';
2
+ export const compose = async ({ composables, context, }) => {
3
+ for (const composable of composables) {
4
+ await drawElementToCanvas(composable.element, context);
5
+ }
6
+ };
@@ -0,0 +1 @@
1
+ export declare function doRectsIntersect(rect1: DOMRect, rect2: DOMRect): boolean;
@@ -0,0 +1,6 @@
1
+ export function doRectsIntersect(rect1, rect2) {
2
+ return !(rect1.right <= rect2.left ||
3
+ rect1.left >= rect2.right ||
4
+ rect1.bottom <= rect2.top ||
5
+ rect1.top >= rect2.bottom);
6
+ }
@@ -1,10 +1,7 @@
1
1
  import type { BorderRadiusCorners } from './border-radius';
2
- export declare const drawBorder: ({ ctx, x, y, width, height, borderRadius, computedStyle, }: {
2
+ export declare const drawBorder: ({ ctx, rect, borderRadius, computedStyle, }: {
3
3
  ctx: OffscreenCanvasRenderingContext2D;
4
- x: number;
5
- y: number;
6
- width: number;
7
- height: number;
4
+ rect: DOMRect;
8
5
  borderRadius: BorderRadiusCorners;
9
6
  computedStyle: CSSStyleDeclaration;
10
7
  }) => void;
@@ -211,7 +211,7 @@ const drawUniformBorder = ({ ctx, x, y, width, height, borderRadius, borderWidth
211
211
  ctx.closePath();
212
212
  ctx.stroke();
213
213
  };
214
- export const drawBorder = ({ ctx, x, y, width, height, borderRadius, computedStyle, }) => {
214
+ export const drawBorder = ({ ctx, rect, borderRadius, computedStyle, }) => {
215
215
  const borders = getBorderSideProperties(computedStyle);
216
216
  // Check if we have any visible border
217
217
  const hasBorder = borders.top.width > 0 ||
@@ -240,10 +240,10 @@ export const drawBorder = ({ ctx, x, y, width, height, borderRadius, computedSty
240
240
  // Draw as a single continuous border for continuous dashing
241
241
  drawUniformBorder({
242
242
  ctx,
243
- x,
244
- y,
245
- width,
246
- height,
243
+ x: rect.left,
244
+ y: rect.top,
245
+ width: rect.width,
246
+ height: rect.height,
247
247
  borderRadius,
248
248
  borderWidth: borders.top.width,
249
249
  borderColor: borders.top.color,
@@ -255,10 +255,10 @@ export const drawBorder = ({ ctx, x, y, width, height, borderRadius, computedSty
255
255
  drawCorner({
256
256
  ctx,
257
257
  corner: 'topLeft',
258
- x,
259
- y,
260
- width,
261
- height,
258
+ x: rect.left,
259
+ y: rect.top,
260
+ width: rect.width,
261
+ height: rect.height,
262
262
  borderRadius,
263
263
  topBorder: borders.top,
264
264
  rightBorder: borders.right,
@@ -268,10 +268,10 @@ export const drawBorder = ({ ctx, x, y, width, height, borderRadius, computedSty
268
268
  drawCorner({
269
269
  ctx,
270
270
  corner: 'topRight',
271
- x,
272
- y,
273
- width,
274
- height,
271
+ x: rect.left,
272
+ y: rect.top,
273
+ width: rect.width,
274
+ height: rect.height,
275
275
  borderRadius,
276
276
  topBorder: borders.top,
277
277
  rightBorder: borders.right,
@@ -281,10 +281,10 @@ export const drawBorder = ({ ctx, x, y, width, height, borderRadius, computedSty
281
281
  drawCorner({
282
282
  ctx,
283
283
  corner: 'bottomRight',
284
- x,
285
- y,
286
- width,
287
- height,
284
+ x: rect.left,
285
+ y: rect.top,
286
+ width: rect.width,
287
+ height: rect.height,
288
288
  borderRadius,
289
289
  topBorder: borders.top,
290
290
  rightBorder: borders.right,
@@ -294,10 +294,10 @@ export const drawBorder = ({ ctx, x, y, width, height, borderRadius, computedSty
294
294
  drawCorner({
295
295
  ctx,
296
296
  corner: 'bottomLeft',
297
- x,
298
- y,
299
- width,
300
- height,
297
+ x: rect.left,
298
+ y: rect.top,
299
+ width: rect.width,
300
+ height: rect.height,
301
301
  borderRadius,
302
302
  topBorder: borders.top,
303
303
  rightBorder: borders.right,
@@ -308,40 +308,40 @@ export const drawBorder = ({ ctx, x, y, width, height, borderRadius, computedSty
308
308
  drawBorderSide({
309
309
  ctx,
310
310
  side: 'top',
311
- x,
312
- y,
313
- width,
314
- height,
311
+ x: rect.left,
312
+ y: rect.top,
313
+ width: rect.width,
314
+ height: rect.height,
315
315
  borderRadius,
316
316
  borderProperties: borders.top,
317
317
  });
318
318
  drawBorderSide({
319
319
  ctx,
320
320
  side: 'right',
321
- x,
322
- y,
323
- width,
324
- height,
321
+ x: rect.left,
322
+ y: rect.top,
323
+ width: rect.width,
324
+ height: rect.height,
325
325
  borderRadius,
326
326
  borderProperties: borders.right,
327
327
  });
328
328
  drawBorderSide({
329
329
  ctx,
330
330
  side: 'bottom',
331
- x,
332
- y,
333
- width,
334
- height,
331
+ x: rect.left,
332
+ y: rect.top,
333
+ width: rect.width,
334
+ height: rect.height,
335
335
  borderRadius,
336
336
  borderProperties: borders.bottom,
337
337
  });
338
338
  drawBorderSide({
339
339
  ctx,
340
340
  side: 'left',
341
- x,
342
- y,
343
- width,
344
- height,
341
+ x: rect.left,
342
+ y: rect.top,
343
+ width: rect.width,
344
+ height: rect.height,
345
345
  borderRadius,
346
346
  borderProperties: borders.left,
347
347
  });
@@ -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,12 @@
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, parentRect, }: {
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;
11
12
  }) => Promise<DrawElementToCanvasReturnValue>;
@@ -1,11 +1,9 @@
1
- import { Internals } from 'remotion';
2
- import { compose } from '../compose';
3
- import { getBiggestBoundingClientRect } from '../get-biggest-bounding-client-rect';
4
1
  import { calculateTransforms } from './calculate-transforms';
5
2
  import { drawElement } from './draw-element';
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);
3
+ import { handle3dTransform } from './handle-3d-transform';
4
+ export const drawElementToCanvas = async ({ element, context, draw, offsetLeft, offsetTop, logLevel, parentRect, }) => {
5
+ const transforms = calculateTransforms({ element, offsetLeft, offsetTop });
6
+ const { totalMatrix, reset, dimensions, opacity, computedStyle } = transforms;
9
7
  if (opacity === 0) {
10
8
  reset();
11
9
  return 'continue';
@@ -15,40 +13,14 @@ export const drawElementToCanvas = async ({ element, context, draw, offsetLeft,
15
13
  return 'continue';
16
14
  }
17
15
  if (!totalMatrix.is2D) {
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({
16
+ await handle3dTransform({
30
17
  element,
31
- context: context2,
32
- offsetLeft: canvasOffsetLeft,
33
- offsetTop: canvasOffsetTop,
18
+ totalMatrix,
19
+ parentRect,
20
+ context,
34
21
  logLevel,
35
22
  });
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();
47
23
  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`);
52
24
  return 'skip-children';
53
25
  }
54
26
  await drawElement({
@@ -1,9 +1,14 @@
1
+ import type { LogLevel } from 'remotion';
1
2
  import type { DrawFn } from './drawn-fn';
2
- export declare const drawElement: ({ dimensions, computedStyle, context, draw, opacity, totalMatrix, }: {
3
- dimensions: DOMRect;
3
+ export declare const drawElement: ({ rect, computedStyle, context, draw, opacity, totalMatrix, parentRect, logLevel, }: {
4
+ rect: DOMRect;
4
5
  computedStyle: CSSStyleDeclaration;
5
6
  context: OffscreenCanvasRenderingContext2D;
6
7
  opacity: number;
7
8
  totalMatrix: DOMMatrix;
8
9
  draw: DrawFn;
9
- }) => Promise<void>;
10
+ parentRect: DOMRect;
11
+ logLevel: LogLevel;
12
+ }) => Promise<{
13
+ cleanupAfterChildren: () => void;
14
+ }>;
@@ -1,50 +1,96 @@
1
1
  import { parseBorderRadius, setBorderRadius } from './border-radius';
2
2
  import { drawBorder } from './draw-border';
3
+ import { setBoxShadow } from './draw-box-shadow';
4
+ import { drawOutline } from './draw-outline';
3
5
  import { setOpacity } from './opacity';
6
+ import { setOverflowHidden } from './overflow';
7
+ import { createCanvasGradient, parseLinearGradient, } from './parse-linear-gradient';
4
8
  import { setTransform } from './transform';
5
- export const drawElement = async ({ dimensions, computedStyle, context, draw, opacity, totalMatrix, }) => {
9
+ export const drawElement = async ({ rect, computedStyle, context, draw, opacity, totalMatrix, parentRect, logLevel, }) => {
6
10
  const background = computedStyle.backgroundColor;
11
+ const { backgroundImage } = computedStyle;
7
12
  const borderRadius = parseBorderRadius({
8
13
  borderRadius: computedStyle.borderRadius,
9
- width: dimensions.width,
10
- height: dimensions.height,
14
+ width: rect.width,
15
+ height: rect.height,
11
16
  });
12
17
  const finishTransform = setTransform({
13
18
  ctx: context,
14
19
  transform: totalMatrix,
20
+ parentRect,
15
21
  });
16
- const finishBorderRadius = setBorderRadius({
22
+ const finishOpacity = setOpacity({
23
+ ctx: context,
24
+ opacity,
25
+ });
26
+ // Draw box shadow before border radius clip and background
27
+ setBoxShadow({
17
28
  ctx: context,
18
- x: dimensions.left,
19
- y: dimensions.top,
20
- width: dimensions.width,
21
- height: dimensions.height,
29
+ computedStyle,
30
+ rect,
22
31
  borderRadius,
32
+ logLevel,
23
33
  });
24
- const finishOpacity = setOpacity({
34
+ const finishBorderRadius = setBorderRadius({
25
35
  ctx: context,
26
- opacity,
36
+ rect,
37
+ borderRadius,
38
+ forceClipEvenWhenZero: false,
27
39
  });
28
- if (background &&
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')))) {
32
63
  const originalFillStyle = context.fillStyle;
33
64
  context.fillStyle = background;
34
- context.fillRect(dimensions.left, dimensions.top, dimensions.width, dimensions.height);
65
+ context.fillRect(rect.left, rect.top, rect.width, rect.height);
35
66
  context.fillStyle = originalFillStyle;
36
67
  }
37
- await draw({ dimensions, computedStyle, contextToDraw: context });
68
+ await draw({ dimensions: rect, computedStyle, contextToDraw: context });
38
69
  drawBorder({
39
70
  ctx: context,
40
- x: dimensions.left,
41
- y: dimensions.top,
42
- width: dimensions.width,
43
- height: dimensions.height,
71
+ rect,
44
72
  borderRadius,
45
73
  computedStyle,
46
74
  });
47
- finishOpacity();
48
75
  finishBorderRadius();
76
+ // Drawing outline ignores overflow: hidden, finishing it and starting a new one for the outline
77
+ drawOutline({
78
+ ctx: context,
79
+ rect,
80
+ borderRadius,
81
+ computedStyle,
82
+ });
83
+ const finishOverflowHidden = setOverflowHidden({
84
+ ctx: context,
85
+ rect,
86
+ borderRadius,
87
+ overflowHidden: computedStyle.overflow === 'hidden',
88
+ });
49
89
  finishTransform();
90
+ return {
91
+ cleanupAfterChildren: () => {
92
+ finishOpacity();
93
+ finishOverflowHidden();
94
+ },
95
+ };
50
96
  };
@@ -0,0 +1,9 @@
1
+ import type { BorderRadiusCorners } from './border-radius';
2
+ export declare const parseOutlineWidth: (value: string) => number;
3
+ export declare const parseOutlineOffset: (value: string) => number;
4
+ export declare const drawOutline: ({ ctx, rect, borderRadius, computedStyle, }: {
5
+ ctx: OffscreenCanvasRenderingContext2D;
6
+ rect: DOMRect;
7
+ borderRadius: BorderRadiusCorners;
8
+ computedStyle: CSSStyleDeclaration;
9
+ }) => void;