@remotion/web-renderer 4.0.392 → 4.0.394

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 (63) hide show
  1. package/dist/calculate-transforms.d.ts +0 -2
  2. package/dist/calculate-transforms.js +0 -17
  3. package/dist/can-use-webfs-target.d.ts +1 -0
  4. package/dist/can-use-webfs-target.js +19 -0
  5. package/dist/composable.d.ts +8 -2
  6. package/dist/compose-canvas.js +4 -28
  7. package/dist/compose.d.ts +6 -1
  8. package/dist/compose.js +31 -23
  9. package/dist/drawing/calculate-transforms.js +2 -2
  10. package/dist/drawing/draw-dom-element.d.ts +2 -0
  11. package/dist/drawing/draw-dom-element.js +17 -0
  12. package/dist/drawing/draw-element-to-canvas.d.ts +7 -3
  13. package/dist/drawing/draw-element-to-canvas.js +23 -24
  14. package/dist/drawing/draw-element.d.ts +2 -1
  15. package/dist/drawing/draw-element.js +1 -1
  16. package/dist/drawing/drawn-fn.d.ts +5 -0
  17. package/dist/drawing/drawn-fn.js +1 -0
  18. package/dist/drawing/text/apply-text-transform.d.ts +1 -0
  19. package/dist/drawing/text/apply-text-transform.js +12 -0
  20. package/dist/drawing/text/draw-text.d.ts +2 -1
  21. package/dist/drawing/text/draw-text.js +41 -54
  22. package/dist/drawing/text/handle-text-node.d.ts +7 -1
  23. package/dist/drawing/text/handle-text-node.js +7 -54
  24. package/dist/drawing/transform-in-3d.js +28 -11
  25. package/dist/esm/index.mjs +365 -8420
  26. package/dist/find-capturable-elements.d.ts +1 -1
  27. package/dist/find-capturable-elements.js +22 -20
  28. package/dist/get-biggest-bounding-client-rect.d.ts +1 -0
  29. package/dist/get-biggest-bounding-client-rect.js +15 -0
  30. package/dist/index.d.ts +2 -1
  31. package/dist/mediabunny-mappings.d.ts +1 -0
  32. package/dist/mediabunny-mappings.js +10 -0
  33. package/dist/output-target.d.ts +1 -0
  34. package/dist/output-target.js +1 -0
  35. package/dist/render-media-on-web.d.ts +6 -1
  36. package/dist/render-media-on-web.js +64 -24
  37. package/dist/render-operations-queue.d.ts +3 -0
  38. package/dist/render-operations-queue.js +3 -0
  39. package/dist/render-still-on-web.js +15 -9
  40. package/dist/send-telemetry-event.d.ts +5 -0
  41. package/dist/send-telemetry-event.js +22 -0
  42. package/dist/take-screenshot.js +1 -1
  43. package/dist/walk-tree.d.ts +1 -0
  44. package/dist/walk-tree.js +14 -0
  45. package/dist/web-fs-target.d.ts +7 -0
  46. package/dist/web-fs-target.js +41 -0
  47. package/package.json +6 -6
  48. package/dist/border-radius.d.ts +0 -31
  49. package/dist/border-radius.js +0 -152
  50. package/dist/drawing/compose-canvas.d.ts +0 -1
  51. package/dist/drawing/compose-canvas.js +0 -36
  52. package/dist/drawing/compose-svg.d.ts +0 -1
  53. package/dist/drawing/compose-svg.js +0 -34
  54. package/dist/drawing/compose.d.ts +0 -5
  55. package/dist/drawing/compose.js +0 -6
  56. package/dist/drawing/get-computed-style-cache.d.ts +0 -0
  57. package/dist/drawing/get-computed-style-cache.js +0 -1
  58. package/dist/find-canvas-elements.d.ts +0 -1
  59. package/dist/find-canvas-elements.js +0 -13
  60. package/dist/opacity.d.ts +0 -4
  61. package/dist/opacity.js +0 -7
  62. package/dist/transform.d.ts +0 -4
  63. package/dist/transform.js +0 -6
@@ -6,6 +6,4 @@ export declare const calculateTransforms: (element: HTMLElement | SVGSVGElement)
6
6
  x: number;
7
7
  y: number;
8
8
  };
9
- borderRadius: import("./drawing/border-radius").BorderRadiusCorners;
10
- opacity: number;
11
9
  };
@@ -1,4 +1,3 @@
1
- import { parseBorderRadius } from './drawing/border-radius';
2
1
  import { parseTransformOrigin } from './parse-transform-origin';
3
2
  const getInternalTransformOrigin = (transform) => {
4
3
  var _a;
@@ -22,18 +21,8 @@ export const calculateTransforms = (element) => {
22
21
  let parent = element;
23
22
  const transforms = [];
24
23
  const toReset = [];
25
- let borderRadius = '';
26
- let opacity = 1;
27
24
  while (parent) {
28
25
  const computedStyle = getComputedStyle(parent);
29
- if (parent === element) {
30
- borderRadius = computedStyle.borderRadius;
31
- }
32
- // Multiply opacity values from element and all parents
33
- const parentOpacity = computedStyle.opacity;
34
- if (parentOpacity && parentOpacity !== '') {
35
- opacity *= parseFloat(parentOpacity);
36
- }
37
26
  if ((computedStyle.transform && computedStyle.transform !== 'none') ||
38
27
  parent === element) {
39
28
  const toParse = computedStyle.transform === 'none' || computedStyle.transform === ''
@@ -81,11 +70,5 @@ export const calculateTransforms = (element) => {
81
70
  }
82
71
  },
83
72
  nativeTransformOrigin,
84
- borderRadius: parseBorderRadius({
85
- borderRadius,
86
- width: dimensions.width,
87
- height: dimensions.height,
88
- }),
89
- opacity,
90
73
  };
91
74
  };
@@ -0,0 +1 @@
1
+ export declare const canUseWebFsWriter: () => Promise<boolean>;
@@ -0,0 +1,19 @@
1
+ export const canUseWebFsWriter = async () => {
2
+ if (!('storage' in navigator)) {
3
+ return false;
4
+ }
5
+ if (!('getDirectory' in navigator.storage)) {
6
+ return false;
7
+ }
8
+ try {
9
+ const directoryHandle = await navigator.storage.getDirectory();
10
+ const fileHandle = await directoryHandle.getFileHandle('remotion-probe-web-fs-support', {
11
+ create: true,
12
+ });
13
+ const canUse = fileHandle.createWritable !== undefined;
14
+ return canUse;
15
+ }
16
+ catch (_a) {
17
+ return false;
18
+ }
19
+ };
@@ -1,4 +1,10 @@
1
1
  export type Composable = {
2
- type: 'element';
3
- element: HTMLElement | SVGElement;
2
+ type: 'canvas';
3
+ element: HTMLCanvasElement;
4
+ } | {
5
+ type: 'svg';
6
+ element: SVGSVGElement;
7
+ } | {
8
+ type: 'img';
9
+ element: HTMLImageElement;
4
10
  };
@@ -1,36 +1,12 @@
1
- import { setBorderRadius } from './border-radius';
2
1
  import { calculateTransforms } from './calculate-transforms';
3
- import { turnSvgIntoDrawable } from './drawing/compose-svg';
4
- import { setOpacity } from './drawing/opacity';
5
- import { setTransform } from './drawing/transform';
2
+ import { turnSvgIntoDrawable } from './compose-svg';
6
3
  export const composeCanvas = async (canvas, context) => {
7
- const { totalMatrix, reset, dimensions, borderRadius, opacity } = calculateTransforms(canvas);
8
- if (opacity === 0) {
9
- reset();
10
- return;
11
- }
4
+ const { totalMatrix, reset, dimensions } = calculateTransforms(canvas);
5
+ context.setTransform(totalMatrix);
12
6
  const drawable = canvas instanceof SVGSVGElement
13
7
  ? await turnSvgIntoDrawable(canvas)
14
8
  : 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
9
  context.drawImage(drawable, dimensions.left, dimensions.top, dimensions.width, dimensions.height);
32
- finishOpacity();
33
- finishBorderRadius();
34
- finishTransform();
10
+ context.setTransform(new DOMMatrix());
35
11
  reset();
36
12
  };
package/dist/compose.d.ts CHANGED
@@ -1 +1,6 @@
1
- export declare const compose: (element: HTMLDivElement, context: OffscreenCanvasRenderingContext2D) => Promise<void>;
1
+ export declare const compose: ({ element, context, offsetLeft, offsetTop, }: {
2
+ element: HTMLElement | SVGElement;
3
+ context: OffscreenCanvasRenderingContext2D;
4
+ offsetLeft: number;
5
+ offsetTop: number;
6
+ }) => Promise<void>;
package/dist/compose.js CHANGED
@@ -1,7 +1,23 @@
1
+ import { drawDomElement } from './drawing/draw-dom-element';
1
2
  import { drawElementToCanvas } from './drawing/draw-element-to-canvas';
2
3
  import { handleTextNode } from './drawing/text/handle-text-node';
3
- import { turnSvgIntoDrawable } from './drawing/turn-svg-into-drawable';
4
- export const compose = async (element, context) => {
4
+ import { skipToNextNonDescendant } from './walk-tree';
5
+ const walkOverNode = ({ node, context, offsetLeft, offsetTop, }) => {
6
+ if (node instanceof HTMLElement || node instanceof SVGElement) {
7
+ return drawElementToCanvas({
8
+ element: node,
9
+ context,
10
+ draw: drawDomElement(node),
11
+ offsetLeft,
12
+ offsetTop,
13
+ });
14
+ }
15
+ if (node instanceof Text) {
16
+ return handleTextNode({ node, context, offsetLeft, offsetTop });
17
+ }
18
+ throw new Error('Unknown node type');
19
+ };
20
+ export const compose = async ({ element, context, offsetLeft, offsetTop, }) => {
5
21
  const treeWalker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, (node) => {
6
22
  if (node instanceof Element) {
7
23
  // SVG does have children, but we process SVG elements in its
@@ -16,28 +32,20 @@ export const compose = async (element, context) => {
16
32
  }
17
33
  return NodeFilter.FILTER_ACCEPT;
18
34
  });
19
- while (treeWalker.nextNode()) {
20
- const node = treeWalker.currentNode;
21
- if (node instanceof HTMLElement || node instanceof SVGElement) {
22
- await drawElementToCanvas({
23
- element: node,
24
- context,
25
- draw: async (dimensions) => {
26
- const drawable = await (node instanceof SVGSVGElement
27
- ? turnSvgIntoDrawable(node)
28
- : node instanceof HTMLImageElement
29
- ? node
30
- : node instanceof HTMLCanvasElement
31
- ? node
32
- : null);
33
- if (drawable) {
34
- context.drawImage(drawable, dimensions.left, dimensions.top, dimensions.width, dimensions.height);
35
- }
36
- },
37
- });
35
+ while (true) {
36
+ const val = await walkOverNode({
37
+ node: treeWalker.currentNode,
38
+ context,
39
+ offsetLeft,
40
+ offsetTop,
41
+ });
42
+ if (val === 'skip-children') {
43
+ if (!skipToNextNonDescendant(treeWalker)) {
44
+ break;
45
+ }
38
46
  }
39
- else if (node instanceof Text) {
40
- await handleTextNode(node, context);
47
+ else if (!treeWalker.nextNode()) {
48
+ break;
41
49
  }
42
50
  }
43
51
  };
@@ -46,10 +46,10 @@ export const calculateTransforms = (element) => {
46
46
  // 2. Rotate
47
47
  // 3. Scale
48
48
  // 4. CSS "transform"
49
- if (rotate !== '') {
49
+ if (rotate !== '' && rotate !== 'none') {
50
50
  additionalMatrices.push(new DOMMatrix(`rotate(${rotate})`));
51
51
  }
52
- if (scale !== '') {
52
+ if (scale !== '' && scale !== 'none') {
53
53
  additionalMatrices.push(new DOMMatrix(`scale(${scale})`));
54
54
  }
55
55
  additionalMatrices.push(matrix);
@@ -0,0 +1,2 @@
1
+ import type { DrawFn } from './drawn-fn';
2
+ export declare const drawDomElement: (node: HTMLElement | SVGElement) => DrawFn;
@@ -0,0 +1,17 @@
1
+ import { turnSvgIntoDrawable } from './turn-svg-into-drawable';
2
+ export const drawDomElement = (node) => {
3
+ const domDrawFn = async ({ dimensions, contextToDraw }) => {
4
+ const drawable = await (node instanceof SVGSVGElement
5
+ ? turnSvgIntoDrawable(node)
6
+ : node instanceof HTMLImageElement
7
+ ? node
8
+ : node instanceof HTMLCanvasElement
9
+ ? node
10
+ : null);
11
+ if (!drawable) {
12
+ return;
13
+ }
14
+ contextToDraw.drawImage(drawable, dimensions.left, dimensions.top, dimensions.width, dimensions.height);
15
+ };
16
+ return domDrawFn;
17
+ };
@@ -1,5 +1,9 @@
1
- export declare const drawElementToCanvas: ({ element, context, draw, }: {
1
+ import type { DrawFn } from './drawn-fn';
2
+ export type DrawElementToCanvasReturnValue = 'continue' | 'skip-children';
3
+ export declare const drawElementToCanvas: ({ element, context, draw, offsetLeft, offsetTop, }: {
2
4
  element: HTMLElement | SVGElement;
3
5
  context: OffscreenCanvasRenderingContext2D;
4
- draw: (dimensions: DOMRect, computedStyle: CSSStyleDeclaration) => Promise<void> | void;
5
- }) => Promise<void>;
6
+ draw: DrawFn;
7
+ offsetLeft: number;
8
+ offsetTop: number;
9
+ }) => Promise<DrawElementToCanvasReturnValue>;
@@ -1,19 +1,20 @@
1
+ import { compose } from '../compose';
1
2
  import { calculateTransforms } from './calculate-transforms';
2
3
  import { drawElement } from './draw-element';
3
4
  import { transformIn3d } from './transform-in-3d';
4
- export const drawElementToCanvas = async ({ element, context, draw, }) => {
5
+ export const drawElementToCanvas = async ({ element, context, draw, offsetLeft, offsetTop, }) => {
5
6
  const { totalMatrix, reset, dimensions, opacity, computedStyle } = calculateTransforms(element);
6
7
  if (opacity === 0) {
7
8
  reset();
8
- return;
9
+ return 'continue';
9
10
  }
10
11
  if (dimensions.width <= 0 || dimensions.height <= 0) {
11
12
  reset();
12
- return;
13
+ return 'continue';
13
14
  }
14
15
  if (!totalMatrix.is2D) {
15
- const offsetLeft = Math.min(dimensions.left, 0);
16
- const offsetTop = Math.min(dimensions.top, 0);
16
+ const canvasOffsetLeft = Math.min(dimensions.left, 0);
17
+ const canvasOffsetTop = Math.min(dimensions.top, 0);
17
18
  const tempCanvasWidth = Math.max(dimensions.width, dimensions.right);
18
19
  const tempCanvasHeight = Math.max(dimensions.height, dimensions.bottom);
19
20
  const tempCanvas = new OffscreenCanvas(tempCanvasWidth, tempCanvasHeight);
@@ -21,34 +22,32 @@ export const drawElementToCanvas = async ({ element, context, draw, }) => {
21
22
  if (!context2) {
22
23
  throw new Error('Could not get context');
23
24
  }
24
- const adjustedDimensions = new DOMRect(dimensions.left - offsetLeft, dimensions.top - offsetTop, dimensions.width, dimensions.height);
25
- await drawElement({
26
- dimensions: adjustedDimensions,
27
- computedStyle,
25
+ await compose({
26
+ element,
28
27
  context: context2,
29
- draw,
30
- opacity,
31
- totalMatrix: new DOMMatrix(),
28
+ offsetLeft: canvasOffsetLeft,
29
+ offsetTop: canvasOffsetTop,
32
30
  });
33
31
  const transformed = transformIn3d({
34
32
  canvasWidth: tempCanvasWidth,
35
33
  canvasHeight: tempCanvasHeight,
36
34
  matrix: totalMatrix,
37
35
  sourceCanvas: tempCanvas,
38
- offsetLeft,
39
- offsetTop,
36
+ offsetLeft: canvasOffsetLeft,
37
+ offsetTop: canvasOffsetTop,
40
38
  });
41
39
  context.drawImage(transformed, 0, 0);
40
+ reset();
41
+ return 'skip-children';
42
42
  }
43
- else {
44
- await drawElement({
45
- dimensions,
46
- computedStyle,
47
- context,
48
- draw,
49
- opacity,
50
- totalMatrix,
51
- });
52
- }
43
+ await drawElement({
44
+ dimensions: new DOMRect(dimensions.left - offsetLeft, dimensions.top - offsetTop, dimensions.width, dimensions.height),
45
+ computedStyle,
46
+ context,
47
+ draw,
48
+ opacity,
49
+ totalMatrix,
50
+ });
53
51
  reset();
52
+ return 'continue';
54
53
  };
@@ -1,8 +1,9 @@
1
+ import type { DrawFn } from './drawn-fn';
1
2
  export declare const drawElement: ({ dimensions, computedStyle, context, draw, opacity, totalMatrix, }: {
2
3
  dimensions: DOMRect;
3
4
  computedStyle: CSSStyleDeclaration;
4
5
  context: OffscreenCanvasRenderingContext2D;
5
6
  opacity: number;
6
7
  totalMatrix: DOMMatrix;
7
- draw: (dimensions: DOMRect, computedStyle: CSSStyleDeclaration) => Promise<void> | void;
8
+ draw: DrawFn;
8
9
  }) => Promise<void>;
@@ -34,7 +34,7 @@ export const drawElement = async ({ dimensions, computedStyle, context, draw, op
34
34
  context.fillRect(dimensions.left, dimensions.top, dimensions.width, dimensions.height);
35
35
  context.fillStyle = originalFillStyle;
36
36
  }
37
- await draw(dimensions, computedStyle);
37
+ await draw({ dimensions, computedStyle, contextToDraw: context });
38
38
  drawBorder({
39
39
  ctx: context,
40
40
  x: dimensions.left,
@@ -0,0 +1,5 @@
1
+ export type DrawFn = ({ computedStyle, contextToDraw, dimensions, }: {
2
+ dimensions: DOMRect;
3
+ computedStyle: CSSStyleDeclaration;
4
+ contextToDraw: OffscreenCanvasRenderingContext2D;
5
+ }) => Promise<void> | void;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare const applyTextTransform: (text: string, transform: string) => string;
@@ -0,0 +1,12 @@
1
+ export const applyTextTransform = (text, transform) => {
2
+ if (transform === 'uppercase') {
3
+ return text.toUpperCase();
4
+ }
5
+ if (transform === 'lowercase') {
6
+ return text.toLowerCase();
7
+ }
8
+ if (transform === 'capitalize') {
9
+ return text.replace(/\b\w/g, (char) => char.toUpperCase());
10
+ }
11
+ return text;
12
+ };
@@ -1 +1,2 @@
1
- export declare const drawTextToCanvas: (parentElement: HTMLElement, context: OffscreenCanvasRenderingContext2D) => Promise<void>;
1
+ import type { DrawFn } from '../drawn-fn';
2
+ export declare const drawText: (span: HTMLSpanElement) => DrawFn;
@@ -1,57 +1,44 @@
1
- import { withResolvers } from '../../with-resolvers';
2
- export const drawTextToCanvas = async (parentElement, context) => {
3
- const clientRect = parentElement.getBoundingClientRect();
4
- const computedStyle = getComputedStyle(parentElement);
5
- const clonedNode = parentElement.cloneNode(true);
6
- for (let i = 0; i < computedStyle.length; i++) {
7
- const propertyName = computedStyle[i];
8
- const value = computedStyle.getPropertyValue(propertyName);
9
- if (propertyName === 'margin-top' ||
10
- propertyName === 'margin-bottom' ||
11
- propertyName === 'margin-left' ||
12
- propertyName === 'margin-right' ||
13
- propertyName === 'margin') {
14
- // @ts-expect-error - propertyName is a valid CSS property
15
- clonedNode.style[propertyName] = '0px';
16
- continue;
1
+ import { Internals } from 'remotion';
2
+ import { applyTextTransform } from './apply-text-transform';
3
+ import { findLineBreaks } from './find-line-breaks.text';
4
+ import { getCollapsedText } from './get-collapsed-text';
5
+ export const drawText = (span) => {
6
+ const drawFn = ({ dimensions: rect, computedStyle, contextToDraw }) => {
7
+ const { fontFamily, fontSize, fontWeight, color, lineHeight, direction, writingMode, letterSpacing, textTransform, } = computedStyle;
8
+ const isVertical = writingMode !== 'horizontal-tb';
9
+ if (isVertical) {
10
+ // TODO: Only warn once per render.
11
+ Internals.Log.warn({
12
+ logLevel: 'warn',
13
+ tag: '@remotion/web-renderer',
14
+ }, 'Detected "writing-mode" CSS property. Vertical text is not yet supported in @remotion/web-renderer');
15
+ return;
17
16
  }
18
- // @ts-expect-error - propertyName is a valid CSS property
19
- clonedNode.style[propertyName] = value;
20
- }
21
- // Create an SVG that contains the cloned node via a foreignObject
22
- const svgNS = 'http://www.w3.org/2000/svg';
23
- const svg = document.createElementNS(svgNS, 'svg');
24
- svg.setAttribute('width', `${clientRect.width}`);
25
- svg.setAttribute('height', `${clientRect.height}`);
26
- const foreignObject = document.createElementNS(svgNS, 'foreignObject');
27
- foreignObject.setAttribute('x', '0');
28
- foreignObject.setAttribute('y', '0');
29
- foreignObject.setAttribute('width', `${clientRect.width}`);
30
- foreignObject.setAttribute('height', `${clientRect.height}`);
31
- // The cloned node must be in XHTML namespace to render properly
32
- const xhtmlNS = 'http://www.w3.org/1999/xhtml';
33
- const wrappedDiv = document.createElementNS(xhtmlNS, 'div');
34
- wrappedDiv.style.width = `${clientRect.width}px`;
35
- wrappedDiv.style.height = `${clientRect.height}px`;
36
- wrappedDiv.appendChild(clonedNode);
37
- foreignObject.appendChild(wrappedDiv);
38
- svg.appendChild(foreignObject);
39
- // Convert SVG to data URL
40
- const serializer = new XMLSerializer();
41
- const svgString = serializer.serializeToString(svg);
42
- const svgDataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
43
- const { promise, resolve, reject } = withResolvers();
44
- // Create image and draw when loaded
45
- const img = new window.Image();
46
- img.onload = function () {
47
- resolve(img);
48
- };
49
- img.onerror = function (err) {
50
- // We may want to add robust error handling here
51
- reject(err);
17
+ contextToDraw.save();
18
+ contextToDraw.font = `${fontWeight} ${fontSize} ${fontFamily}`;
19
+ contextToDraw.fillStyle = color;
20
+ contextToDraw.letterSpacing = letterSpacing;
21
+ const fontSizePx = parseFloat(fontSize);
22
+ // TODO: This is not necessarily correct, need to create text and measure to know for sure
23
+ const lineHeightPx = lineHeight === 'normal' ? 1.2 * fontSizePx : parseFloat(lineHeight);
24
+ const baselineOffset = (lineHeightPx - fontSizePx) / 2;
25
+ const isRTL = direction === 'rtl';
26
+ contextToDraw.textAlign = isRTL ? 'right' : 'left';
27
+ contextToDraw.textBaseline = 'top';
28
+ const originalText = span.textContent;
29
+ const collapsedText = getCollapsedText(span);
30
+ const transformedText = applyTextTransform(collapsedText, textTransform);
31
+ span.textContent = transformedText;
32
+ // For RTL text, fill from the right edge instead of left
33
+ const xPosition = isRTL ? rect.right : rect.left;
34
+ const lines = findLineBreaks(span, isRTL);
35
+ let offsetTop = 0;
36
+ for (const line of lines) {
37
+ contextToDraw.fillText(line.text, xPosition + line.offsetHorizontal, rect.top + baselineOffset + offsetTop);
38
+ offsetTop += line.offsetTop;
39
+ }
40
+ span.textContent = originalText;
41
+ contextToDraw.restore();
52
42
  };
53
- img.src = svgDataUrl;
54
- await promise;
55
- console.log(clientRect);
56
- context.drawImage(img, clientRect.left, clientRect.top, clientRect.width, clientRect.height);
43
+ return drawFn;
57
44
  };
@@ -1 +1,7 @@
1
- export declare const handleTextNode: (node: Text, context: OffscreenCanvasRenderingContext2D) => Promise<void>;
1
+ import type { DrawElementToCanvasReturnValue } from '../draw-element-to-canvas';
2
+ export declare const handleTextNode: ({ node, context, offsetLeft, offsetTop, }: {
3
+ node: Text;
4
+ context: OffscreenCanvasRenderingContext2D;
5
+ offsetLeft: number;
6
+ offsetTop: number;
7
+ }) => Promise<DrawElementToCanvasReturnValue>;
@@ -1,20 +1,6 @@
1
- import { Internals } from 'remotion';
2
1
  import { drawElementToCanvas } from '../draw-element-to-canvas';
3
- import { findLineBreaks } from './find-line-breaks.text';
4
- import { getCollapsedText } from './get-collapsed-text';
5
- const applyTextTransform = (text, transform) => {
6
- if (transform === 'uppercase') {
7
- return text.toUpperCase();
8
- }
9
- if (transform === 'lowercase') {
10
- return text.toLowerCase();
11
- }
12
- if (transform === 'capitalize') {
13
- return text.replace(/\b\w/g, (char) => char.toUpperCase());
14
- }
15
- return text;
16
- };
17
- export const handleTextNode = async (node, context) => {
2
+ import { drawText } from './draw-text';
3
+ export const handleTextNode = async ({ node, context, offsetLeft, offsetTop, }) => {
18
4
  const span = document.createElement('span');
19
5
  const parent = node.parentNode;
20
6
  if (!parent) {
@@ -22,48 +8,15 @@ export const handleTextNode = async (node, context) => {
22
8
  }
23
9
  parent.insertBefore(span, node);
24
10
  span.appendChild(node);
25
- await drawElementToCanvas({
11
+ const value = await drawElementToCanvas({
26
12
  context,
27
13
  element: span,
28
- draw(rect, style) {
29
- const { fontFamily, fontSize, fontWeight, color, lineHeight, direction, writingMode, letterSpacing, textTransform, } = style;
30
- const isVertical = writingMode !== 'horizontal-tb';
31
- if (isVertical) {
32
- // TODO: Only warn once per render.
33
- Internals.Log.warn({
34
- logLevel: 'warn',
35
- tag: '@remotion/web-renderer',
36
- }, 'Detected "writing-mode" CSS property. Vertical text is not yet supported in @remotion/web-renderer');
37
- return;
38
- }
39
- context.save();
40
- context.font = `${fontWeight} ${fontSize} ${fontFamily}`;
41
- context.fillStyle = color;
42
- context.letterSpacing = letterSpacing;
43
- const fontSizePx = parseFloat(fontSize);
44
- // TODO: This is not necessarily correct, need to create text and measure to know for sure
45
- const lineHeightPx = lineHeight === 'normal' ? 1.2 * fontSizePx : parseFloat(lineHeight);
46
- const baselineOffset = (lineHeightPx - fontSizePx) / 2;
47
- const isRTL = direction === 'rtl';
48
- context.textAlign = isRTL ? 'right' : 'left';
49
- context.textBaseline = 'top';
50
- const originalText = span.textContent;
51
- const collapsedText = getCollapsedText(span);
52
- const transformedText = applyTextTransform(collapsedText, textTransform);
53
- span.textContent = transformedText;
54
- // For RTL text, fill from the right edge instead of left
55
- const xPosition = isRTL ? rect.right : rect.left;
56
- const lines = findLineBreaks(span, isRTL);
57
- let offsetTop = 0;
58
- for (const line of lines) {
59
- context.fillText(line.text, xPosition + line.offsetHorizontal, rect.top + baselineOffset + offsetTop);
60
- offsetTop += line.offsetTop;
61
- }
62
- span.textContent = originalText;
63
- context.restore();
64
- },
14
+ draw: drawText(span),
15
+ offsetLeft,
16
+ offsetTop,
65
17
  });
66
18
  // Undo the layout manipulation
67
19
  parent.insertBefore(node, span);
68
20
  parent.removeChild(span);
21
+ return value;
69
22
  };
@@ -12,7 +12,16 @@ function compileShader(shaderGl, source, type) {
12
12
  }
13
13
  return shader;
14
14
  }
15
- export const transformIn3d = ({ canvasWidth, canvasHeight, matrix, sourceCanvas, offsetLeft, offsetTop, }) => {
15
+ let helperCanvas = null;
16
+ const createHelperCanvas = ({ canvasWidth, canvasHeight, }) => {
17
+ if (helperCanvas &&
18
+ helperCanvas.canvas.width >= canvasWidth &&
19
+ helperCanvas.canvas.height >= canvasHeight) {
20
+ // Clear and draw
21
+ helperCanvas.gl.clearColor(0, 0, 0, 0); // Transparent background
22
+ helperCanvas.gl.clear(helperCanvas.gl.COLOR_BUFFER_BIT);
23
+ return helperCanvas;
24
+ }
16
25
  const canvas = new OffscreenCanvas(canvasWidth, canvasHeight);
17
26
  const gl = canvas.getContext('webgl');
18
27
  if (!gl) {
@@ -52,6 +61,19 @@ export const transformIn3d = ({ canvasWidth, canvasHeight, matrix, sourceCanvas,
52
61
  throw new Error('Program link error: ' + gl.getProgramInfoLog(program));
53
62
  }
54
63
  gl.useProgram(program);
64
+ // Clear and draw
65
+ gl.clearColor(0, 0, 0, 0); // Transparent background
66
+ gl.clear(gl.COLOR_BUFFER_BIT);
67
+ // Enable blending for transparency
68
+ gl.enable(gl.BLEND);
69
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
70
+ helperCanvas = { canvas, gl, program };
71
+ return helperCanvas;
72
+ };
73
+ export const transformIn3d = ({ canvasWidth, canvasHeight, matrix, sourceCanvas, offsetLeft, offsetTop, }) => {
74
+ const { canvas, gl, program } = createHelperCanvas({ canvasWidth, canvasHeight });
75
+ const vertexBuffer = gl.createBuffer();
76
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
55
77
  // Create a quad (two triangles) with texture coordinates
56
78
  // prettier-ignore
57
79
  const vertices = new Float32Array([
@@ -65,8 +87,6 @@ export const transformIn3d = ({ canvasWidth, canvasHeight, matrix, sourceCanvas,
65
87
  canvasWidth + offsetLeft, offsetTop, 1, 0, // bottom-right
66
88
  canvasWidth + offsetLeft, canvasHeight + offsetTop, 1, 1, // top-right
67
89
  ]);
68
- const vertexBuffer = gl.createBuffer();
69
- gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
70
90
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
71
91
  const aPosition = gl.getAttribLocation(program, 'aPosition');
72
92
  const aTexCoord = gl.getAttribLocation(program, 'aTexCoord');
@@ -91,12 +111,12 @@ export const transformIn3d = ({ canvasWidth, canvasHeight, matrix, sourceCanvas,
91
111
  const zScale = 1000000000; // By default infinite in chrome
92
112
  // Create orthographic projection matrix for pixel coordinates
93
113
  const projectionMatrix = new Float32Array([
94
- 2 / canvasWidth,
114
+ 2 / canvas.width,
95
115
  0,
96
116
  0,
97
117
  0,
98
118
  0,
99
- -2 / canvasHeight,
119
+ -2 / canvas.height,
100
120
  0,
101
121
  0,
102
122
  0,
@@ -114,12 +134,9 @@ export const transformIn3d = ({ canvasWidth, canvasHeight, matrix, sourceCanvas,
114
134
  gl.uniformMatrix4fv(uTransform, false, transformMatrix);
115
135
  gl.uniformMatrix4fv(uProjection, false, projectionMatrix);
116
136
  gl.uniform1i(uTexture, 0); // Use texture unit 0
117
- // Clear and draw
118
- gl.clearColor(0, 0, 0, 0); // Transparent background
119
- gl.clear(gl.COLOR_BUFFER_BIT);
120
- // Enable blending for transparency
121
- gl.enable(gl.BLEND);
122
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
123
137
  gl.drawArrays(gl.TRIANGLES, 0, 6);
138
+ // Clean up resources to prevent leaks and ensure clean state for reuse
139
+ gl.deleteTexture(texture);
140
+ gl.deleteBuffer(vertexBuffer);
124
141
  return canvas;
125
142
  };