| /* |
| * Copyright (C) 2004, 2005, 2006 Nikolas Zimmermann <zimmermann@kde.org> |
| * Copyright (C) 2004, 2005, 2006, 2007 Rob Buis <buis@kde.org> |
| * Copyright (C) 2007-2019 Apple Inc. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "config.h" |
| #include "SVGLengthValue.h" |
| |
| #include "AnimationUtilities.h" |
| #include "CSSPrimitiveValue.h" |
| #include "SVGLengthContext.h" |
| #include "SVGParserUtilities.h" |
| #include <wtf/text/StringConcatenateNumbers.h> |
| #include <wtf/text/StringParsingBuffer.h> |
| #include <wtf/text/TextStream.h> |
| |
| namespace WebCore { |
| |
| static inline const char* lengthTypeToString(SVGLengthType lengthType) |
| { |
| switch (lengthType) { |
| case SVGLengthType::Unknown: |
| case SVGLengthType::Number: |
| return ""; |
| case SVGLengthType::Percentage: |
| return "%"; |
| case SVGLengthType::Ems: |
| return "em"; |
| case SVGLengthType::Exs: |
| return "ex"; |
| case SVGLengthType::Pixels: |
| return "px"; |
| case SVGLengthType::Centimeters: |
| return "cm"; |
| case SVGLengthType::Millimeters: |
| return "mm"; |
| case SVGLengthType::Inches: |
| return "in"; |
| case SVGLengthType::Points: |
| return "pt"; |
| case SVGLengthType::Picas: |
| return "pc"; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return ""; |
| } |
| |
| template<typename CharacterType> static inline SVGLengthType parseLengthType(StringParsingBuffer<CharacterType>& buffer) |
| { |
| if (buffer.atEnd()) |
| return SVGLengthType::Number; |
| |
| auto firstChar = *buffer++; |
| |
| if (buffer.atEnd()) |
| return firstChar == '%' ? SVGLengthType::Percentage : SVGLengthType::Unknown; |
| |
| auto secondChar = *buffer++; |
| |
| if (!buffer.atEnd()) |
| return SVGLengthType::Unknown; |
| |
| if (firstChar == 'e' && secondChar == 'm') |
| return SVGLengthType::Ems; |
| if (firstChar == 'e' && secondChar == 'x') |
| return SVGLengthType::Exs; |
| if (firstChar == 'p' && secondChar == 'x') |
| return SVGLengthType::Pixels; |
| if (firstChar == 'c' && secondChar == 'm') |
| return SVGLengthType::Centimeters; |
| if (firstChar == 'm' && secondChar == 'm') |
| return SVGLengthType::Millimeters; |
| if (firstChar == 'i' && secondChar == 'n') |
| return SVGLengthType::Inches; |
| if (firstChar == 'p' && secondChar == 't') |
| return SVGLengthType::Points; |
| if (firstChar == 'p' && secondChar == 'c') |
| return SVGLengthType::Picas; |
| |
| return SVGLengthType::Unknown; |
| } |
| |
| static inline SVGLengthType primitiveTypeToLengthType(CSSUnitType primitiveType) |
| { |
| switch (primitiveType) { |
| case CSSUnitType::CSS_UNKNOWN: |
| return SVGLengthType::Unknown; |
| case CSSUnitType::CSS_NUMBER: |
| return SVGLengthType::Number; |
| case CSSUnitType::CSS_PERCENTAGE: |
| return SVGLengthType::Percentage; |
| case CSSUnitType::CSS_EMS: |
| return SVGLengthType::Ems; |
| case CSSUnitType::CSS_EXS: |
| return SVGLengthType::Exs; |
| case CSSUnitType::CSS_PX: |
| return SVGLengthType::Pixels; |
| case CSSUnitType::CSS_CM: |
| return SVGLengthType::Centimeters; |
| case CSSUnitType::CSS_MM: |
| return SVGLengthType::Millimeters; |
| case CSSUnitType::CSS_IN: |
| return SVGLengthType::Inches; |
| case CSSUnitType::CSS_PT: |
| return SVGLengthType::Points; |
| case CSSUnitType::CSS_PC: |
| return SVGLengthType::Picas; |
| default: |
| return SVGLengthType::Unknown; |
| } |
| |
| return SVGLengthType::Unknown; |
| } |
| |
| static inline CSSUnitType lengthTypeToPrimitiveType(SVGLengthType lengthType) |
| { |
| switch (lengthType) { |
| case SVGLengthType::Unknown: |
| return CSSUnitType::CSS_UNKNOWN; |
| case SVGLengthType::Number: |
| return CSSUnitType::CSS_NUMBER; |
| case SVGLengthType::Percentage: |
| return CSSUnitType::CSS_PERCENTAGE; |
| case SVGLengthType::Ems: |
| return CSSUnitType::CSS_EMS; |
| case SVGLengthType::Exs: |
| return CSSUnitType::CSS_EXS; |
| case SVGLengthType::Pixels: |
| return CSSUnitType::CSS_PX; |
| case SVGLengthType::Centimeters: |
| return CSSUnitType::CSS_CM; |
| case SVGLengthType::Millimeters: |
| return CSSUnitType::CSS_MM; |
| case SVGLengthType::Inches: |
| return CSSUnitType::CSS_IN; |
| case SVGLengthType::Points: |
| return CSSUnitType::CSS_PT; |
| case SVGLengthType::Picas: |
| return CSSUnitType::CSS_PC; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return CSSUnitType::CSS_UNKNOWN; |
| } |
| |
| SVGLengthValue::SVGLengthValue(SVGLengthMode lengthMode, const String& valueAsString) |
| : m_lengthMode(lengthMode) |
| { |
| setValueAsString(valueAsString); |
| } |
| |
| SVGLengthValue::SVGLengthValue(float valueInSpecifiedUnits, SVGLengthType lengthType, SVGLengthMode lengthMode) |
| : m_valueInSpecifiedUnits(valueInSpecifiedUnits) |
| , m_lengthType(lengthType) |
| , m_lengthMode(lengthMode) |
| { |
| ASSERT(m_lengthType != SVGLengthType::Unknown); |
| } |
| |
| SVGLengthValue::SVGLengthValue(const SVGLengthContext& context, float value, SVGLengthType lengthType, SVGLengthMode lengthMode) |
| : m_lengthType(lengthType) |
| , m_lengthMode(lengthMode) |
| { |
| setValue(context, value); |
| } |
| |
| std::optional<SVGLengthValue> SVGLengthValue::construct(SVGLengthMode lengthMode, StringView valueAsString) |
| { |
| SVGLengthValue length { lengthMode }; |
| if (length.setValueAsString(valueAsString).hasException()) |
| return std::nullopt; |
| return length; |
| } |
| |
| SVGLengthValue SVGLengthValue::construct(SVGLengthMode lengthMode, StringView valueAsString, SVGParsingError& parseError, SVGLengthNegativeValuesMode negativeValuesMode) |
| { |
| SVGLengthValue length(lengthMode); |
| |
| if (length.setValueAsString(valueAsString).hasException()) |
| parseError = ParsingAttributeFailedError; |
| else if (negativeValuesMode == SVGLengthNegativeValuesMode::Forbid && length.valueInSpecifiedUnits() < 0) |
| parseError = NegativeValueForbiddenError; |
| |
| return length; |
| } |
| |
| SVGLengthValue SVGLengthValue::blend(const SVGLengthValue& from, const SVGLengthValue& to, float progress) |
| { |
| if ((from.isZero() && to.isZero()) |
| || from.lengthType() == SVGLengthType::Unknown |
| || to.lengthType() == SVGLengthType::Unknown |
| || (!from.isZero() && from.lengthType() != SVGLengthType::Percentage && to.lengthType() == SVGLengthType::Percentage) |
| || (!to.isZero() && from.lengthType() == SVGLengthType::Percentage && to.lengthType() != SVGLengthType::Percentage) |
| || (!from.isZero() && !to.isZero() && (from.lengthType() == SVGLengthType::Ems || from.lengthType() == SVGLengthType::Exs) && from.lengthType() != to.lengthType())) |
| return to; |
| |
| if (from.lengthType() == SVGLengthType::Percentage || to.lengthType() == SVGLengthType::Percentage) { |
| auto fromPercent = from.valueAsPercentage() * 100; |
| auto toPercent = to.valueAsPercentage() * 100; |
| return { WebCore::blend(fromPercent, toPercent, { progress }), SVGLengthType::Percentage }; |
| } |
| |
| if (from.lengthType() == to.lengthType() || from.isZero() || to.isZero() || from.isRelative()) { |
| auto fromValue = from.valueInSpecifiedUnits(); |
| auto toValue = to.valueInSpecifiedUnits(); |
| return { WebCore::blend(fromValue, toValue, { progress }), to.isZero() ? from.lengthType() : to.lengthType() }; |
| } |
| |
| SVGLengthContext nonRelativeLengthContext(nullptr); |
| auto fromValueInUserUnits = nonRelativeLengthContext.convertValueToUserUnits(from.valueInSpecifiedUnits(), from.lengthType(), from.lengthMode()); |
| if (fromValueInUserUnits.hasException()) |
| return { }; |
| |
| auto fromValue = nonRelativeLengthContext.convertValueFromUserUnits(fromValueInUserUnits.releaseReturnValue(), to.lengthType(), to.lengthMode()); |
| if (fromValue.hasException()) |
| return { }; |
| |
| float toValue = to.valueInSpecifiedUnits(); |
| return { WebCore::blend(fromValue.releaseReturnValue(), toValue, { progress }), to.lengthType() }; |
| } |
| |
| SVGLengthValue SVGLengthValue::fromCSSPrimitiveValue(const CSSPrimitiveValue& value) |
| { |
| // FIXME: This needs to call value.computeLength() so it can correctly resolve non-absolute units (webkit.org/b/204826). |
| SVGLengthType lengthType = primitiveTypeToLengthType(value.primitiveType()); |
| return lengthType == SVGLengthType::Unknown ? SVGLengthValue() : SVGLengthValue(value.floatValue(), lengthType); |
| } |
| |
| Ref<CSSPrimitiveValue> SVGLengthValue::toCSSPrimitiveValue(const SVGLengthValue& length) |
| { |
| return CSSPrimitiveValue::create(length.valueInSpecifiedUnits(), lengthTypeToPrimitiveType(length.lengthType())); |
| } |
| |
| ExceptionOr<void> SVGLengthValue::setValueAsString(StringView valueAsString, SVGLengthMode lengthMode) |
| { |
| m_valueInSpecifiedUnits = 0; |
| m_lengthMode = lengthMode; |
| m_lengthType = SVGLengthType::Number; |
| return setValueAsString(valueAsString); |
| } |
| |
| float SVGLengthValue::value(const SVGLengthContext& context) const |
| { |
| auto result = valueForBindings(context); |
| if (result.hasException()) |
| return 0; |
| return result.releaseReturnValue(); |
| } |
| |
| String SVGLengthValue::valueAsString() const |
| { |
| return makeString(m_valueInSpecifiedUnits, lengthTypeToString(m_lengthType)); |
| } |
| |
| ExceptionOr<float> SVGLengthValue::valueForBindings(const SVGLengthContext& context) const |
| { |
| return context.convertValueToUserUnits(m_valueInSpecifiedUnits, m_lengthType, m_lengthMode); |
| } |
| |
| ExceptionOr<void> SVGLengthValue::setValue(const SVGLengthContext& context, float value) |
| { |
| // 100% = 100.0 instead of 1.0 for historical reasons, this could eventually be changed |
| if (m_lengthType == SVGLengthType::Percentage) |
| value = value / 100; |
| |
| auto convertedValue = context.convertValueFromUserUnits(value, m_lengthType, m_lengthMode); |
| if (convertedValue.hasException()) |
| return convertedValue.releaseException(); |
| m_valueInSpecifiedUnits = convertedValue.releaseReturnValue(); |
| return { }; |
| } |
| |
| ExceptionOr<void> SVGLengthValue::setValue(const SVGLengthContext& context, float value, SVGLengthType lengthType, SVGLengthMode lengthMode) |
| { |
| // FIXME: Seems like a bug that we change the value of m_unit even if setValue throws an exception. |
| m_lengthMode = lengthMode; |
| m_lengthType = lengthType; |
| return setValue(context, value); |
| } |
| |
| ExceptionOr<void> SVGLengthValue::setValueAsString(StringView string) |
| { |
| if (string.isEmpty()) |
| return { }; |
| |
| return readCharactersForParsing(string, [&](auto buffer) -> ExceptionOr<void> { |
| auto convertedNumber = parseNumber(buffer, SuffixSkippingPolicy::DontSkip); |
| if (!convertedNumber) |
| return Exception { SyntaxError }; |
| |
| auto lengthType = parseLengthType(buffer); |
| if (lengthType == SVGLengthType::Unknown) |
| return Exception { SyntaxError }; |
| |
| m_lengthType = lengthType; |
| m_valueInSpecifiedUnits = *convertedNumber; |
| return { }; |
| }); |
| } |
| |
| ExceptionOr<void> SVGLengthValue::convertToSpecifiedUnits(const SVGLengthContext& context, SVGLengthType lengthType) |
| { |
| auto valueInUserUnits = valueForBindings(context); |
| if (valueInUserUnits.hasException()) |
| return valueInUserUnits.releaseException(); |
| |
| auto originalLengthType = m_lengthType; |
| m_lengthType = lengthType; |
| auto result = setValue(context, valueInUserUnits.releaseReturnValue()); |
| if (!result.hasException()) |
| return { }; |
| |
| m_lengthType = originalLengthType; |
| return result.releaseException(); |
| } |
| |
| TextStream& operator<<(TextStream& ts, const SVGLengthValue& length) |
| { |
| ts << length.valueAsString(); |
| return ts; |
| } |
| |
| } |