| /* |
| * 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); |
| } |
| |
| } |