blob: 16e3f00d7193183df0afc01782c842cb2cf53b0f [file] [log] [blame]
/*
* Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Apple Inc. All rights reserved.
* Copyright (C) 2012 Google Inc. All rights reserved.
* Copyright (C) 2012, 2013 Adobe Systems Incorporated. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer.
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "config.h"
#include "TransformFunctions.h"
#include "CSSFunctionValue.h"
#include "CSSPrimitiveValueMappings.h"
#include "CSSValueList.h"
#include "Matrix3DTransformOperation.h"
#include "MatrixTransformOperation.h"
#include "PerspectiveTransformOperation.h"
#include "RotateTransformOperation.h"
#include "ScaleTransformOperation.h"
#include "SkewTransformOperation.h"
#include "TranslateTransformOperation.h"
#include <wtf/text/StringConcatenateNumbers.h>
namespace WebCore {
static TransformOperation::OperationType transformOperationType(CSSValueID type)
{
switch (type) {
case CSSValueScale:
return TransformOperation::SCALE;
case CSSValueScaleX:
return TransformOperation::SCALE_X;
case CSSValueScaleY:
return TransformOperation::SCALE_Y;
case CSSValueScaleZ:
return TransformOperation::SCALE_Z;
case CSSValueScale3d:
return TransformOperation::SCALE_3D;
case CSSValueTranslate:
return TransformOperation::TRANSLATE;
case CSSValueTranslateX:
return TransformOperation::TRANSLATE_X;
case CSSValueTranslateY:
return TransformOperation::TRANSLATE_Y;
case CSSValueTranslateZ:
return TransformOperation::TRANSLATE_Z;
case CSSValueTranslate3d:
return TransformOperation::TRANSLATE_3D;
case CSSValueRotate:
return TransformOperation::ROTATE;
case CSSValueRotateX:
return TransformOperation::ROTATE_X;
case CSSValueRotateY:
return TransformOperation::ROTATE_Y;
case CSSValueRotateZ:
return TransformOperation::ROTATE_Z;
case CSSValueRotate3d:
return TransformOperation::ROTATE_3D;
case CSSValueSkew:
return TransformOperation::SKEW;
case CSSValueSkewX:
return TransformOperation::SKEW_X;
case CSSValueSkewY:
return TransformOperation::SKEW_Y;
case CSSValueMatrix:
return TransformOperation::MATRIX;
case CSSValueMatrix3d:
return TransformOperation::MATRIX_3D;
case CSSValuePerspective:
return TransformOperation::PERSPECTIVE;
default:
break;
}
return TransformOperation::NONE;
}
Length convertToFloatLength(const CSSPrimitiveValue* primitiveValue, const CSSToLengthConversionData& conversionData)
{
return primitiveValue ? primitiveValue->convertToLength<FixedFloatConversion | PercentConversion | CalculatedConversion>(conversionData) : Length(LengthType::Undefined);
}
// FIXME: This should return std::optional<TransformOperations>
bool transformsForValue(const CSSValue& value, const CSSToLengthConversionData& conversionData, TransformOperations& outOperations)
{
ASSERT(!outOperations.size());
if (!is<CSSValueList>(value))
return false;
auto& operations = outOperations.operations();
for (auto& currentValue : downcast<CSSValueList>(value)) {
if (!is<CSSFunctionValue>(currentValue))
continue;
auto& transformValue = downcast<CSSFunctionValue>(currentValue.get());
if (!transformValue.length())
continue;
bool haveNonPrimitiveValue = false;
for (unsigned j = 0; j < transformValue.length(); ++j) {
if (!is<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(j))) {
haveNonPrimitiveValue = true;
break;
}
}
if (haveNonPrimitiveValue)
continue;
auto& firstValue = downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(0));
switch (transformValue.name()) {
case CSSValueScale:
case CSSValueScaleX:
case CSSValueScaleY: {
double sx = 1.0;
double sy = 1.0;
if (transformValue.name() == CSSValueScaleY)
sy = firstValue.doubleValueDividingBy100IfPercentage();
else {
sx = firstValue.doubleValueDividingBy100IfPercentage();
if (transformValue.name() != CSSValueScaleX) {
if (transformValue.length() > 1) {
auto& secondValue = downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(1));
sy = secondValue.doubleValueDividingBy100IfPercentage();
} else
sy = sx;
}
}
operations.append(ScaleTransformOperation::create(sx, sy, 1.0, transformOperationType(transformValue.name())));
break;
}
case CSSValueScaleZ:
case CSSValueScale3d: {
double sx = 1.0;
double sy = 1.0;
double sz = 1.0;
if (transformValue.name() == CSSValueScaleZ)
sz = firstValue.doubleValueDividingBy100IfPercentage();
else if (transformValue.name() == CSSValueScaleY)
sy = firstValue.doubleValueDividingBy100IfPercentage();
else {
sx = firstValue.doubleValueDividingBy100IfPercentage();
if (transformValue.name() != CSSValueScaleX) {
if (transformValue.length() > 2) {
auto& thirdValue = downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(2));
sz = thirdValue.doubleValueDividingBy100IfPercentage();
}
if (transformValue.length() > 1) {
auto& secondValue = downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(1));
sy = secondValue.doubleValueDividingBy100IfPercentage();
} else
sy = sx;
}
}
operations.append(ScaleTransformOperation::create(sx, sy, sz, transformOperationType(transformValue.name())));
break;
}
case CSSValueTranslate:
case CSSValueTranslateX:
case CSSValueTranslateY: {
Length tx = Length(0, LengthType::Fixed);
Length ty = Length(0, LengthType::Fixed);
if (transformValue.name() == CSSValueTranslateY)
ty = convertToFloatLength(&firstValue, conversionData);
else {
tx = convertToFloatLength(&firstValue, conversionData);
if (transformValue.name() != CSSValueTranslateX) {
if (transformValue.length() > 1) {
auto& secondValue = downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(1));
ty = convertToFloatLength(&secondValue, conversionData);
}
}
}
if (tx.isUndefined() || ty.isUndefined()) {
operations.clear();
return false;
}
operations.append(TranslateTransformOperation::create(tx, ty, Length(0, LengthType::Fixed), transformOperationType(transformValue.name())));
break;
}
case CSSValueTranslateZ:
case CSSValueTranslate3d: {
Length tx = Length(0, LengthType::Fixed);
Length ty = Length(0, LengthType::Fixed);
Length tz = Length(0, LengthType::Fixed);
if (transformValue.name() == CSSValueTranslateZ)
tz = convertToFloatLength(&firstValue, conversionData);
else if (transformValue.name() == CSSValueTranslateY)
ty = convertToFloatLength(&firstValue, conversionData);
else {
tx = convertToFloatLength(&firstValue, conversionData);
if (transformValue.name() != CSSValueTranslateX) {
if (transformValue.length() > 2) {
auto& thirdValue = downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(2));
tz = convertToFloatLength(&thirdValue, conversionData);
}
if (transformValue.length() > 1) {
auto& secondValue = downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(1));
ty = convertToFloatLength(&secondValue, conversionData);
}
}
}
if (tx.isUndefined() || ty.isUndefined() || tz.isUndefined()) {
operations.clear();
return false;
}
operations.append(TranslateTransformOperation::create(tx, ty, tz, transformOperationType(transformValue.name())));
break;
}
case CSSValueRotate: {
double angle = firstValue.computeDegrees();
operations.append(RotateTransformOperation::create(0, 0, 1, angle, transformOperationType(transformValue.name())));
break;
}
case CSSValueRotateX:
case CSSValueRotateY:
case CSSValueRotateZ: {
double x = 0;
double y = 0;
double z = 0;
double angle = firstValue.computeDegrees();
if (transformValue.name() == CSSValueRotateX)
x = 1;
else if (transformValue.name() == CSSValueRotateY)
y = 1;
else
z = 1;
operations.append(RotateTransformOperation::create(x, y, z, angle, transformOperationType(transformValue.name())));
break;
}
case CSSValueRotate3d: {
if (transformValue.length() < 4)
break;
auto& secondValue = downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(1));
auto& thirdValue = downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(2));
auto& fourthValue = downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(3));
double x = firstValue.doubleValue();
double y = secondValue.doubleValue();
double z = thirdValue.doubleValue();
double angle = fourthValue.computeDegrees();
operations.append(RotateTransformOperation::create(x, y, z, angle, transformOperationType(transformValue.name())));
break;
}
case CSSValueSkew:
case CSSValueSkewX:
case CSSValueSkewY: {
double angleX = 0;
double angleY = 0;
double angle = firstValue.computeDegrees();
if (transformValue.name() == CSSValueSkewY)
angleY = angle;
else {
angleX = angle;
if (transformValue.name() == CSSValueSkew) {
if (transformValue.length() > 1) {
auto& secondValue = downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(1));
angleY = secondValue.computeDegrees();
}
}
}
operations.append(SkewTransformOperation::create(angleX, angleY, transformOperationType(transformValue.name())));
break;
}
case CSSValueMatrix: {
if (transformValue.length() < 6)
break;
double a = firstValue.doubleValue();
double b = downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(1)).doubleValue();
double c = downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(2)).doubleValue();
double d = downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(3)).doubleValue();
double e = conversionData.zoom() * downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(4)).doubleValue();
double f = conversionData.zoom() * downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(5)).doubleValue();
operations.append(MatrixTransformOperation::create(a, b, c, d, e, f));
break;
}
case CSSValueMatrix3d: {
if (transformValue.length() < 16)
break;
TransformationMatrix matrix(downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(0)).doubleValue(),
downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(1)).doubleValue(),
downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(2)).doubleValue(),
downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(3)).doubleValue(),
downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(4)).doubleValue(),
downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(5)).doubleValue(),
downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(6)).doubleValue(),
downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(7)).doubleValue(),
downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(8)).doubleValue(),
downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(9)).doubleValue(),
downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(10)).doubleValue(),
downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(11)).doubleValue(),
conversionData.zoom() * downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(12)).doubleValue(),
conversionData.zoom() * downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(13)).doubleValue(),
downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(14)).doubleValue(),
downcast<CSSPrimitiveValue>(*transformValue.itemWithoutBoundsCheck(15)).doubleValue());
operations.append(Matrix3DTransformOperation::create(matrix));
break;
}
case CSSValuePerspective: {
std::optional<Length> perspectiveLength;
if (!firstValue.isValueID()) {
if (firstValue.isLength())
perspectiveLength = convertToFloatLength(&firstValue, conversionData);
else {
// This is a quirk that should go away when 3d transforms are finalized.
// FIXME: https://bugs.webkit.org/show_bug.cgi?id=232669
// This does not deal properly with calc(), because we aren't passing conversionData here.
double doubleValue = firstValue.doubleValue();
if (doubleValue < 0) {
operations.clear();
return false;
}
perspectiveLength = Length(clampToPositiveInteger(doubleValue), LengthType::Fixed);
}
} else
ASSERT(firstValue.valueID() == CSSValueNone);
operations.append(PerspectiveTransformOperation::create(perspectiveLength));
break;
}
default:
ASSERT_NOT_REACHED();
break;
}
}
return true;
}
RefPtr<TranslateTransformOperation> translateForValue(const CSSValue& value, const CSSToLengthConversionData& conversionData)
{
if (!is<CSSValueList>(value))
return nullptr;
auto& valueList = downcast<CSSValueList>(value);
if (!valueList.length())
return nullptr;
auto type = TransformOperation::TRANSLATE;
Length tx = Length(0, LengthType::Fixed);
Length ty = Length(0, LengthType::Fixed);
Length tz = Length(0, LengthType::Fixed);
for (unsigned i = 0; i < valueList.length(); ++i) {
auto* valueItem = valueList.itemWithoutBoundsCheck(i);
if (!is<CSSPrimitiveValue>(valueItem))
return nullptr;
if (!i)
tx = convertToFloatLength(downcast<CSSPrimitiveValue>(valueItem), conversionData);
else if (i == 1)
ty = convertToFloatLength(downcast<CSSPrimitiveValue>(valueItem), conversionData);
else if (i == 2) {
type = TransformOperation::TRANSLATE_3D;
tz = convertToFloatLength(downcast<CSSPrimitiveValue>(valueItem), conversionData);
}
}
return TranslateTransformOperation::create(tx, ty, tz, type);
}
RefPtr<ScaleTransformOperation> scaleForValue(const CSSValue& value)
{
if (!is<CSSValueList>(value))
return nullptr;
auto& valueList = downcast<CSSValueList>(value);
if (!valueList.length())
return nullptr;
auto type = TransformOperation::SCALE;
double sx = 1.0;
double sy = 1.0;
double sz = 1.0;
for (unsigned i = 0; i < valueList.length(); ++i) {
auto* valueItem = valueList.itemWithoutBoundsCheck(i);
if (!is<CSSPrimitiveValue>(valueItem))
return nullptr;
if (!i) {
sx = downcast<CSSPrimitiveValue>(*valueItem).doubleValueDividingBy100IfPercentage();
sy = sx;
} else if (i == 1)
sy = downcast<CSSPrimitiveValue>(*valueItem).doubleValueDividingBy100IfPercentage();
else if (i == 2) {
type = TransformOperation::SCALE_3D;
sz = downcast<CSSPrimitiveValue>(*valueItem).doubleValueDividingBy100IfPercentage();
}
}
return ScaleTransformOperation::create(sx, sy, sz, type);
}
RefPtr<RotateTransformOperation> rotateForValue(const CSSValue& value)
{
if (!is<CSSValueList>(value))
return nullptr;
auto& valueList = downcast<CSSValueList>(value);
auto numberOfItems = valueList.length();
// There are three scenarios here since the rotation axis is defined either as:
// - no value: implicit 2d rotation
// - 1 value: an axis identifier (x/y/z)
// - 3 values: three numbers defining an x/y/z vector
// The angle is specified as the last value.
if (numberOfItems != 1 && numberOfItems != 2 && numberOfItems != 4)
return nullptr;
auto* lastValue = valueList.itemWithoutBoundsCheck(numberOfItems - 1);
if (!is<CSSPrimitiveValue>(lastValue))
return nullptr;
auto angle = downcast<CSSPrimitiveValue>(*lastValue).computeDegrees();
if (numberOfItems == 1)
return RotateTransformOperation::create(angle, TransformOperation::ROTATE);
double x = 0.0;
double y = 0.0;
double z = 0.0;
auto type = TransformOperation::ROTATE;
if (numberOfItems == 2) {
// An axis identifier was specified.
auto* axisIdentifierItem = valueList.itemWithoutBoundsCheck(0);
if (!is<CSSPrimitiveValue>(axisIdentifierItem))
return nullptr;
auto axisIdentifier = downcast<CSSPrimitiveValue>(*axisIdentifierItem).valueID();
if (axisIdentifier == CSSValueX) {
type = TransformOperation::ROTATE_X;
x = 1.0;
} else if (axisIdentifier == CSSValueY) {
type = TransformOperation::ROTATE_Y;
y = 1.0;
} else if (axisIdentifier == CSSValueZ) {
type = TransformOperation::ROTATE_3D;
z = 1.0;
} else
return nullptr;
} else if (numberOfItems == 4) {
// The axis was specified using a vector.
type = TransformOperation::ROTATE;
for (unsigned i = 0; i < 3; ++i) {
auto* valueItem = valueList.itemWithoutBoundsCheck(i);
if (!is<CSSPrimitiveValue>(valueItem))
return nullptr;
if (!i)
x = downcast<CSSPrimitiveValue>(*valueItem).doubleValue();
else if (i == 1)
y = downcast<CSSPrimitiveValue>(*valueItem).doubleValue();
else if (i == 2) {
type = TransformOperation::ROTATE_3D;
z = downcast<CSSPrimitiveValue>(*valueItem).doubleValue();
}
}
}
return RotateTransformOperation::create(x, y, z, angle, type);
}
}