Skip to content

Conversation

@JonnyBurger
Copy link
Member

No description provided.

@vercel
Copy link
Contributor

vercel bot commented Nov 1, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
bugs Ready Ready Preview Comment Nov 5, 2025 9:19am
remotion Error Error Nov 5, 2025 9:19am

Comment on lines 6 to 10
const [x, y] = transformOrigin.split(' ');

return {
x: parseFloat(x),
y: parseFloat(y),
Copy link
Contributor

@vercel vercel bot Nov 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parseTransformOrigin function only splits on spaces and assumes exactly two values, but CSS transform-origin supports many formats including single values, keywords, and complex values with spaces (e.g., calc(50% + 10px) 50%), causing it to return NaN for many valid inputs.

View Details
📝 Patch Details
diff --git a/packages/web-renderer/src/parse-transform-origin.ts b/packages/web-renderer/src/parse-transform-origin.ts
index d0922da030..040b2da251 100644
--- a/packages/web-renderer/src/parse-transform-origin.ts
+++ b/packages/web-renderer/src/parse-transform-origin.ts
@@ -3,10 +3,100 @@ export const parseTransformOrigin = (transformOrigin: string) => {
 		return null;
 	}
 
-	const [x, y] = transformOrigin.split(' ');
+	// Handle calc() expressions and other complex values by not splitting on spaces inside parentheses
+	const values = parseTransformOriginValues(transformOrigin.trim());
+
+	if (values.length === 0) {
+		return null;
+	}
+
+	// Single value: applies to horizontal, vertical defaults to center (50%)
+	if (values.length === 1) {
+		const x = parseTransformOriginValue(values[0]);
+		return {
+			x: x !== null ? x : 50, // Default to center if parsing fails
+			y: 50, // CSS spec: single value sets horizontal, vertical defaults to center
+		};
+	}
+
+	// Two values: first is horizontal, second is vertical
+	const x = parseTransformOriginValue(values[0]);
+	const y = parseTransformOriginValue(values[1]);
 
 	return {
-		x: parseFloat(x),
-		y: parseFloat(y),
+		x: x !== null ? x : 50,
+		y: y !== null ? y : 50,
 	};
 };
+
+// Parse space-separated values while respecting parentheses (for calc(), etc.)
+function parseTransformOriginValues(str: string): string[] {
+	const values: string[] = [];
+	let current = '';
+	let parenDepth = 0;
+
+	for (let i = 0; i < str.length; i++) {
+		const char = str[i];
+
+		if (char === '(') {
+			parenDepth++;
+			current += char;
+		} else if (char === ')') {
+			parenDepth--;
+			current += char;
+		} else if (char === ' ' && parenDepth === 0) {
+			if (current.trim()) {
+				values.push(current.trim());
+				current = '';
+			}
+		} else {
+			current += char;
+		}
+	}
+
+	if (current.trim()) {
+		values.push(current.trim());
+	}
+
+	return values;
+}
+
+// Parse individual transform-origin value (keyword, percentage, length)
+function parseTransformOriginValue(value: string): number | null {
+	// Handle keywords
+	switch (value.toLowerCase()) {
+		case 'left':
+		case 'top':
+			return 0;
+		case 'center':
+			return 50;
+		case 'right':
+		case 'bottom':
+			return 100;
+	}
+
+	// Handle percentages
+	if (value.endsWith('%')) {
+		const num = parseFloat(value.slice(0, -1));
+		return isNaN(num) ? null : num;
+	}
+
+	// Handle pixel values (convert to percentage - this is approximate)
+	// Note: Without element dimensions, we can't convert px to % accurately
+	// For now, treat px values as if they were percentages for basic functionality
+	if (value.endsWith('px')) {
+		const num = parseFloat(value.slice(0, -2));
+		return isNaN(num) ? null : num;
+	}
+
+	// Handle calc() and other complex expressions
+	// For calc() expressions, we can't evaluate them without context
+	// Return 50% (center) as a reasonable fallback
+	if (value.startsWith('calc(')) {
+		return 50;
+	}
+
+	// Try parsing as a raw number
+	const num = parseFloat(value);
+	return isNaN(num) ? null : num;
+}

Analysis

parseTransformOrigin function returns NaN for valid CSS transform-origin values

What fails: parseTransformOrigin() in packages/web-renderer/src/parse-transform-origin.ts produces NaN values for valid CSS transform-origin inputs like single values, keywords, and calc() expressions

How to reproduce:

// Test cases that fail with current implementation:
parseTransformOrigin("center")                    // Returns {x: NaN, y: NaN}
parseTransformOrigin("50%")                       // Returns {x: 50, y: NaN}  
parseTransformOrigin("calc(50% + 10px) 50%")     // Returns {x: NaN, y: NaN}
parseTransformOrigin("left top")                  // Returns {x: NaN, y: NaN}

Result: imageBitmap.transformOrigin contains NaN values in canvas drawing code (compose.ts lines 80-81, 92-93), causing incorrect transform origin calculations and visual rendering errors

Expected: Should parse all valid CSS transform-origin syntax per MDN specification - single values, keywords (left/center/right/top/bottom), percentages, and calc() expressions

@JonnyBurger JonnyBurger changed the title @remotion/web-renderer: Include SVG @remotion/web-renderer: Include SVG, visual testing Nov 4, 2025
visually, the tests are differering on ubuntu and macos. since everybody uses macos to develop, we should use macos to create references.

tests only run on node >= 18, but we also test node 16. so this avoids the tests being run on anything else than macos essentially
@JonnyBurger JonnyBurger merged commit 185f8fc into main Nov 5, 2025
12 of 14 checks passed
@JonnyBurger JonnyBurger deleted the web-renderer-svg branch November 5, 2025 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants