@remotion/web-renderer 4.0.394 → 4.0.396
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/border-radius.d.ts +31 -0
- package/dist/border-radius.js +152 -0
- package/dist/calculate-transforms.d.ts +2 -0
- package/dist/calculate-transforms.js +17 -0
- package/dist/composable.d.ts +2 -8
- package/dist/compose-canvas.js +28 -4
- package/dist/compose.d.ts +6 -3
- package/dist/compose.js +41 -12
- package/dist/drawing/border-radius.d.ts +3 -5
- package/dist/drawing/border-radius.js +12 -11
- package/dist/drawing/calculate-transforms.d.ts +6 -2
- package/dist/drawing/calculate-transforms.js +19 -22
- package/dist/drawing/canvas-offset-from-rect.d.ts +8 -0
- package/dist/drawing/canvas-offset-from-rect.js +12 -0
- package/dist/drawing/clamp-rect-to-parent-bounds.d.ts +4 -0
- package/dist/drawing/clamp-rect-to-parent-bounds.js +7 -0
- package/dist/drawing/compose-canvas.d.ts +1 -0
- package/dist/drawing/compose-canvas.js +36 -0
- package/dist/drawing/compose-svg.d.ts +1 -0
- package/dist/drawing/compose-svg.js +34 -0
- package/dist/drawing/compose.d.ts +5 -0
- package/dist/drawing/compose.js +6 -0
- package/dist/drawing/draw-border.d.ts +2 -5
- package/dist/drawing/draw-border.js +307 -55
- package/dist/drawing/draw-element-to-canvas.d.ts +4 -1
- package/dist/drawing/draw-element-to-canvas.js +9 -26
- package/dist/drawing/draw-element.d.ts +5 -3
- package/dist/drawing/draw-element.js +29 -14
- package/dist/drawing/draw-outline.d.ts +9 -0
- package/dist/drawing/draw-outline.js +116 -0
- package/dist/drawing/get-bounding-box-including-shadow.d.ts +1 -0
- package/dist/drawing/get-bounding-box-including-shadow.js +6 -0
- package/dist/drawing/get-computed-style-cache.d.ts +0 -0
- package/dist/drawing/get-computed-style-cache.js +1 -0
- package/dist/drawing/get-pretransform-rect.d.ts +1 -0
- package/dist/drawing/get-pretransform-rect.js +31 -0
- package/dist/drawing/handle-3d-transform.d.ts +10 -0
- package/dist/drawing/handle-3d-transform.js +39 -0
- package/dist/drawing/has-transform.d.ts +4 -0
- package/dist/drawing/has-transform.js +14 -0
- package/dist/drawing/overflow.d.ts +7 -0
- package/dist/drawing/overflow.js +12 -0
- package/dist/drawing/process-node.d.ts +17 -0
- package/dist/drawing/process-node.js +41 -0
- package/dist/drawing/text/draw-text.js +6 -8
- package/dist/drawing/text/handle-text-node.d.ts +8 -5
- package/dist/drawing/text/handle-text-node.js +6 -5
- package/dist/drawing/transform-in-3d.d.ts +7 -7
- package/dist/drawing/transform-in-3d.js +27 -13
- package/dist/drawing/transform-rect-with-matrix.d.ts +4 -0
- package/dist/drawing/transform-rect-with-matrix.js +19 -0
- package/dist/drawing/turn-svg-into-drawable.js +7 -0
- package/dist/esm/index.mjs +897 -205
- package/dist/find-canvas-elements.d.ts +1 -0
- package/dist/find-canvas-elements.js +13 -0
- package/dist/find-capturable-elements.d.ts +1 -1
- package/dist/find-capturable-elements.js +20 -22
- package/dist/get-biggest-bounding-client-rect.js +18 -5
- package/dist/internal-state.d.ts +9 -0
- package/dist/internal-state.js +12 -0
- package/dist/opacity.d.ts +4 -0
- package/dist/opacity.js +7 -0
- package/dist/render-media-on-web.d.ts +3 -0
- package/dist/render-media-on-web.js +38 -15
- package/dist/render-still-on-web.d.ts +6 -1
- package/dist/render-still-on-web.js +29 -8
- package/dist/send-telemetry-event.js +1 -1
- package/dist/take-screenshot.d.ts +8 -2
- package/dist/take-screenshot.js +16 -4
- package/dist/transform.d.ts +4 -0
- package/dist/transform.js +6 -0
- package/package.json +7 -6
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
export const parseOutlineWidth = (value) => {
|
|
2
|
+
return parseFloat(value) || 0;
|
|
3
|
+
};
|
|
4
|
+
export const parseOutlineOffset = (value) => {
|
|
5
|
+
return parseFloat(value) || 0;
|
|
6
|
+
};
|
|
7
|
+
const getLineDashPattern = (style, width) => {
|
|
8
|
+
if (style === 'dashed') {
|
|
9
|
+
return [width * 2, width];
|
|
10
|
+
}
|
|
11
|
+
if (style === 'dotted') {
|
|
12
|
+
return [width, width];
|
|
13
|
+
}
|
|
14
|
+
return [];
|
|
15
|
+
};
|
|
16
|
+
export const drawOutline = ({ ctx, rect, borderRadius, computedStyle, }) => {
|
|
17
|
+
const outlineWidth = parseOutlineWidth(computedStyle.outlineWidth);
|
|
18
|
+
const { outlineStyle } = computedStyle;
|
|
19
|
+
const outlineColor = computedStyle.outlineColor || 'black';
|
|
20
|
+
const outlineOffset = parseOutlineOffset(computedStyle.outlineOffset);
|
|
21
|
+
// Check if we have a visible outline
|
|
22
|
+
if (outlineWidth <= 0 ||
|
|
23
|
+
outlineStyle === 'none' ||
|
|
24
|
+
outlineStyle === 'hidden') {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Save original canvas state
|
|
28
|
+
const originalStrokeStyle = ctx.strokeStyle;
|
|
29
|
+
const originalLineWidth = ctx.lineWidth;
|
|
30
|
+
const originalLineDash = ctx.getLineDash();
|
|
31
|
+
ctx.beginPath();
|
|
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
|
+
// Draw continuous path with border radius
|
|
81
|
+
ctx.moveTo(outlineX + adjustedBorderRadius.topLeft.horizontal, outlineY);
|
|
82
|
+
// Top edge
|
|
83
|
+
ctx.lineTo(outlineX + outlineW - adjustedBorderRadius.topRight.horizontal, outlineY);
|
|
84
|
+
// Top-right corner
|
|
85
|
+
if (adjustedBorderRadius.topRight.horizontal > 0 ||
|
|
86
|
+
adjustedBorderRadius.topRight.vertical > 0) {
|
|
87
|
+
ctx.ellipse(outlineX + outlineW - adjustedBorderRadius.topRight.horizontal, outlineY + adjustedBorderRadius.topRight.vertical, adjustedBorderRadius.topRight.horizontal, adjustedBorderRadius.topRight.vertical, 0, -Math.PI / 2, 0);
|
|
88
|
+
}
|
|
89
|
+
// Right edge
|
|
90
|
+
ctx.lineTo(outlineX + outlineW, outlineY + outlineH - adjustedBorderRadius.bottomRight.vertical);
|
|
91
|
+
// Bottom-right corner
|
|
92
|
+
if (adjustedBorderRadius.bottomRight.horizontal > 0 ||
|
|
93
|
+
adjustedBorderRadius.bottomRight.vertical > 0) {
|
|
94
|
+
ctx.ellipse(outlineX + outlineW - adjustedBorderRadius.bottomRight.horizontal, outlineY + outlineH - adjustedBorderRadius.bottomRight.vertical, adjustedBorderRadius.bottomRight.horizontal, adjustedBorderRadius.bottomRight.vertical, 0, 0, Math.PI / 2);
|
|
95
|
+
}
|
|
96
|
+
// Bottom edge
|
|
97
|
+
ctx.lineTo(outlineX + adjustedBorderRadius.bottomLeft.horizontal, outlineY + outlineH);
|
|
98
|
+
// Bottom-left corner
|
|
99
|
+
if (adjustedBorderRadius.bottomLeft.horizontal > 0 ||
|
|
100
|
+
adjustedBorderRadius.bottomLeft.vertical > 0) {
|
|
101
|
+
ctx.ellipse(outlineX + adjustedBorderRadius.bottomLeft.horizontal, outlineY + outlineH - adjustedBorderRadius.bottomLeft.vertical, adjustedBorderRadius.bottomLeft.horizontal, adjustedBorderRadius.bottomLeft.vertical, 0, Math.PI / 2, Math.PI);
|
|
102
|
+
}
|
|
103
|
+
// Left edge
|
|
104
|
+
ctx.lineTo(outlineX, outlineY + adjustedBorderRadius.topLeft.vertical);
|
|
105
|
+
// Top-left corner
|
|
106
|
+
if (adjustedBorderRadius.topLeft.horizontal > 0 ||
|
|
107
|
+
adjustedBorderRadius.topLeft.vertical > 0) {
|
|
108
|
+
ctx.ellipse(outlineX + adjustedBorderRadius.topLeft.horizontal, outlineY + adjustedBorderRadius.topLeft.vertical, adjustedBorderRadius.topLeft.horizontal, adjustedBorderRadius.topLeft.vertical, 0, Math.PI, (Math.PI * 3) / 2);
|
|
109
|
+
}
|
|
110
|
+
ctx.closePath();
|
|
111
|
+
ctx.stroke();
|
|
112
|
+
// Restore original canvas state
|
|
113
|
+
ctx.strokeStyle = originalStrokeStyle;
|
|
114
|
+
ctx.lineWidth = originalLineWidth;
|
|
115
|
+
ctx.setLineDash(originalLineDash);
|
|
116
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getBoundingBoxIncludingOutline: (element: Element) => DOMRect;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const getBoundingBoxIncludingOutline = (element) => {
|
|
2
|
+
var _a, _b, _c, _d, _e;
|
|
3
|
+
const rect = element.getBoundingClientRect();
|
|
4
|
+
const shadow = (_a = element.shadowRoot) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
|
|
5
|
+
return new DOMRect(Math.min(rect.left, (_b = shadow === null || shadow === void 0 ? void 0 : shadow.left) !== null && _b !== void 0 ? _b : 0), Math.min(rect.top, (_c = shadow === null || shadow === void 0 ? void 0 : shadow.top) !== null && _c !== void 0 ? _c : 0), Math.max(rect.right, (_d = shadow === null || shadow === void 0 ? void 0 : shadow.right) !== null && _d !== void 0 ? _d : 0), Math.max(rect.bottom, (_e = shadow === null || shadow === void 0 ? void 0 : shadow.bottom) !== null && _e !== void 0 ? _e : 0));
|
|
6
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getPreTransformRect(targetRect: DOMRect, matrix: DOMMatrix): DOMRect;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function getPreTransformRect(targetRect, matrix) {
|
|
2
|
+
// 1. Determine the effective 2D transformation by transforming basis vectors
|
|
3
|
+
const origin = new DOMPoint(0, 0).matrixTransform(matrix);
|
|
4
|
+
const unitX = new DOMPoint(1, 0).matrixTransform(matrix);
|
|
5
|
+
const unitY = new DOMPoint(0, 1).matrixTransform(matrix);
|
|
6
|
+
// 2. Compute the 2D basis vectors after transformation
|
|
7
|
+
const basisX = { x: unitX.x - origin.x, y: unitX.y - origin.y };
|
|
8
|
+
const basisY = { x: unitY.x - origin.x, y: unitY.y - origin.y };
|
|
9
|
+
// 3. Build the effective 2D matrix and invert it
|
|
10
|
+
const effective2D = new DOMMatrix([
|
|
11
|
+
basisX.x,
|
|
12
|
+
basisX.y, // a, b (first column)
|
|
13
|
+
basisY.x,
|
|
14
|
+
basisY.y, // c, d (second column)
|
|
15
|
+
origin.x,
|
|
16
|
+
origin.y, // e, f (translation)
|
|
17
|
+
]);
|
|
18
|
+
const inverse2D = effective2D.inverse();
|
|
19
|
+
// 4. Transform target rect corners using the 2D inverse
|
|
20
|
+
const corners = [
|
|
21
|
+
new DOMPoint(targetRect.x, targetRect.y),
|
|
22
|
+
new DOMPoint(targetRect.x + targetRect.width, targetRect.y),
|
|
23
|
+
new DOMPoint(targetRect.x + targetRect.width, targetRect.y + targetRect.height),
|
|
24
|
+
new DOMPoint(targetRect.x, targetRect.y + targetRect.height),
|
|
25
|
+
];
|
|
26
|
+
const transformedCorners = corners.map((c) => c.matrixTransform(inverse2D));
|
|
27
|
+
// 5. Compute bounding box
|
|
28
|
+
const xs = transformedCorners.map((p) => p.x);
|
|
29
|
+
const ys = transformedCorners.map((p) => p.y);
|
|
30
|
+
return new DOMRect(Math.min(...xs), Math.min(...ys), Math.max(...xs) - Math.min(...xs), Math.max(...ys) - Math.min(...ys));
|
|
31
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { LogLevel } from 'remotion';
|
|
2
|
+
import type { InternalState } from '../internal-state';
|
|
3
|
+
export declare const handle3dTransform: ({ element, matrix, parentRect, context, logLevel, internalState, }: {
|
|
4
|
+
element: HTMLElement | SVGElement;
|
|
5
|
+
matrix: DOMMatrix;
|
|
6
|
+
parentRect: DOMRect;
|
|
7
|
+
context: OffscreenCanvasRenderingContext2D;
|
|
8
|
+
logLevel: LogLevel;
|
|
9
|
+
internalState: InternalState;
|
|
10
|
+
}) => Promise<void>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Internals } from 'remotion';
|
|
2
|
+
import { compose } from '../compose';
|
|
3
|
+
import { getBiggestBoundingClientRect } from '../get-biggest-bounding-client-rect';
|
|
4
|
+
import { getNarrowerRect } from './clamp-rect-to-parent-bounds';
|
|
5
|
+
import { getPreTransformRect } from './get-pretransform-rect';
|
|
6
|
+
import { transformIn3d } from './transform-in-3d';
|
|
7
|
+
export const handle3dTransform = async ({ element, matrix, parentRect, context, logLevel, internalState, }) => {
|
|
8
|
+
const unclampedBiggestBoundingClientRect = getBiggestBoundingClientRect(element);
|
|
9
|
+
const biggestPossiblePretransformRect = getPreTransformRect(parentRect, matrix);
|
|
10
|
+
const preTransformRect = getNarrowerRect({
|
|
11
|
+
firstRect: unclampedBiggestBoundingClientRect,
|
|
12
|
+
secondRect: biggestPossiblePretransformRect,
|
|
13
|
+
});
|
|
14
|
+
const start = Date.now();
|
|
15
|
+
const tempCanvas = new OffscreenCanvas(Math.ceil(preTransformRect.width), Math.ceil(preTransformRect.height));
|
|
16
|
+
await compose({
|
|
17
|
+
element,
|
|
18
|
+
context: tempCanvas.getContext('2d'),
|
|
19
|
+
logLevel,
|
|
20
|
+
parentRect: preTransformRect,
|
|
21
|
+
internalState,
|
|
22
|
+
});
|
|
23
|
+
const afterCompose = Date.now();
|
|
24
|
+
const { canvas: transformed, rect: transformedRect } = transformIn3d({
|
|
25
|
+
untransformedRect: preTransformRect,
|
|
26
|
+
matrix,
|
|
27
|
+
sourceCanvas: tempCanvas,
|
|
28
|
+
});
|
|
29
|
+
context.drawImage(transformed, transformedRect.x, transformedRect.y);
|
|
30
|
+
const afterDraw = Date.now();
|
|
31
|
+
Internals.Log.trace({
|
|
32
|
+
logLevel,
|
|
33
|
+
tag: '@remotion/web-renderer',
|
|
34
|
+
}, `Transforming element in 3D - canvas size: ${transformedRect.width}x${transformedRect.height} - compose: ${afterCompose - start}ms - draw: ${afterDraw - afterCompose}ms`);
|
|
35
|
+
internalState.add3DTransform({
|
|
36
|
+
canvasWidth: Math.ceil(transformedRect.width),
|
|
37
|
+
canvasHeight: Math.ceil(transformedRect.height),
|
|
38
|
+
});
|
|
39
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const hasTransformCssValue: (style: CSSStyleDeclaration) => boolean;
|
|
2
|
+
export declare const hasRotateCssValue: (style: CSSStyleDeclaration) => boolean;
|
|
3
|
+
export declare const hasScaleCssValue: (style: CSSStyleDeclaration) => boolean;
|
|
4
|
+
export declare const hasAnyTransformCssValue: (style: CSSStyleDeclaration) => boolean;
|
|
@@ -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,7 @@
|
|
|
1
|
+
import type { BorderRadiusCorners } from './border-radius';
|
|
2
|
+
export declare const setOverflowHidden: ({ ctx, rect, borderRadius, overflowHidden, }: {
|
|
3
|
+
ctx: OffscreenCanvasRenderingContext2D;
|
|
4
|
+
rect: DOMRect;
|
|
5
|
+
borderRadius: BorderRadiusCorners;
|
|
6
|
+
overflowHidden: boolean;
|
|
7
|
+
}) => () => void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { setBorderRadius } from './border-radius';
|
|
2
|
+
export const setOverflowHidden = ({ ctx, rect, borderRadius, overflowHidden, }) => {
|
|
3
|
+
if (!overflowHidden) {
|
|
4
|
+
return () => { };
|
|
5
|
+
}
|
|
6
|
+
return setBorderRadius({
|
|
7
|
+
ctx,
|
|
8
|
+
rect,
|
|
9
|
+
borderRadius,
|
|
10
|
+
forceClipEvenWhenZero: true,
|
|
11
|
+
});
|
|
12
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { LogLevel } from 'remotion';
|
|
2
|
+
import type { InternalState } from '../internal-state';
|
|
3
|
+
import type { DrawFn } from './drawn-fn';
|
|
4
|
+
export type ProcessNodeReturnValue = {
|
|
5
|
+
type: 'continue';
|
|
6
|
+
cleanupAfterChildren: () => void;
|
|
7
|
+
} | {
|
|
8
|
+
type: 'skip-children';
|
|
9
|
+
};
|
|
10
|
+
export declare const processNode: ({ element, context, draw, logLevel, parentRect, internalState, }: {
|
|
11
|
+
element: HTMLElement | SVGElement;
|
|
12
|
+
context: OffscreenCanvasRenderingContext2D;
|
|
13
|
+
draw: DrawFn;
|
|
14
|
+
logLevel: LogLevel;
|
|
15
|
+
parentRect: DOMRect;
|
|
16
|
+
internalState: InternalState;
|
|
17
|
+
}) => Promise<ProcessNodeReturnValue>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { calculateTransforms } from './calculate-transforms';
|
|
2
|
+
import { drawElement } from './draw-element';
|
|
3
|
+
import { handle3dTransform } from './handle-3d-transform';
|
|
4
|
+
export const processNode = async ({ element, context, draw, logLevel, parentRect, internalState, }) => {
|
|
5
|
+
const transforms = calculateTransforms({
|
|
6
|
+
element,
|
|
7
|
+
offsetLeft: parentRect.x,
|
|
8
|
+
offsetTop: parentRect.y,
|
|
9
|
+
});
|
|
10
|
+
const { totalMatrix, reset, dimensions, opacity, computedStyle } = transforms;
|
|
11
|
+
if (opacity === 0) {
|
|
12
|
+
reset();
|
|
13
|
+
return { type: 'continue', cleanupAfterChildren: () => { } };
|
|
14
|
+
}
|
|
15
|
+
if (dimensions.width <= 0 || dimensions.height <= 0) {
|
|
16
|
+
reset();
|
|
17
|
+
return { type: 'continue', cleanupAfterChildren: () => { } };
|
|
18
|
+
}
|
|
19
|
+
if (!totalMatrix.is2D) {
|
|
20
|
+
await handle3dTransform({
|
|
21
|
+
element,
|
|
22
|
+
matrix: totalMatrix,
|
|
23
|
+
parentRect,
|
|
24
|
+
context,
|
|
25
|
+
logLevel,
|
|
26
|
+
internalState,
|
|
27
|
+
});
|
|
28
|
+
reset();
|
|
29
|
+
return { type: 'skip-children' };
|
|
30
|
+
}
|
|
31
|
+
const { cleanupAfterChildren } = await drawElement({
|
|
32
|
+
rect: new DOMRect(dimensions.left - parentRect.x, dimensions.top - parentRect.y, dimensions.width, dimensions.height),
|
|
33
|
+
computedStyle,
|
|
34
|
+
context,
|
|
35
|
+
draw,
|
|
36
|
+
opacity,
|
|
37
|
+
totalMatrix,
|
|
38
|
+
});
|
|
39
|
+
reset();
|
|
40
|
+
return { type: 'continue', cleanupAfterChildren };
|
|
41
|
+
};
|
|
@@ -4,7 +4,7 @@ import { findLineBreaks } from './find-line-breaks.text';
|
|
|
4
4
|
import { getCollapsedText } from './get-collapsed-text';
|
|
5
5
|
export const drawText = (span) => {
|
|
6
6
|
const drawFn = ({ dimensions: rect, computedStyle, contextToDraw }) => {
|
|
7
|
-
const { fontFamily, fontSize, fontWeight, color,
|
|
7
|
+
const { fontFamily, fontSize, fontWeight, color, direction, writingMode, letterSpacing, textTransform, } = computedStyle;
|
|
8
8
|
const isVertical = writingMode !== 'horizontal-tb';
|
|
9
9
|
if (isVertical) {
|
|
10
10
|
// TODO: Only warn once per render.
|
|
@@ -15,16 +15,13 @@ export const drawText = (span) => {
|
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
17
|
contextToDraw.save();
|
|
18
|
-
|
|
18
|
+
const fontSizePx = parseFloat(fontSize);
|
|
19
|
+
contextToDraw.font = `${fontWeight} ${fontSizePx}px ${fontFamily}`;
|
|
19
20
|
contextToDraw.fillStyle = color;
|
|
20
21
|
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
22
|
const isRTL = direction === 'rtl';
|
|
26
23
|
contextToDraw.textAlign = isRTL ? 'right' : 'left';
|
|
27
|
-
contextToDraw.textBaseline = '
|
|
24
|
+
contextToDraw.textBaseline = 'alphabetic';
|
|
28
25
|
const originalText = span.textContent;
|
|
29
26
|
const collapsedText = getCollapsedText(span);
|
|
30
27
|
const transformedText = applyTextTransform(collapsedText, textTransform);
|
|
@@ -33,8 +30,9 @@ export const drawText = (span) => {
|
|
|
33
30
|
const xPosition = isRTL ? rect.right : rect.left;
|
|
34
31
|
const lines = findLineBreaks(span, isRTL);
|
|
35
32
|
let offsetTop = 0;
|
|
33
|
+
const { fontBoundingBoxAscent } = contextToDraw.measureText(lines[0].text);
|
|
36
34
|
for (const line of lines) {
|
|
37
|
-
contextToDraw.fillText(line.text, xPosition + line.offsetHorizontal, rect.top +
|
|
35
|
+
contextToDraw.fillText(line.text, xPosition + line.offsetHorizontal, rect.top + offsetTop + fontBoundingBoxAscent);
|
|
38
36
|
offsetTop += line.offsetTop;
|
|
39
37
|
}
|
|
40
38
|
span.textContent = originalText;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type { LogLevel } from 'remotion';
|
|
2
|
+
import type { InternalState } from '../../internal-state';
|
|
3
|
+
import type { ProcessNodeReturnValue } from '../process-node';
|
|
4
|
+
export declare const handleTextNode: ({ node, context, logLevel, parentRect, internalState, }: {
|
|
3
5
|
node: Text;
|
|
4
6
|
context: OffscreenCanvasRenderingContext2D;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
logLevel: LogLevel;
|
|
8
|
+
parentRect: DOMRect;
|
|
9
|
+
internalState: InternalState;
|
|
10
|
+
}) => Promise<ProcessNodeReturnValue>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { processNode } from '../process-node';
|
|
2
2
|
import { drawText } from './draw-text';
|
|
3
|
-
export const handleTextNode = async ({ node, context,
|
|
3
|
+
export const handleTextNode = async ({ node, context, logLevel, parentRect, internalState, }) => {
|
|
4
4
|
const span = document.createElement('span');
|
|
5
5
|
const parent = node.parentNode;
|
|
6
6
|
if (!parent) {
|
|
@@ -8,12 +8,13 @@ export const handleTextNode = async ({ node, context, offsetLeft, offsetTop, })
|
|
|
8
8
|
}
|
|
9
9
|
parent.insertBefore(span, node);
|
|
10
10
|
span.appendChild(node);
|
|
11
|
-
const value = await
|
|
11
|
+
const value = await processNode({
|
|
12
12
|
context,
|
|
13
13
|
element: span,
|
|
14
14
|
draw: drawText(span),
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
logLevel,
|
|
16
|
+
parentRect,
|
|
17
|
+
internalState,
|
|
17
18
|
});
|
|
18
19
|
// Undo the layout manipulation
|
|
19
20
|
parent.insertBefore(node, span);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export declare const transformIn3d: ({
|
|
2
|
-
|
|
3
|
-
canvasHeight: number;
|
|
4
|
-
offsetLeft: number;
|
|
5
|
-
offsetTop: number;
|
|
1
|
+
export declare const transformIn3d: ({ matrix, sourceCanvas, untransformedRect, }: {
|
|
2
|
+
untransformedRect: DOMRect;
|
|
6
3
|
matrix: DOMMatrix;
|
|
7
|
-
sourceCanvas:
|
|
8
|
-
}) =>
|
|
4
|
+
sourceCanvas: OffscreenCanvas;
|
|
5
|
+
}) => {
|
|
6
|
+
canvas: OffscreenCanvas;
|
|
7
|
+
rect: DOMRect;
|
|
8
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { transformDOMRect } from './transform-rect-with-matrix';
|
|
1
2
|
function compileShader(shaderGl, source, type) {
|
|
2
3
|
const shader = shaderGl.createShader(type);
|
|
3
4
|
if (!shader) {
|
|
@@ -22,6 +23,12 @@ const createHelperCanvas = ({ canvasWidth, canvasHeight, }) => {
|
|
|
22
23
|
helperCanvas.gl.clear(helperCanvas.gl.COLOR_BUFFER_BIT);
|
|
23
24
|
return helperCanvas;
|
|
24
25
|
}
|
|
26
|
+
if (helperCanvas) {
|
|
27
|
+
helperCanvas.gl.deleteProgram(helperCanvas.program);
|
|
28
|
+
helperCanvas.gl.deleteShader(helperCanvas.vertexShader);
|
|
29
|
+
helperCanvas.gl.deleteShader(helperCanvas.fragmentShader);
|
|
30
|
+
helperCanvas = null;
|
|
31
|
+
}
|
|
25
32
|
const canvas = new OffscreenCanvas(canvasWidth, canvasHeight);
|
|
26
33
|
const gl = canvas.getContext('webgl');
|
|
27
34
|
if (!gl) {
|
|
@@ -67,11 +74,18 @@ const createHelperCanvas = ({ canvasWidth, canvasHeight, }) => {
|
|
|
67
74
|
// Enable blending for transparency
|
|
68
75
|
gl.enable(gl.BLEND);
|
|
69
76
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
70
|
-
helperCanvas = { canvas, gl, program };
|
|
77
|
+
helperCanvas = { canvas, gl, program, vertexShader, fragmentShader };
|
|
71
78
|
return helperCanvas;
|
|
72
79
|
};
|
|
73
|
-
export const transformIn3d = ({
|
|
74
|
-
const
|
|
80
|
+
export const transformIn3d = ({ matrix, sourceCanvas, untransformedRect, }) => {
|
|
81
|
+
const rectAfterTransforms = transformDOMRect({
|
|
82
|
+
rect: untransformedRect,
|
|
83
|
+
matrix,
|
|
84
|
+
});
|
|
85
|
+
const { canvas, gl, program } = createHelperCanvas({
|
|
86
|
+
canvasWidth: Math.ceil(rectAfterTransforms.width),
|
|
87
|
+
canvasHeight: Math.ceil(rectAfterTransforms.height),
|
|
88
|
+
});
|
|
75
89
|
const vertexBuffer = gl.createBuffer();
|
|
76
90
|
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
|
|
77
91
|
// Create a quad (two triangles) with texture coordinates
|
|
@@ -79,13 +93,13 @@ export const transformIn3d = ({ canvasWidth, canvasHeight, matrix, sourceCanvas,
|
|
|
79
93
|
const vertices = new Float32Array([
|
|
80
94
|
// Position (x, y) + TexCoord (u, v)
|
|
81
95
|
// First:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
untransformedRect.x, untransformedRect.y, 0, 0, // bottom-left
|
|
97
|
+
untransformedRect.x + untransformedRect.width, untransformedRect.y, 1, 0, // bottom-right
|
|
98
|
+
untransformedRect.x, untransformedRect.y + untransformedRect.height, 0, 1, // top-left
|
|
85
99
|
// Second:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
100
|
+
untransformedRect.x, untransformedRect.y + untransformedRect.height, 0, 1, // top-left
|
|
101
|
+
untransformedRect.x + untransformedRect.width, untransformedRect.y, 1, 0, // bottom-right
|
|
102
|
+
untransformedRect.x + untransformedRect.width, untransformedRect.y + untransformedRect.height, 1, 1, // top-right
|
|
89
103
|
]);
|
|
90
104
|
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
|
|
91
105
|
const aPosition = gl.getAttribLocation(program, 'aPosition');
|
|
@@ -109,7 +123,7 @@ export const transformIn3d = ({ canvasWidth, canvasHeight, matrix, sourceCanvas,
|
|
|
109
123
|
// The transform matrix
|
|
110
124
|
const transformMatrix = matrix.toFloat32Array();
|
|
111
125
|
const zScale = 1000000000; // By default infinite in chrome
|
|
112
|
-
// Create orthographic projection matrix for pixel coordinates
|
|
126
|
+
// Create orthographic projection matrix for pixel coordinates with offset
|
|
113
127
|
const projectionMatrix = new Float32Array([
|
|
114
128
|
2 / canvas.width,
|
|
115
129
|
0,
|
|
@@ -123,8 +137,8 @@ export const transformIn3d = ({ canvasWidth, canvasHeight, matrix, sourceCanvas,
|
|
|
123
137
|
0,
|
|
124
138
|
-2 / zScale,
|
|
125
139
|
0,
|
|
126
|
-
-1,
|
|
127
|
-
1,
|
|
140
|
+
-1 + (2 * -rectAfterTransforms.x) / canvas.width,
|
|
141
|
+
1 - (2 * -rectAfterTransforms.y) / canvas.height,
|
|
128
142
|
0,
|
|
129
143
|
1,
|
|
130
144
|
]);
|
|
@@ -138,5 +152,5 @@ export const transformIn3d = ({ canvasWidth, canvasHeight, matrix, sourceCanvas,
|
|
|
138
152
|
// Clean up resources to prevent leaks and ensure clean state for reuse
|
|
139
153
|
gl.deleteTexture(texture);
|
|
140
154
|
gl.deleteBuffer(vertexBuffer);
|
|
141
|
-
return canvas;
|
|
155
|
+
return { canvas, rect: rectAfterTransforms };
|
|
142
156
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function transformDOMRect({ rect, matrix, }) {
|
|
2
|
+
// Get all four corners of the rectangle
|
|
3
|
+
const topLeft = new DOMPointReadOnly(rect.left, rect.top);
|
|
4
|
+
const topRight = new DOMPointReadOnly(rect.right, rect.top);
|
|
5
|
+
const bottomLeft = new DOMPointReadOnly(rect.left, rect.bottom);
|
|
6
|
+
const bottomRight = new DOMPointReadOnly(rect.right, rect.bottom);
|
|
7
|
+
// Transform all corners
|
|
8
|
+
const transformedTopLeft = topLeft.matrixTransform(matrix);
|
|
9
|
+
const transformedTopRight = topRight.matrixTransform(matrix);
|
|
10
|
+
const transformedBottomLeft = bottomLeft.matrixTransform(matrix);
|
|
11
|
+
const transformedBottomRight = bottomRight.matrixTransform(matrix);
|
|
12
|
+
// Find the bounding box of the transformed points
|
|
13
|
+
const minX = Math.min(transformedTopLeft.x / transformedTopLeft.w, transformedTopRight.x / transformedTopRight.w, transformedBottomLeft.x / transformedBottomLeft.w, transformedBottomRight.x / transformedBottomRight.w);
|
|
14
|
+
const maxX = Math.max(transformedTopLeft.x / transformedTopLeft.w, transformedTopRight.x / transformedTopRight.w, transformedBottomLeft.x / transformedBottomLeft.w, transformedBottomRight.x / transformedBottomRight.w);
|
|
15
|
+
const minY = Math.min(transformedTopLeft.y / transformedTopLeft.w, transformedTopRight.y / transformedTopRight.w, transformedBottomLeft.y / transformedBottomLeft.w, transformedBottomRight.y / transformedBottomRight.w);
|
|
16
|
+
const maxY = Math.max(transformedTopLeft.y / transformedTopLeft.w, transformedTopRight.y / transformedTopRight.w, transformedBottomLeft.y / transformedBottomLeft.w, transformedBottomRight.y / transformedBottomRight.w);
|
|
17
|
+
// Create a new DOMRect from the bounding box
|
|
18
|
+
return new DOMRect(minX, minY, maxX - minX, maxY - minY);
|
|
19
|
+
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
export const turnSvgIntoDrawable = (svg) => {
|
|
2
|
+
const { fill, color } = getComputedStyle(svg);
|
|
2
3
|
const originalTransform = svg.style.transform;
|
|
3
4
|
const originalTransformOrigin = svg.style.transformOrigin;
|
|
4
5
|
const originalMarginLeft = svg.style.marginLeft;
|
|
5
6
|
const originalMarginRight = svg.style.marginRight;
|
|
6
7
|
const originalMarginTop = svg.style.marginTop;
|
|
7
8
|
const originalMarginBottom = svg.style.marginBottom;
|
|
9
|
+
const originalFill = svg.style.fill;
|
|
10
|
+
const originalColor = svg.style.color;
|
|
8
11
|
svg.style.transform = 'none';
|
|
9
12
|
svg.style.transformOrigin = '';
|
|
10
13
|
// Margins were already included in the positioning calculation,
|
|
@@ -13,6 +16,8 @@ export const turnSvgIntoDrawable = (svg) => {
|
|
|
13
16
|
svg.style.marginRight = '0';
|
|
14
17
|
svg.style.marginTop = '0';
|
|
15
18
|
svg.style.marginBottom = '0';
|
|
19
|
+
svg.style.fill = fill;
|
|
20
|
+
svg.style.color = color;
|
|
16
21
|
const svgData = new XMLSerializer().serializeToString(svg);
|
|
17
22
|
svg.style.marginLeft = originalMarginLeft;
|
|
18
23
|
svg.style.marginRight = originalMarginRight;
|
|
@@ -20,6 +25,8 @@ export const turnSvgIntoDrawable = (svg) => {
|
|
|
20
25
|
svg.style.marginBottom = originalMarginBottom;
|
|
21
26
|
svg.style.transform = originalTransform;
|
|
22
27
|
svg.style.transformOrigin = originalTransformOrigin;
|
|
28
|
+
svg.style.fill = originalFill;
|
|
29
|
+
svg.style.color = originalColor;
|
|
23
30
|
return new Promise((resolve, reject) => {
|
|
24
31
|
const image = new Image();
|
|
25
32
|
const url = `data:image/svg+xml;base64,${btoa(svgData)}`;
|