Browse Source

More strict about units. Numbers, lengths, angles and percentages are now separated. Lengths require a unit, except for "0".
Mixing units during interpolation now works properly, e.g. percentage and length.

Michael 7 years ago
parent
commit
b6927b4e0c

+ 0 - 7
Include/Rocket/Core/Decorator.h

@@ -102,13 +102,6 @@ protected:
 	/// @return The texture at the appropriate index, or NULL if the index was invalid.
 	const Texture* GetTexture(int index = 0) const;
 
-	/// Returns the floating-point value of a numerical property from a dictionary of properties, resolving it
-	/// against a base value if it is a relative property.
-	/// @param[in] properties The user-supplied dictionary of properties.
-	/// @param[in] name The name of the desired property. This must be a numerical property.
-	/// @return The fully-resolved value of the property, or 0 if an error occured.
-	float ResolveProperty(const PropertyDictionary& properties, const String& name, float base_value) const;
-
 private:
 	DecoratorInstancer* instancer;
 

+ 2 - 0
Include/Rocket/Core/Element.h

@@ -240,6 +240,8 @@ public:
 	/// Returns local 'width' and 'height' properties from element's style or local cache,
 	/// ignoring default values.
 	void GetLocalDimensionProperties(const Property **width, const Property **height);
+	/// Returns the size of the containing block.
+	Vector2f GetContainingBlock();
 	/// Returns 'overflow' properties' values from element's style or local cache.
 	void GetOverflow(int *overflow_x, int *overflow_y);
 	/// Returns 'position' property value from element's style or local cache.

+ 9 - 4
Include/Rocket/Core/Property.h

@@ -57,14 +57,14 @@ public:
 		DEG = 1 << 5,				// number suffixed by 'deg'; fetch as < float >
 		RAD = 1 << 6,				// number suffixed by 'rad'; fetch as < float >
 		COLOUR = 1 << 7,			// colour; fetch as < Colourb >
-		ABSOLUTE_UNIT = NUMBER | PX | DEG | RAD | COLOUR,
+		DP = 1 << 11,				// density-independent pixel; number suffixed by 'dp'; fetch as < float >
+		ABSOLUTE_UNIT = NUMBER | PX | DP | DEG | RAD | COLOUR,
 
 		// Relative values.
 		EM = 1 << 8,				// number suffixed by 'em'; fetch as < float >
 		PERCENT = 1 << 9,			// number suffixed by '%'; fetch as < float >
 		REM = 1 << 10,				// number suffixed by 'rem'; fetch as < float >
-		DP = 1 << 11,				// density-independent pixel; number suffixed by 'dp'; fetch as < float >
-		RELATIVE_UNIT = EM | REM | PERCENT | DP,
+		RELATIVE_UNIT = EM | REM | PERCENT,
 
 		// Values based on pixels-per-inch.
 		INCH = 1 << 12,				// number suffixed by 'in'; fetch as < float >
@@ -74,7 +74,12 @@ public:
 		PC = 1 << 16,				// number suffixed by 'pc'; fetch as < float >
 		PPI_UNIT = INCH | CM | MM | PT | PC,
 
-		TRANSFORM = 1 << 17			// transform; fetch as < TransformRef >
+		TRANSFORM = 1 << 17,			// transform; fetch as < TransformRef >
+
+		LENGTH = PX | DP | PPI_UNIT | EM | REM,
+		LENGTH_PERCENT = LENGTH | PERCENT,
+		NUMBER_LENGTH_PERCENT = NUMBER | LENGTH | PERCENT,
+		ANGLE = NUMBER | DEG | RAD
 	};
 
 	Property();

+ 10 - 0
Include/Rocket/Core/PropertyDefinition.h

@@ -39,6 +39,8 @@ namespace Core {
 	@author Peter Curry
  */
 
+enum class RelativeTarget { None, ContainingBlockWidth, ContainingBlockHeight, FontSize, ParentFontSize, LineHeight };
+
 class ROCKETCORE_API PropertyDefinition
 {
 public:
@@ -51,6 +53,9 @@ public:
 	/// @return This property definition.
 	PropertyDefinition& AddParser(const String& parser_name, const String& parser_parameters = "");
 
+	/// Set target for relative units when resolving sizes such as percentages.
+	PropertyDefinition& SetRelativeTarget(RelativeTarget relative_target);
+
 	/// Called when parsing a RCSS declaration.
 	/// @param property[out] The property to set the parsed value onto.
 	/// @param value[in] The raw value defined for this property.
@@ -71,6 +76,9 @@ public:
 	/// Returns the default defined for this property.
 	const Property* GetDefaultValue() const;
 
+	/// Returns the target for resolving values with percent and possibly number units.
+	RelativeTarget GetRelativeTarget() const;
+
 private:
 	Property default_value;
 	bool inherited;
@@ -83,6 +91,8 @@ private:
 	};
 
 	std::vector< ParserState > parsers;
+
+	RelativeTarget relative_target;
 };
 
 }

+ 6 - 6
Samples/assets/invader.rcss

@@ -3,7 +3,7 @@ body
 	font-family: Delicious;
 	font-weight: normal;
 	font-style: normal;
-	font-size: 15;
+	font-size: 15px;
 	color: white;
 }
 
@@ -52,7 +52,7 @@ div#title_bar span
 	padding-top: 17px;
 	padding-bottom: 48px;
 
-	font-size: 22;
+	font-size: 22px;
 	font-weight: bold;
 
 	outline-font-effect: outline;
@@ -109,7 +109,7 @@ h1
 	margin-bottom: 0.4em;
 
 	text-align: left;
-	font-size: 16;
+	font-size: 16px;
 	font-weight: bold;
 
 	shadow-font-effect: shadow;
@@ -144,7 +144,7 @@ input.submit
 	padding-top: 8px;
 	vertical-align: -18px;
 
-	font-size: 18;
+	font-size: 18px;
 	text-align: center;
 	tab-index: auto;
 
@@ -191,7 +191,7 @@ select,
 dataselect
 {
 	color: black;
-	font-size: 13;
+	font-size: 13px;
 }
 
 datagrid input.text
@@ -205,7 +205,7 @@ datagrid input.text
 	border-color: black;
 	background-color: white;
 
-	font-size: 15;
+	font-size: 15px;
 	
 	background-decorator: none;
 }

+ 6 - 5
Samples/basic/animation/data/animation.rml

@@ -10,7 +10,7 @@
 			width: 1400px;
 			height: 750px;
 			perspective: 3000px;
-			opacity: 0;
+			/*opacity: 0;*/
 		}
 		button {
 			margin-top: 50px;
@@ -29,7 +29,7 @@
 		#start_game 
 		{
 			opacity: 0.8;
-			transform: rotate(-350) translateX(100) scale(1.2);
+			transform: rotate(370deg) translateX(100px) scale(1.2);
 			transform-origin: 30% 80% 0;
 		}
 
@@ -38,9 +38,9 @@
 		}
 		#exit {
 			transform: rotate(45deg);
+			transform-origin: 50% 50% 0;
 		}
 		
-		
 		div.container
 		{
 			margin-top: 15px;
@@ -62,7 +62,7 @@
 		
 		/* -- TRANSFORM TESTS */
 		#generic {
-			transform: translateX(100) rotate3d(1.0, 0, 1.0, 0deg);
+			transform: translateX(100px) rotate3d(1.0, 0, 1.0, 0deg);
 		}
 		#combine {
 			transform: rotate(45deg);
@@ -75,7 +75,7 @@
 		
 		/* -- MIXED UNITS TESTS */
 		#abs_rel {
-			margin: auto;
+			margin: 0;
 			margin-left: 50px;
 		}
 		#abs_rel_transform {
@@ -90,6 +90,7 @@
 
 <body template="window">
 <div style="width: 30%; height: 80%; position: absolute;">
+	<div style="font-size: 0.85em; text-align: left;" id="fps"></div>
 	<button id="start_game">Start Game</button><br />
 	<button id="high_scores" onkeydown="hello">High Scores</button><br />
 	<button id="options">Options</button><br />

+ 22 - 12
Samples/basic/animation/src/main.cpp

@@ -37,7 +37,6 @@
 
 
 // Animations TODO:
-//  - Some primitives should be converted to DecomposedMatrix4 when adding key: Matrix3d, Matrix2d, Perspective.
 //  - Update transform animations / resolve keys again when parent box size changes.
 //  - RCSS support? Both @keyframes and transition, maybe.
 //  - Profiling
@@ -57,14 +56,14 @@ public:
 				document->GetElementById("title")->SetInnerRML(title);
 				document->SetProperty("left", Property(position.x, Property::PX));
 				document->SetProperty("top", Property(position.y, Property::PX));
-				document->Animate("opacity", Property(1.0f, Property::NUMBER), 0.8f, Tween{Tween::Quadratic, Tween::Out}, 1, false, 0.0f);
+				//document->Animate("opacity", Property(1.0f, Property::NUMBER), 0.8f, Tween{Tween::Quadratic, Tween::Out}, 1, false, 0.0f);
 			}
 
 			// Button fun
 			{
 				auto el = document->GetElementById("start_game");
 				PropertyDictionary pd;
-				StyleSheetSpecification::ParsePropertyDeclaration(pd, "transform", "rotate(10) translateX(100)");
+				StyleSheetSpecification::ParsePropertyDeclaration(pd, "transform", "rotate(10) translateX(100px)");
 				auto p = pd.GetProperty("transform");
 				el->Animate("transform", *p, 1.8f, Tween{ Tween::Elastic, Tween::InOut }, -1, true);
 
@@ -115,16 +114,16 @@ public:
 			// Mixed units tests
 			{
 				auto el = document->GetElementById("abs_rel");
-				el->Animate("margin-left", Property(100.f, Property::PERCENT), 2.0f, Tween{}, -1, true);
+				el->Animate("margin-left", Property(100.f, Property::PERCENT), 1.5f, Tween{}, -1, true);
 			}
 			{
 				auto el = document->GetElementById("abs_rel_transform");
 				auto p = Transform::MakeProperty({ Transforms::TranslateX{0, Property::PX} });
-				el->Animate("transform", p, 2.0f, Tween{}, -1, true);
+				el->Animate("transform", p, 1.5f, Tween{}, -1, true);
 			}
 			{
 				auto el = document->GetElementById("text_align");
-				el->Animate("text-align", Property(3, Property::KEYWORD), 2.0f, Tween{}, -1, true);
+				//el->Animate("text-align", Property(3, Property::KEYWORD), 2.0f, Tween{}, -1, true);
 			}
 
 			document->Show();
@@ -173,6 +172,7 @@ void GameLoop()
 	static float t_prev = 0.0f;
 	float t = Shell::GetElapsedTime();
 	float dt = t - t_prev;
+	t_prev = t;
 	//if(dt > 1.0f)
 	if(nudge)
 	{
@@ -186,6 +186,13 @@ void GameLoop()
 		Rocket::Core::Log::Message(Rocket::Core::Log::LT_INFO, "margin-left: '%f'   abs: %f.", ff, f_left);
 		nudge = 0;
 	}
+
+	if (window)
+	{
+		auto el = window->GetDocument()->GetElementById("fps");
+		float fps = 1.0f / dt;
+		el->SetInnerRML(Rocket::Core::String{ 20, "FPS: %f", fps });
+	}
 	//static int f_prev = 0.0f;
 	//int df = f - f_prev;
 	//f_prev = f;
@@ -205,26 +212,25 @@ public:
 		if(value == "exit")
 			Shell::RequestExit();
 
-		if (event == "keydown" ||
-			event == "keyup")
+		if (event == "keydown")
 		{
 			bool key_down = event == "keydown";
 			Rocket::Core::Input::KeyIdentifier key_identifier = (Rocket::Core::Input::KeyIdentifier) event.GetParameter< int >("key_identifier", 0);
 
-			if (key_identifier == Rocket::Core::Input::KI_SPACE && key_down)
+			if (key_identifier == Rocket::Core::Input::KI_SPACE)
 			{
 				pause_loop = !pause_loop;
 			}
-			else if (key_identifier == Rocket::Core::Input::KI_RETURN && key_down)
+			else if (key_identifier == Rocket::Core::Input::KI_RETURN)
 			{
 				pause_loop = true;
 				single_loop = true;
 			}
-			else if (key_identifier == Rocket::Core::Input::KI_OEM_PLUS && key_down)
+			else if (key_identifier == Rocket::Core::Input::KI_OEM_PLUS)
 			{
 				nudge = 1;
 			}
-			else if (key_identifier == Rocket::Core::Input::KI_OEM_MINUS && key_down)
+			else if (key_identifier == Rocket::Core::Input::KI_OEM_MINUS)
 			{
 				nudge = -1;
 			}
@@ -232,6 +238,10 @@ public:
 			{
 				Shell::RequestExit();
 			}
+			else if (key_identifier == Rocket::Core::Input::KI_F8)
+			{
+				Rocket::Debugger::SetVisible(!Rocket::Debugger::IsVisible());
+			}
 		}
 	}
 

+ 1 - 1
Samples/invaders/data/game.rml

@@ -27,7 +27,7 @@
 				padding: 9px 0px 0px 65px;
 				margin: 0px 20px;
 
-				font-size: 20;
+				font-size: 20px;
 
 				background-decorator: tiled-horizontal;
 				background-left-image: ../../assets/invader.tga 147px 55px 229px 0px;

+ 0 - 37
Source/Core/Decorator.cpp

@@ -103,43 +103,6 @@ const Texture* Decorator::GetTexture(int index) const
 	return &(textures[index]);
 }
 
-// Returns the floating-point value of a numerical property from a dictionary of properties.
-float Decorator::ResolveProperty(const PropertyDictionary& properties, const String& name, float base_value) const
-{
-	const Property* property = properties.GetProperty(name);
-	if (property == NULL)
-	{
-		ROCKET_ERROR;
-		return 0;
-	}
-
-	// Need to include em!
-	if (property->unit & Property::PERCENT)
-		return base_value * property->value.Get< float >() * 0.01f;
-
-	if (property->unit & Property::NUMBER || property->unit & Property::PX)
-		return property->value.Get< float >();
-	    
-    // Values based on pixels-per-inch.
-	if (property->unit & Property::PPI_UNIT)
-	{
-		float inch = property->value.Get< float >() * GetRenderInterface()->GetPixelsPerInch();
-
-		if (property->unit & Property::INCH) // inch
-			return inch;
-		if (property->unit & Property::CM) // centimeter
-			return inch / 2.54f;
-		if (property->unit & Property::MM) // millimeter
-			return inch / 25.4f;
-		if (property->unit & Property::PT) // point
-			return inch / 72.0f;
-		if (property->unit & Property::PC) // pica
-			return inch / 6.0f;
-	}
-
-	ROCKET_ERROR;
-	return 0;
-}
 
 }
 }

+ 24 - 7
Source/Core/Element.cpp

@@ -184,7 +184,7 @@ void Element::UpdateAnimations()
 
 		for(auto& animation : animations)
 		{
-			Property property = animation.UpdateAndGetProperty(time);
+			Property property = animation.UpdateAndGetProperty(time, *this);
 			if(property.unit != Property::UNKNOWN)
 				SetProperty(animation.GetPropertyName(), property);
 		}
@@ -607,6 +607,28 @@ void Element::GetLocalDimensionProperties(const Property **width, const Property
 	style->GetLocalDimensionProperties(width, height);
 }
 
+Vector2f Element::GetContainingBlock()
+{
+	Vector2f containing_block(0, 0);
+
+	if (offset_parent != NULL)
+	{
+		int position_property = GetPosition();
+		const Box& parent_box = offset_parent->GetBox();
+
+		if (position_property == POSITION_STATIC || position_property == POSITION_RELATIVE)
+		{
+			containing_block = parent_box.GetSize();
+		}
+		else if(position_property == POSITION_ABSOLUTE || position_property == POSITION_FIXED)
+		{
+			containing_block = parent_box.GetSize(Box::PADDING);
+		}
+	}
+
+	return containing_block;
+}
+
 void Element::GetOverflow(int *overflow_x, int *overflow_y)
 {
 	style->GetOverflow(overflow_x, overflow_y);
@@ -911,18 +933,13 @@ bool Element::Animate(const String & property_name, const Property & target_valu
 	{
 		float start_time = Clock::GetElapsedTime() + delay;
 		const Property* property = GetProperty(property_name);
-		if (!property) 
+		if (!property || !property->definition) 
 			return false;
 
 		animations.push_back(
 			ElementAnimation{ property_name, *property, start_time, duration, num_iterations, alternate_direction }
 		);
 		animation = &animations.back();
-		if(!animation->IsValid())
-		{
-			animations.pop_back();
-			animation = nullptr;
-		}
 	}
 	else
 	{

+ 59 - 77
Source/Core/ElementAnimation.cpp

@@ -27,7 +27,9 @@
 
 #include "precompiled.h"
 #include "ElementAnimation.h"
+#include "ElementStyle.h"
 #include "../../Include/Rocket/Core/TransformPrimitive.h"
+#include "../../Include/Rocket/Core/StyleSheetSpecification.h"
 
 namespace Rocket {
 namespace Core {
@@ -77,69 +79,73 @@ static bool CombineAndDecompose(Transform& t, Element& e)
 	return true;
 }
 
-static Variant InterpolateValues(const Variant & v0, const Variant & v1, float alpha)
+
+static Property InterpolateProperties(const Property & p0, const Property& p1, float alpha, Element& element, const PropertyDefinition* definition)
 {
-	auto type0 = v0.GetType();
-	auto type1 = v1.GetType();
-	if (type0 != type1)
+	if ((p0.unit & Property::NUMBER_LENGTH_PERCENT) && (p1.unit & Property::NUMBER_LENGTH_PERCENT))
 	{
-		//Log::Message(Log::LT_WARNING, "Interpolating properties must be of same unit. Got types: '%c' and '%c'.", type0, type1);
-		return v0;
+		if (p0.unit == p1.unit)
+		{
+			// If we have the same units, we can just interpolate regardless of what the value represents.
+			float f0 = p0.value.Get<float>();
+			float f1 = p1.value.Get<float>();
+			float f = (1.0f - alpha) * f0 + alpha * f1;
+			return Property{ f, p0.unit };
+		}
+		else
+		{
+			// Otherwise, convert units to pixels.
+			float f0 = element.GetStyle()->ResolveNumberLengthPercent(&p0, definition->GetRelativeTarget());
+			float f1 = element.GetStyle()->ResolveNumberLengthPercent(&p1, definition->GetRelativeTarget());
+			float f = (1.0f - alpha) * f0 + alpha * f1;
+			return Property{ f, Property::PX };
+		}
 	}
 
-	switch (type0)
+	if (p0.unit == Property::COLOUR && p1.unit == Property::COLOUR)
 	{
-	case Variant::FLOAT:
-	{
-		float f0 = v0.Get<float>();
-		float f1 = v1.Get<float>();
-		float f = (1.0f - alpha) * f0 + alpha * f1;
-		return Variant(f);
-	}
-	case Variant::COLOURB:
-	{
-		Colourf c0 = ColourToLinearSpace(v0.Get<Colourb>());
-		Colourf c1 = ColourToLinearSpace(v1.Get<Colourb>());
+		Colourf c0 = ColourToLinearSpace(p0.value.Get<Colourb>());
+		Colourf c1 = ColourToLinearSpace(p1.value.Get<Colourb>());
+
 		Colourf c = c0 * (1.0f - alpha) + c1 * alpha;
-		return Variant(ColourFromLinearSpace(c));
+
+		return Property{ ColourFromLinearSpace(c), Property::COLOUR };
 	}
-	case Variant::TRANSFORMREF:
+
+	if (p0.unit == Property::TRANSFORM && p1.unit == Property::TRANSFORM)
 	{
 		using namespace Rocket::Core::Transforms;
 
 		// Build the new, interpolating transform
 		auto t = TransformRef{ new Transform };
 
-		auto t0 = v0.Get<TransformRef>();
-		auto t1 = v1.Get<TransformRef>();
+		auto t0 = p0.value.Get<TransformRef>();
+		auto t1 = p1.value.Get<TransformRef>();
 
-		const auto& p0 = t0->GetPrimitives();
-		const auto& p1 = t1->GetPrimitives();
+		const auto& prim0 = t0->GetPrimitives();
+		const auto& prim1 = t1->GetPrimitives();
 
-		if (p0.size() != p1.size())
+		if (prim0.size() != prim1.size())
 		{
-			ROCKET_ERRORMSG("Transform primitives not of same size during interpolation. Were the transforms properly prepared for interpolation?")
-			return Variant{ t0 };
+			ROCKET_ERRORMSG("Transform primitives not of same size during interpolation. Were the transforms properly prepared for interpolation?");
+			return Property{ t0, Property::TRANSFORM };
 		}
 
-		for (size_t i = 0; i < p0.size(); i++)
+		for (size_t i = 0; i < prim0.size(); i++)
 		{
-			Primitive p = p0[i];
-			if (!p.InterpolateWith(p1[i], alpha))
+			Primitive p = prim0[i];
+			if (!p.InterpolateWith(prim1[i], alpha))
 			{
 				ROCKET_ERRORMSG("Transform primitives can not be interpolated. Were the transforms properly prepared for interpolation?");
-				return Variant{ t0 };
+				return Property{ t0, Property::TRANSFORM };
 			}
 			t->AddPrimitive(p);
 		}
 
-		return Variant(t);
+		return Property{ t, Property::TRANSFORM };
 	}
-	}
-
-	//Log::Message(Log::LT_WARNING, "Can not interpolate types of: '%c'.", type0);
 
-	return v0;
+	return alpha < 0.5f ? p0 : p1;
 }
 
 
@@ -326,8 +332,8 @@ static bool PrepareTransforms(std::vector<AnimationKey>& keys, Element& element,
 			continue;
 		}
 
-		auto& t0 = keys[i - 1].value.Get<TransformRef>();
-		auto& t1 = keys[i].value.Get<TransformRef>();
+		auto& t0 = keys[i - 1].property.value.Get<TransformRef>();
+		auto& t1 = keys[i].property.value.Get<TransformRef>();
 
 		auto result = PrepareTransformPair(*t0, *t1, element);
 
@@ -352,49 +358,29 @@ static bool PrepareTransforms(std::vector<AnimationKey>& keys, Element& element,
 }
 
 
-static bool TryMakeUnitValid(Variant& value)
-{
-	bool result = true;
-
-	//switch (value.GetType())
-	//{
-	//case Variant::FLOAT:
-	//case Variant::COLOURB:
-	//case Variant::TRANSFORMREF:
-	//	break;
-	//default:
-	//{
-	//	// Try to convert types to float so they can be interpolated
-	//	float f = 0.0f;
-	//	result = value.GetInto(f);
-	//	if (result) value.Reset(f);
-	//	break;
-	//}
-	//}
-	return result;
-}
-
 ElementAnimation::ElementAnimation(const String& property_name, const Property& current_value, float start_world_time, float duration, int num_iterations, bool alternate_direction)
-	: property_name(property_name), property_unit(current_value.unit), property_specificity(current_value.specificity),
-	duration(duration), num_iterations(num_iterations), alternate_direction(alternate_direction),
-	keys({ AnimationKey{0.0f, current_value.value, Tween{}} }),
+	: property_name(property_name), duration(duration), num_iterations(num_iterations), alternate_direction(alternate_direction),
+	keys({ AnimationKey{0.0f, current_value, Tween{}} }),
 	last_update_world_time(start_world_time), time_since_iteration_start(0.0f), current_iteration(0), reverse_direction(false), animation_complete(false)
 {
-	valid = true;// TryMakeUnitValid(keys.back().value);
+	ROCKET_ASSERT(current_value.definition);
 }
 
 
 bool ElementAnimation::AddKey(float time, const Property & property, Element& element, Tween tween)
 {
-	if (property.unit != property_unit || !valid)
+	int valid_properties = (Property::NUMBER_LENGTH_PERCENT | Property::ANGLE | Property::COLOUR | Property::TRANSFORM);
+
+	if (!(property.unit & valid_properties))
+	{
+		Log::Message(Log::LT_WARNING, "Property '%s' is not a valid target for interpolation.", property.ToString().CString());
 		return false;
+	}
 
 	bool result = true;
-	keys.push_back({ time, property.value, tween });
-	//keys.push_back({ time, property, tween });
-	//result = TryMakeUnitValid(keys.back().value);
+	keys.push_back({ time, property, tween });
 
-	if (result && property.unit == Property::TRANSFORM)
+	if (property.unit == Property::TRANSFORM)
 	{
 		bool must_decompose = false;
 		auto& transform = *property.value.Get<TransformRef>();
@@ -422,12 +408,10 @@ bool ElementAnimation::AddKey(float time, const Property & property, Element& el
 }
 
 
-Property ElementAnimation::UpdateAndGetProperty(float world_time)
+Property ElementAnimation::UpdateAndGetProperty(float world_time, Element& element)
 {
-	Property result;
-
-	if (animation_complete || !valid || world_time - last_update_world_time <= 0.0f)
-		return result;
+	if (animation_complete || world_time - last_update_world_time <= 0.0f)
+		return Property{};
 
 	const float dt = Math::Min(world_time - last_update_world_time, 0.1f);
 
@@ -493,9 +477,7 @@ Property ElementAnimation::UpdateAndGetProperty(float world_time)
 
 	alpha = keys[key1].tween(alpha);
 
-	result.unit = property_unit;
-	result.specificity = property_specificity;
-	result.value = InterpolateValues(keys[key0].value, keys[key1].value, alpha);
+	Property result = InterpolateProperties(keys[key0].property, keys[key1].property, alpha, element, keys[0].property.definition);
 	
 	return result;
 }

+ 2 - 6
Source/Core/ElementAnimation.h

@@ -38,7 +38,7 @@ namespace Core {
 
 struct AnimationKey {
 	float time;
-	Variant value;
+	Property property;
 	Tween tween;  // Tweening between the previous and this key. Ignored for the first animation key.
 };
 
@@ -47,8 +47,6 @@ class ElementAnimation
 {
 private:
 	String property_name;
-	Property::Unit property_unit;
-	int property_specificity;
 
 	float duration;           // for a single iteration
 	int num_iterations;       // -1 for infinity
@@ -62,7 +60,6 @@ private:
 	bool reverse_direction;
 
 	bool animation_complete;
-	bool valid;
 
 public:
 
@@ -70,13 +67,12 @@ public:
 
 	bool AddKey(float time, const Property& property, Element& element, Tween tween);
 
-	Property UpdateAndGetProperty(float time);
+	Property UpdateAndGetProperty(float time, Element& element);
 
 	const String& GetPropertyName() const { return property_name; }
 	float GetDuration() const { return duration; }
 	void SetDuration(float duration) { this->duration = duration; }
 	bool IsComplete() const { return animation_complete; }
-	bool IsValid() const { return valid; }
 };
 
 

+ 131 - 5
Source/Core/ElementStyle.cpp

@@ -343,6 +343,135 @@ const Property* ElementStyle::GetLocalProperty(const String& name)
 	return NULL;
 }
 
+float ElementStyle::ResolveLength(const Property * property)
+{
+	if (!property)
+	{
+		ROCKET_ERROR;
+		return 0.0f;
+	}
+
+	if (!(property->unit & Property::LENGTH))
+	{
+		ROCKET_ERRORMSG("Trying to resolve length on a non-length property.");
+		return 0.0f;
+	}
+
+	switch (property->unit)
+	{
+	case Property::NUMBER:
+	case Property::PX:
+		return property->value.Get< float >();
+	case Property::EM:
+		return property->value.Get< float >() * ElementUtilities::GetFontSize(element);
+	case Property::REM:
+		return property->value.Get< float >() * ElementUtilities::GetFontSize(element->GetOwnerDocument());
+	case Property::DP:
+		return property->value.Get< float >() * ElementUtilities::GetDensityIndependentPixelRatio(element);
+	}
+
+	// Values based on pixels-per-inch.
+	if (property->unit & Property::PPI_UNIT)
+	{
+		float inch = property->value.Get< float >() * element->GetRenderInterface()->GetPixelsPerInch();
+
+		switch (property->unit)
+		{
+		case Property::INCH: // inch
+			return inch;
+		case Property::CM: // centimeter
+			return inch * (1.0f / 2.54f);
+		case Property::MM: // millimeter
+			return inch * (1.0f / 25.4f);
+		case Property::PT: // point
+			return inch * (1.0f / 72.0f);
+		case Property::PC: // pica
+			return inch * (1.0f / 6.0f);
+		}
+	}
+
+	// We're not a numeric property; return 0.
+	return 0.0f;
+}
+
+float ElementStyle::ResolveAngle(const Property * property)
+{
+	switch (property->unit)
+	{
+	case Property::NUMBER:
+	case Property::DEG:
+		return Math::DegreesToRadians(property->value.Get< float >());
+	case Property::RAD:
+		return property->value.Get< float >();
+	case Property::PERCENT:
+		return property->value.Get< float >() * 0.01f * 2.0f * Math::ROCKET_PI;
+	}
+	ROCKET_ERRORMSG("Trying to resolve angle on a non-angle property.");
+	return 0.0f;
+}
+
+float ElementStyle::ResolveNumberLengthPercent(const String& name, const Property * property)
+{
+	if (property->unit & Property::LENGTH)
+	{
+		return ResolveLength(property);
+	}
+
+	auto definition = property->definition;
+	if (!definition) definition = StyleSheetSpecification::GetProperty(name);
+	if (!definition) return 0.0f;
+
+	auto relative_target = definition->GetRelativeTarget();
+	
+	return ResolveNumberLengthPercent(property, relative_target);
+}
+
+float ElementStyle::ResolveNumberLengthPercent(const Property * property, RelativeTarget relative_target)
+{
+	if (property->unit & Property::LENGTH)
+	{
+		return ResolveLength(property);
+	}
+
+	float base_value = 0.0f;
+
+	switch (relative_target)
+	{
+	case RelativeTarget::None:
+		base_value = 1.0f;
+		break;
+	case RelativeTarget::ContainingBlockWidth:
+		base_value = element->GetContainingBlock().x;
+		break;
+	case RelativeTarget::ContainingBlockHeight:
+		base_value = element->GetContainingBlock().y;
+		break;
+	case RelativeTarget::FontSize:
+		base_value = (int)ElementUtilities::GetFontSize(element);
+		break;
+	case RelativeTarget::ParentFontSize:
+		base_value = (int)ElementUtilities::GetFontSize(element->GetParentNode());
+		break;
+	case RelativeTarget::LineHeight:
+		base_value = (int)ElementUtilities::GetLineHeight(element);
+		break;
+	default:
+		break;
+	}
+
+	float scale_value = 0.0f;
+
+	switch (property->unit)
+	{
+	case Property::NUMBER:
+		scale_value = property->value.Get< float >();
+	case Property::PERCENT:
+		scale_value = property->value.Get< float >() * 0.01f;
+	}
+
+	return base_value * scale_value;
+}
+
 // Resolves one of this element's properties.
 float ElementStyle::ResolveProperty(const Property* property, float base_value)
 {
@@ -363,9 +492,9 @@ float ElementStyle::ResolveProperty(const Property* property, float base_value)
 			return base_value * property->value.Get< float >() * 0.01f;
 
 		case Property::EM:
-			return property->value.Get< float >() * ElementUtilities::GetFontSize(element);
+			return property->value.Get< float >() * (float)ElementUtilities::GetFontSize(element);
 		case Property::REM:
-			return property->value.Get< float >() * ElementUtilities::GetFontSize(element->GetOwnerDocument());
+			return property->value.Get< float >() * (float)ElementUtilities::GetFontSize(element->GetOwnerDocument());
 		case Property::DP:
 			return property->value.Get< float >() * ElementUtilities::GetDensityIndependentPixelRatio(element);
 
@@ -442,9 +571,6 @@ float ElementStyle::ResolveProperty(const String& name, float base_value)
 			case Property::EM:
 				return property->value.Get< float >() * base_value;
 
-			case Property::DP:
-				return property->value.Get< float >() * ElementUtilities::GetDensityIndependentPixelRatio(element);
-
 			case Property::REM:
 				// If an rem-relative font size is specified, it is expressed relative to the document's
 				// font height.

+ 15 - 0
Source/Core/ElementStyle.h

@@ -105,6 +105,21 @@ public:
 	/// @param[in] name The name of the property to fetch the value for.
 	/// @return The value of this property for this element, or NULL if this property has not been explicitly defined for this element.
 	const Property* GetLocalProperty(const String& name);
+
+
+	/// Resolves a length property to pixels. Note: This excludes percentages.
+	float ResolveLength(const Property* property);
+	
+	/// Resolves an angle to radians
+	static float ResolveAngle(const Property* property);
+
+	/// Resolves a number-length-percentage property to pixels.
+	float ResolveNumberLengthPercent(const String& name, const Property* property);
+
+	/// Resolves the canonical unit (pixels) from 'number-length-percent' property.
+	/// 'percentage' and 'number' gets multiplied by the size of the specified relative reference.
+	float ResolveNumberLengthPercent(const Property* property, RelativeTarget relative_target);
+
 	/// Resolves one of this element's properties. If the value is a number or px, this is returned. If it's a 
 	/// percentage then it is resolved based on the second argument (the base value).
 	/// If it's an angle, it is returned as radians.

+ 16 - 40
Source/Core/ElementUtilities.cpp

@@ -33,6 +33,7 @@
 #include "LayoutEngine.h"
 #include "../../Include/Rocket/Core.h"
 #include "../../Include/Rocket/Core/TransformPrimitive.h"
+#include "ElementStyle.h"
 
 namespace Rocket {
 namespace Core {
@@ -147,56 +148,31 @@ int ElementUtilities::GetFontSize(Element* element)
 // Returns an element's line height, if it has a font defined.
 int ElementUtilities::GetLineHeight(Element* element)
 {
+	// TODO: Return float or int?
 	const Property* line_height_property = element->GetLineHeightProperty();
 
-	Element* font_element = element;
-	if (line_height_property->unit == Property::REM)
-		font_element = element->GetOwnerDocument();
-
-	FontFaceHandle* font_face_handle = font_element->GetFontFaceHandle();
-	if (font_face_handle == NULL)
-		return 0;
+	if (line_height_property->unit & Property::LENGTH && line_height_property->unit != Property::NUMBER)
+	{
+		float value = element->GetStyle()->ResolveLength(line_height_property);
+		return Math::RoundToInteger(value);
+	}
 
-	int line_height = font_face_handle->GetLineHeight();
-	float inch = element->GetRenderInterface()->GetPixelsPerInch();
+	float scale_factor = 1.0f;
 
 	switch (line_height_property->unit)
 	{
-	ROCKET_UNUSED_SWITCH_ENUM(Property::UNKNOWN);
-	ROCKET_UNUSED_SWITCH_ENUM(Property::KEYWORD);
-	ROCKET_UNUSED_SWITCH_ENUM(Property::STRING);
-	ROCKET_UNUSED_SWITCH_ENUM(Property::COLOUR);
-	ROCKET_UNUSED_SWITCH_ENUM(Property::ABSOLUTE_UNIT);
-	ROCKET_UNUSED_SWITCH_ENUM(Property::PPI_UNIT);
-	ROCKET_UNUSED_SWITCH_ENUM(Property::RELATIVE_UNIT);
 	case Property::NUMBER:
-	case Property::EM:
-	case Property::REM:
-		// If the property is a straight number or an em measurement, then it scales the line height.
-		return Math::RoundToInteger(line_height_property->value.Get< float >() * line_height);
+		scale_factor = line_height_property->value.Get< float >();
+		break;
 	case Property::PERCENT:
-		// If the property is a percentage, then it scales the line height.
-		return Math::RoundToInteger(line_height_property->value.Get< float >() * line_height * 0.01f);
-	case Property::PX:
-		// A px measurement.
-		return Math::RoundToInteger(line_height_property->value.Get< float >());
-	case Property::DP:
-		// A density-independent pixel measurement.
-		return Math::RoundToInteger(line_height_property->value.Get< float >() * ElementUtilities::GetDensityIndependentPixelRatio(element));
-	case Property::INCH:
-		// Values based on pixels-per-inch.
-		return Math::RoundToInteger(line_height_property->value.Get< float >() * inch);
-	case Property::CM:
-		return Math::RoundToInteger(line_height_property->value.Get< float >() * inch * (1.0f / 2.54f));
-	case Property::MM:
-		return Math::RoundToInteger(line_height_property->value.Get< float >() * inch * (1.0f / 25.4f));
-	case Property::PT:
-		return Math::RoundToInteger(line_height_property->value.Get< float >() * inch * (1.0f / 72.0f));
-	case Property::PC:
-		return Math::RoundToInteger(line_height_property->value.Get< float >() * inch * (1.0f / 6.0f));
+		scale_factor = line_height_property->value.Get< float >() * 0.01f;
+		break;
 	}
 
-	return 0;
+	float font_size = (float)GetFontSize(element);
+	float value = font_size * scale_factor;
+
+	return Math::RoundToInteger(value);
 }
 
 // Returns the width of a string rendered within the context of the given element.

+ 16 - 1
Source/Core/PropertyDefinition.cpp

@@ -33,7 +33,7 @@
 namespace Rocket {
 namespace Core {
 
-PropertyDefinition::PropertyDefinition(const String& _default_value, bool _inherited, bool _forces_layout) : default_value(_default_value, Property::UNKNOWN)
+PropertyDefinition::PropertyDefinition(const String& _default_value, bool _inherited, bool _forces_layout) : default_value(_default_value, Property::UNKNOWN), relative_target(RelativeTarget::None)
 {
 	inherited = _inherited;
 	forces_layout = _forces_layout;
@@ -167,5 +167,20 @@ const Property* PropertyDefinition::GetDefaultValue() const
 	return &default_value;
 }
 
+/// Returns the default defined for this property.
+
+RelativeTarget PropertyDefinition::GetRelativeTarget() const
+{
+	return relative_target;
+}
+
+/// Set target for units with scaling percentages
+
+PropertyDefinition & PropertyDefinition::SetRelativeTarget(RelativeTarget relative_target) 
+{
+	this->relative_target = relative_target;
+	return *this;
+}
+
 }
 }

+ 11 - 5
Source/Core/PropertyParserNumber.cpp

@@ -31,8 +31,8 @@
 namespace Rocket {
 namespace Core {
 
-PropertyParserNumber::PropertyParserNumber(int units)
-	: units(units)
+PropertyParserNumber::PropertyParserNumber(int units, Property::Unit zero_unit)
+	: units(units), zero_unit(zero_unit)
 {
 
 	if (units & Property::PERCENT)
@@ -117,9 +117,15 @@ bool PropertyParserNumber::ParseValue(Property& property, const String& value, c
 
 	if ((units & property.unit) == 0)
 	{
-		// Detected unit not allowed (this can only apply to NUMBER,
-		// i.e., when no unit was found but one is required).
-		return false;
+		// Detected unit not allowed (this can only apply to NUMBER, i.e., when no unit was found but one is required).
+		// However, we allow values of "0" if zero_unit is set.
+		bool result = (zero_unit != Property::UNKNOWN && (value.Length() == 1 && value[0] == '0'));
+		if(result)
+		{
+			property.unit = zero_unit;
+			property.value = Variant(0.0f);
+		}
+		return result;
 	}
 
 	float float_value;

+ 4 - 20
Source/Core/PropertyParserNumber.h

@@ -42,26 +42,7 @@ namespace Core {
 class PropertyParserNumber : public PropertyParser
 {
 public:
-	enum
-	{
-		NUMBER =	Property::NUMBER,
-		LENGTH =	Property::NUMBER
-				| Property::PERCENT
-				| Property::PX
-		        | Property::DP
-				| Property::EM
-				| Property::INCH
-				| Property::CM
-				| Property::MM
-				| Property::PT
-				| Property::PC,
-		ANGLE =		Property::NUMBER
-				| Property::PERCENT
-				| Property::DEG
-				| Property::RAD
-	};
-
-	PropertyParserNumber(int units);
+	PropertyParserNumber(int units, Property::Unit zero_unit = Property::UNKNOWN);
 	virtual ~PropertyParserNumber();
 
 	/// Called to parse a RCSS number declaration.
@@ -78,6 +59,9 @@ private:
 	// Stores a bit mask of allowed units.
 	int units;
 
+	// If zero unit is set and pure numbers are not allowed, parsing of "0" is still allowed and assigned the given unit
+	Property::Unit zero_unit;
+
 	// Stores a list of the numerical units and their suffixes.
 	typedef std::pair< Property::Unit, String > UnitSuffix;
 	std::vector< UnitSuffix > unit_suffixes;

+ 3 - 3
Source/Core/PropertyParserTransform.cpp

@@ -32,9 +32,9 @@ namespace Rocket {
 namespace Core {
 
 PropertyParserTransform::PropertyParserTransform()
-	: number(PropertyParserNumber::NUMBER),
-	  length(PropertyParserNumber::LENGTH),
-	  angle(PropertyParserNumber::ANGLE)
+	: number(Property::NUMBER),
+	  length(Property::LENGTH_PERCENT, Property::PX),
+	  angle(Property::ANGLE, Property::RAD)
 {
 }
 

+ 30 - 28
Source/Core/StyleSheetSpecification.cpp

@@ -140,9 +140,11 @@ bool StyleSheetSpecification::ParsePropertyDeclaration(PropertyDictionary& dicti
 // Registers Rocket's default parsers.
 void StyleSheetSpecification::RegisterDefaultParsers()
 {
-	RegisterParser("number", new PropertyParserNumber(PropertyParserNumber::NUMBER));
-	RegisterParser("length", new PropertyParserNumber(PropertyParserNumber::LENGTH));
-	RegisterParser("angle", new PropertyParserNumber(PropertyParserNumber::ANGLE));
+	RegisterParser("number", new PropertyParserNumber(Property::NUMBER));
+	RegisterParser("length", new PropertyParserNumber(Property::LENGTH, Property::PX));
+	RegisterParser("length_percent", new PropertyParserNumber(Property::LENGTH_PERCENT, Property::PX));
+	RegisterParser("number_length_percent", new PropertyParserNumber(Property::NUMBER_LENGTH_PERCENT, Property::PX));
+	RegisterParser("angle", new PropertyParserNumber(Property::ANGLE, Property::RAD));
 	RegisterParser("keyword", new PropertyParserKeyword());
 	RegisterParser("string", new PropertyParserString());
 	RegisterParser(COLOR, new PropertyParserColour());
@@ -156,22 +158,22 @@ void StyleSheetSpecification::RegisterDefaultProperties()
 
 	RegisterProperty(MARGIN_TOP, "0px", false, true)
 		.AddParser("keyword", "auto")
-		.AddParser("length");
+		.AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth);
 	RegisterProperty(MARGIN_RIGHT, "0px", false, true)
 		.AddParser("keyword", "auto")
-		.AddParser("length");
+		.AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth);
 	RegisterProperty(MARGIN_BOTTOM, "0px", false, true)
 		.AddParser("keyword", "auto")
-		.AddParser("length");
+		.AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth);
 	RegisterProperty(MARGIN_LEFT, "0px", false, true)
 		.AddParser("keyword", "auto")
-		.AddParser("length");
+		.AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth);
 	RegisterShorthand(MARGIN, "margin-top, margin-right, margin-bottom, margin-left");
 
-	RegisterProperty(PADDING_TOP, "0px", false, true).AddParser("length");
-	RegisterProperty(PADDING_RIGHT, "0px", false, true).AddParser("length");
-	RegisterProperty(PADDING_BOTTOM, "0px", false, true).AddParser("length");
-	RegisterProperty(PADDING_LEFT, "0px", false, true).AddParser("length");
+	RegisterProperty(PADDING_TOP, "0px", false, true).AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth);
+	RegisterProperty(PADDING_RIGHT, "0px", false, true).AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth);
+	RegisterProperty(PADDING_BOTTOM, "0px", false, true).AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth);
+	RegisterProperty(PADDING_LEFT, "0px", false, true).AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth);
 	RegisterShorthand(PADDING, "padding-top, padding-right, padding-bottom, padding-left");
 
 	RegisterProperty(BORDER_TOP_WIDTH, "0px", false, true).AddParser("length");
@@ -196,16 +198,16 @@ void StyleSheetSpecification::RegisterDefaultProperties()
 	RegisterProperty(POSITION, "static", false, true).AddParser("keyword", "static, relative, absolute, fixed");
 	RegisterProperty(TOP, "auto", false, false)
 		.AddParser("keyword", "auto")
-		.AddParser("length");
+		.AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockHeight);
 	RegisterProperty(RIGHT, "auto", false, false)
 		.AddParser("keyword", "auto")
-		.AddParser("length");
+		.AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth);
 	RegisterProperty(BOTTOM, "auto", false, false)
 		.AddParser("keyword", "auto")
-		.AddParser("length");
+		.AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockHeight);
 	RegisterProperty(LEFT, "auto", false, false)
 		.AddParser("keyword", "auto")
-		.AddParser("length");
+		.AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth);
 
 	RegisterProperty(FLOAT, "none", false, true).AddParser("keyword", "none, left, right");
 	RegisterProperty(CLEAR, "none", false, true).AddParser("keyword", "none, left, right, both");
@@ -216,20 +218,20 @@ void StyleSheetSpecification::RegisterDefaultProperties()
 
 	RegisterProperty(WIDTH, "auto", false, true)
 		.AddParser("keyword", "auto")
-		.AddParser("length");
-	RegisterProperty(MIN_WIDTH, "0px", false, true).AddParser("length");
-	RegisterProperty(MAX_WIDTH, "-1", false, true).AddParser("length");
+		.AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth);
+	RegisterProperty(MIN_WIDTH, "0px", false, true).AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth);
+	RegisterProperty(MAX_WIDTH, "-1px", false, true).AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockWidth);
 
 	RegisterProperty(HEIGHT, "auto", false, true)
 		.AddParser("keyword", "auto")
-		.AddParser("length");
-	RegisterProperty(MIN_HEIGHT, "0px", false, true).AddParser("length");
-	RegisterProperty(MAX_HEIGHT, "-1", false, true).AddParser("length");
+		.AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockHeight);
+	RegisterProperty(MIN_HEIGHT, "0px", false, true).AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockHeight);
+	RegisterProperty(MAX_HEIGHT, "-1px", false, true).AddParser("length_percent").SetRelativeTarget(RelativeTarget::ContainingBlockHeight);
 
-	RegisterProperty(LINE_HEIGHT, "1.2", true, true).AddParser("length");
+	RegisterProperty(LINE_HEIGHT, "1.2", true, true).AddParser("number_length_percent").SetRelativeTarget(RelativeTarget::FontSize);
 	RegisterProperty(VERTICAL_ALIGN, "baseline", false, true)
 		.AddParser("keyword", "baseline, middle, sub, super, text-top, text-bottom, top, bottom")
-		.AddParser("length");
+		.AddParser("length_percent").SetRelativeTarget(RelativeTarget::LineHeight);
 
 	RegisterProperty(OVERFLOW_X, "visible", false, true).AddParser("keyword", "visible, hidden, auto, scroll");
 	RegisterProperty(OVERFLOW_Y, "visible", false, true).AddParser("keyword", "visible, hidden, auto, scroll");
@@ -250,7 +252,7 @@ void StyleSheetSpecification::RegisterDefaultProperties()
 	RegisterProperty(FONT_CHARSET, "U+0020-007E", true, false).AddParser("string");
 	RegisterProperty(FONT_STYLE, "normal", true, true).AddParser("keyword", "normal, italic");
 	RegisterProperty(FONT_WEIGHT, "normal", true, true).AddParser("keyword", "normal, bold");
-	RegisterProperty(FONT_SIZE, "12", true, true).AddParser("length");
+	RegisterProperty(FONT_SIZE, "12px", true, true).AddParser("length").AddParser("length_percent").SetRelativeTarget(RelativeTarget::ParentFontSize);
 	RegisterShorthand(FONT, "font-style, font-weight, font-size, font-family, font-charset");
 
 	RegisterProperty(TEXT_ALIGN, LEFT, true, true).AddParser("keyword", "left, right, center, justify");
@@ -269,12 +271,12 @@ void StyleSheetSpecification::RegisterDefaultProperties()
 
 	// Perspective and Transform specifications
 	RegisterProperty(PERSPECTIVE, "none", false, false).AddParser("keyword", "none").AddParser("length");
-	RegisterProperty(PERSPECTIVE_ORIGIN_X, "50%", false, false).AddParser("keyword", "left, center, right").AddParser("length");
-	RegisterProperty(PERSPECTIVE_ORIGIN_Y, "50%", false, false).AddParser("keyword", "top, center, bottom").AddParser("length");
+	RegisterProperty(PERSPECTIVE_ORIGIN_X, "50%", false, false).AddParser("keyword", "left, center, right").AddParser("length_percent");
+	RegisterProperty(PERSPECTIVE_ORIGIN_Y, "50%", false, false).AddParser("keyword", "top, center, bottom").AddParser("length_percent");
 	RegisterShorthand(PERSPECTIVE_ORIGIN, "perspective-origin-x, perspective-origin-y");
 	RegisterProperty(TRANSFORM, "none", false, false).AddParser("keyword", "none").AddParser(TRANSFORM);
-	RegisterProperty(TRANSFORM_ORIGIN_X, "50%", false, false).AddParser("keyword", "left, center, right").AddParser("length");
-	RegisterProperty(TRANSFORM_ORIGIN_Y, "50%", false, false).AddParser("keyword", "top, center, bottom").AddParser("length");
+	RegisterProperty(TRANSFORM_ORIGIN_X, "50%", false, false).AddParser("keyword", "left, center, right").AddParser("length_percent");
+	RegisterProperty(TRANSFORM_ORIGIN_Y, "50%", false, false).AddParser("keyword", "top, center, bottom").AddParser("length_percent");
 	RegisterProperty(TRANSFORM_ORIGIN_Z, "0", false, false).AddParser("length");
 	RegisterShorthand(TRANSFORM_ORIGIN, "transform-origin-x, transform-origin-y, transform-origin-z");
 

+ 3 - 3
Source/Core/TransformPrimitive.cpp

@@ -95,19 +95,19 @@ float NumericValue::Resolve(Element& e, float base) const noexcept
 float NumericValue::ResolveWidth(Element& e) const noexcept
 {
 	if(unit & (Property::PX | Property::NUMBER)) return number;
-	return Resolve(e, e.GetBox().GetSize().x);
+	return Resolve(e, e.GetBox().GetSize(Box::BORDER).x);
 }
 
 float NumericValue::ResolveHeight(Element& e) const noexcept
 {
 	if (unit & (Property::PX | Property::NUMBER)) return number;
-	return Resolve(e, e.GetBox().GetSize().y);
+	return Resolve(e, e.GetBox().GetSize(Box::BORDER).y);
 }
 
 float NumericValue::ResolveDepth(Element& e) const noexcept
 {
 	if (unit & (Property::PX | Property::NUMBER)) return number;
-	Vector2f size = e.GetBox().GetSize();
+	Vector2f size = e.GetBox().GetSize(Box::BORDER);
 	return Resolve(e, Math::Max(size.x, size.y));
 }