blob: 0a2b699aca7f04b97bf6ae767f3f2483113f373c [file] [log] [blame]
/*
* Copyright (C) 2004, 2005, 2006 Nikolas Zimmermann <zimmermann@kde.org>
* Copyright (C) 2004, 2005, 2006, 2007 Rob Buis <buis@kde.org>
* Copyright (C) 2007 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 "core/svg/SVGLength.h"
#include "core/SVGNames.h"
#include "core/css/CSSPrimitiveValue.h"
#include "core/css/CSSValue.h"
#include "core/css/parser/CSSParser.h"
#include "core/svg/SVGAnimationElement.h"
#include "wtf/MathExtras.h"
#include "wtf/text/WTFString.h"
namespace blink {
SVGLength::SVGLength(SVGLengthMode mode)
: m_value(
CSSPrimitiveValue::create(0, CSSPrimitiveValue::UnitType::UserUnits)),
m_unitMode(static_cast<unsigned>(mode)) {
ASSERT(unitMode() == mode);
}
SVGLength::SVGLength(const SVGLength& o)
: m_value(o.m_value), m_unitMode(o.m_unitMode) {}
DEFINE_TRACE(SVGLength) {
visitor->trace(m_value);
SVGPropertyBase::trace(visitor);
}
SVGLength* SVGLength::clone() const {
return new SVGLength(*this);
}
SVGPropertyBase* SVGLength::cloneForAnimation(const String& value) const {
SVGLength* length = create();
length->m_unitMode = m_unitMode;
if (length->setValueAsString(value) != SVGParseStatus::NoError)
length->m_value =
CSSPrimitiveValue::create(0, CSSPrimitiveValue::UnitType::UserUnits);
return length;
}
bool SVGLength::operator==(const SVGLength& other) const {
return m_unitMode == other.m_unitMode && m_value == other.m_value;
}
float SVGLength::value(const SVGLengthContext& context) const {
if (isCalculated())
return context.resolveValue(*asCSSPrimitiveValue(), unitMode());
return context.convertValueToUserUnits(m_value->getFloatValue(), unitMode(),
m_value->typeWithCalcResolved());
}
void SVGLength::setValueAsNumber(float value) {
m_value =
CSSPrimitiveValue::create(value, CSSPrimitiveValue::UnitType::UserUnits);
}
void SVGLength::setValue(float value, const SVGLengthContext& context) {
m_value = CSSPrimitiveValue::create(
context.convertValueFromUserUnits(value, unitMode(),
m_value->typeWithCalcResolved()),
m_value->typeWithCalcResolved());
}
static bool isCalcCSSUnitType(CSSPrimitiveValue::UnitType type) {
return type >= CSSPrimitiveValue::UnitType::Calc &&
type <= CSSPrimitiveValue::UnitType::CalcPercentageWithLengthAndNumber;
}
static bool isSupportedCSSUnitType(CSSPrimitiveValue::UnitType type) {
return (CSSPrimitiveValue::isLength(type) ||
type == CSSPrimitiveValue::UnitType::Number ||
type == CSSPrimitiveValue::UnitType::Percentage ||
isCalcCSSUnitType(type)) &&
type != CSSPrimitiveValue::UnitType::QuirkyEms;
}
void SVGLength::setUnitType(CSSPrimitiveValue::UnitType type) {
ASSERT(isSupportedCSSUnitType(type));
m_value = CSSPrimitiveValue::create(m_value->getFloatValue(), type);
}
float SVGLength::valueAsPercentage() const {
// LengthTypePercentage is represented with 100% = 100.0. Good for accuracy
// but could eventually be changed.
if (m_value->isPercentage()) {
// Note: This division is a source of floating point inaccuracy.
return m_value->getFloatValue() / 100;
}
return m_value->getFloatValue();
}
float SVGLength::valueAsPercentage100() const {
// LengthTypePercentage is represented with 100% = 100.0. Good for accuracy
// but could eventually be changed.
if (m_value->isPercentage())
return m_value->getFloatValue();
return m_value->getFloatValue() * 100;
}
float SVGLength::scaleByPercentage(float input) const {
float result = input * m_value->getFloatValue();
if (m_value->isPercentage()) {
// Delaying division by 100 as long as possible since it introduces floating
// point errors.
result = result / 100;
}
return result;
}
SVGParsingError SVGLength::setValueAsString(const String& string) {
if (string.isEmpty()) {
m_value =
CSSPrimitiveValue::create(0, CSSPrimitiveValue::UnitType::UserUnits);
return SVGParseStatus::NoError;
}
CSSParserContext svgParserContext(SVGAttributeMode, nullptr);
const CSSValue* parsed =
CSSParser::parseSingleValue(CSSPropertyX, string, svgParserContext);
if (!parsed || !parsed->isPrimitiveValue())
return SVGParseStatus::ExpectedLength;
const CSSPrimitiveValue* newValue = toCSSPrimitiveValue(parsed);
if (!isSupportedCSSUnitType(newValue->typeWithCalcResolved()))
return SVGParseStatus::ExpectedLength;
m_value = newValue;
return SVGParseStatus::NoError;
}
String SVGLength::valueAsString() const {
return m_value->customCSSText();
}
void SVGLength::newValueSpecifiedUnits(CSSPrimitiveValue::UnitType type,
float value) {
m_value = CSSPrimitiveValue::create(value, type);
}
void SVGLength::convertToSpecifiedUnits(CSSPrimitiveValue::UnitType type,
const SVGLengthContext& context) {
ASSERT(isSupportedCSSUnitType(type));
float valueInUserUnits = value(context);
m_value = CSSPrimitiveValue::create(
context.convertValueFromUserUnits(valueInUserUnits, unitMode(), type),
type);
}
SVGLengthMode SVGLength::lengthModeForAnimatedLengthAttribute(
const QualifiedName& attrName) {
typedef HashMap<QualifiedName, SVGLengthMode> LengthModeForLengthAttributeMap;
DEFINE_STATIC_LOCAL(LengthModeForLengthAttributeMap, s_lengthModeMap, ());
if (s_lengthModeMap.isEmpty()) {
s_lengthModeMap.set(SVGNames::xAttr, SVGLengthMode::Width);
s_lengthModeMap.set(SVGNames::yAttr, SVGLengthMode::Height);
s_lengthModeMap.set(SVGNames::cxAttr, SVGLengthMode::Width);
s_lengthModeMap.set(SVGNames::cyAttr, SVGLengthMode::Height);
s_lengthModeMap.set(SVGNames::dxAttr, SVGLengthMode::Width);
s_lengthModeMap.set(SVGNames::dyAttr, SVGLengthMode::Height);
s_lengthModeMap.set(SVGNames::frAttr, SVGLengthMode::Other);
s_lengthModeMap.set(SVGNames::fxAttr, SVGLengthMode::Width);
s_lengthModeMap.set(SVGNames::fyAttr, SVGLengthMode::Height);
s_lengthModeMap.set(SVGNames::rAttr, SVGLengthMode::Other);
s_lengthModeMap.set(SVGNames::rxAttr, SVGLengthMode::Width);
s_lengthModeMap.set(SVGNames::ryAttr, SVGLengthMode::Height);
s_lengthModeMap.set(SVGNames::widthAttr, SVGLengthMode::Width);
s_lengthModeMap.set(SVGNames::heightAttr, SVGLengthMode::Height);
s_lengthModeMap.set(SVGNames::x1Attr, SVGLengthMode::Width);
s_lengthModeMap.set(SVGNames::x2Attr, SVGLengthMode::Width);
s_lengthModeMap.set(SVGNames::y1Attr, SVGLengthMode::Height);
s_lengthModeMap.set(SVGNames::y2Attr, SVGLengthMode::Height);
s_lengthModeMap.set(SVGNames::refXAttr, SVGLengthMode::Width);
s_lengthModeMap.set(SVGNames::refYAttr, SVGLengthMode::Height);
s_lengthModeMap.set(SVGNames::markerWidthAttr, SVGLengthMode::Width);
s_lengthModeMap.set(SVGNames::markerHeightAttr, SVGLengthMode::Height);
s_lengthModeMap.set(SVGNames::textLengthAttr, SVGLengthMode::Width);
s_lengthModeMap.set(SVGNames::startOffsetAttr, SVGLengthMode::Width);
}
if (s_lengthModeMap.contains(attrName))
return s_lengthModeMap.get(attrName);
return SVGLengthMode::Other;
}
bool SVGLength::negativeValuesForbiddenForAnimatedLengthAttribute(
const QualifiedName& attrName) {
DEFINE_STATIC_LOCAL(
HashSet<QualifiedName>, noNegativeValuesSet,
({
SVGNames::frAttr, SVGNames::rAttr, SVGNames::rxAttr, SVGNames::ryAttr,
SVGNames::widthAttr, SVGNames::heightAttr, SVGNames::markerWidthAttr,
SVGNames::markerHeightAttr, SVGNames::textLengthAttr,
}));
return noNegativeValuesSet.contains(attrName);
}
void SVGLength::add(SVGPropertyBase* other, SVGElement* contextElement) {
SVGLengthContext lengthContext(contextElement);
setValue(value(lengthContext) + toSVGLength(other)->value(lengthContext),
lengthContext);
}
void SVGLength::calculateAnimatedValue(SVGAnimationElement* animationElement,
float percentage,
unsigned repeatCount,
SVGPropertyBase* fromValue,
SVGPropertyBase* toValue,
SVGPropertyBase* toAtEndOfDurationValue,
SVGElement* contextElement) {
SVGLength* fromLength = toSVGLength(fromValue);
SVGLength* toLength = toSVGLength(toValue);
SVGLength* toAtEndOfDurationLength = toSVGLength(toAtEndOfDurationValue);
SVGLengthContext lengthContext(contextElement);
float animatedNumber = value(lengthContext);
animationElement->animateAdditiveNumber(
percentage, repeatCount, fromLength->value(lengthContext),
toLength->value(lengthContext),
toAtEndOfDurationLength->value(lengthContext), animatedNumber);
ASSERT(unitMode() == lengthModeForAnimatedLengthAttribute(
animationElement->attributeName()));
// TODO(shanmuga.m): Construct a calc() expression if the units fall in
// different categories.
CSSPrimitiveValue::UnitType newUnit = CSSPrimitiveValue::UnitType::UserUnits;
if (percentage < 0.5) {
if (!fromLength->isCalculated())
newUnit = fromLength->typeWithCalcResolved();
} else {
if (!toLength->isCalculated())
newUnit = toLength->typeWithCalcResolved();
}
animatedNumber = lengthContext.convertValueFromUserUnits(animatedNumber,
unitMode(), newUnit);
m_value = CSSPrimitiveValue::create(animatedNumber, newUnit);
}
float SVGLength::calculateDistance(SVGPropertyBase* toValue,
SVGElement* contextElement) {
SVGLengthContext lengthContext(contextElement);
SVGLength* toLength = toSVGLength(toValue);
return fabsf(toLength->value(lengthContext) - value(lengthContext));
}
} // namespace blink